web-remarq 0.4.2 → 0.4.4

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
@@ -122,7 +122,7 @@ var AnnotationStorage = class {
122
122
  const raw = localStorage.getItem(STORAGE_KEY);
123
123
  if (raw) {
124
124
  const parsed = JSON.parse(raw);
125
- const _a = parsed, { version, annotations } = _a, rest = __objRest(_a, ["version", "annotations"]);
125
+ const _a3 = parsed, { version, annotations } = _a3, rest = __objRest(_a3, ["version", "annotations"]);
126
126
  this.annotations = annotations != null ? annotations : [];
127
127
  this.extraFields = rest;
128
128
  this.migrateViewportBuckets();
@@ -204,13 +204,13 @@ function detectRemarqPlugin(el) {
204
204
  };
205
205
  }
206
206
  function detectExternalSource(el) {
207
- var _a;
208
- const source = (_a = el.dataset.source) != null ? _a : el.getAttribute("data-locator");
207
+ var _a3;
208
+ const source = (_a3 = el.dataset.source) != null ? _a3 : el.getAttribute("data-locator");
209
209
  if (!source) return { source: null, component: null };
210
210
  return { source, component: null };
211
211
  }
212
212
  function detectReactFiber(el) {
213
- var _a, _b, _c, _d;
213
+ var _a3, _b, _c, _d;
214
214
  const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
215
215
  if (!key) return { source: null, component: null };
216
216
  let current = el[key];
@@ -218,7 +218,7 @@ function detectReactFiber(el) {
218
218
  while (current && depth < 15) {
219
219
  const debugSource = current._debugSource;
220
220
  if (debugSource == null ? void 0 : debugSource.fileName) {
221
- const source = `${debugSource.fileName}:${(_a = debugSource.lineNumber) != null ? _a : 0}:${(_b = debugSource.columnNumber) != null ? _b : 0}`;
221
+ const source = `${debugSource.fileName}:${(_a3 = debugSource.lineNumber) != null ? _a3 : 0}:${(_b = debugSource.columnNumber) != null ? _b : 0}`;
222
222
  const fiberType = current.type;
223
223
  const component = typeof fiberType === "object" && fiberType ? (_d = (_c = fiberType.displayName) != null ? _c : fiberType.name) != null ? _d : null : null;
224
224
  return { source, component };
@@ -239,8 +239,8 @@ function detectSource(el) {
239
239
  // src/core/fingerprint.ts
240
240
  var TEXT_MAX_LENGTH = 50;
241
241
  function createFingerprint(el, options2) {
242
- var _a, _b, _c, _d, _e, _f, _g;
243
- const dataAttr = (_a = options2 == null ? void 0 : options2.dataAttribute) != null ? _a : "data-annotate";
242
+ var _a3, _b, _c, _d, _e, _f, _g;
243
+ const dataAttr = (_a3 = options2 == null ? void 0 : options2.dataAttribute) != null ? _a3 : "data-annotate";
244
244
  return __spreadValues({
245
245
  dataAnnotate: (_b = el.getAttribute(dataAttr)) != null ? _b : null,
246
246
  dataTestId: (_e = (_d = (_c = el.getAttribute("data-testid")) != null ? _c : el.getAttribute("data-test")) != null ? _d : el.getAttribute("data-cy")) != null ? _e : null,
@@ -285,11 +285,11 @@ function getStableId(el) {
285
285
  return id;
286
286
  }
287
287
  function getTextContent(el) {
288
- var _a, _b, _c;
288
+ var _a3, _b, _c;
289
289
  let text = "";
290
290
  for (const node of Array.from(el.childNodes)) {
291
291
  if (node.nodeType === Node.TEXT_NODE) {
292
- text += (_a = node.textContent) != null ? _a : "";
292
+ text += (_a3 = node.textContent) != null ? _a3 : "";
293
293
  }
294
294
  }
295
295
  text = text.trim();
@@ -374,13 +374,13 @@ function jaccardSimilarity(a, b) {
374
374
  return union === 0 ? 0 : intersection / union;
375
375
  }
376
376
  function scoreCandidate(el, fp, dataAttr) {
377
- var _a, _b;
377
+ var _a3, _b;
378
378
  let score = 0;
379
379
  const elAnnotate = el.getAttribute(dataAttr);
380
380
  if (fp.dataAnnotate && elAnnotate === fp.dataAnnotate) {
381
381
  score += 100;
382
382
  }
383
- const elText = (_b = (_a = el.textContent) == null ? void 0 : _a.trim().slice(0, 50)) != null ? _b : null;
383
+ const elText = (_b = (_a3 = el.textContent) == null ? void 0 : _a3.trim().slice(0, 50)) != null ? _b : null;
384
384
  const textSim = textSimilarity(fp.textContent, elText);
385
385
  if (textSim > 0.7) {
386
386
  score += textSim * 35;
@@ -432,8 +432,8 @@ function buildDomPath2(el) {
432
432
  return parts.join(" > ");
433
433
  }
434
434
  function matchElement(fp, options2) {
435
- var _a;
436
- const dataAttr = (_a = options2 == null ? void 0 : options2.dataAttribute) != null ? _a : "data-annotate";
435
+ var _a3;
436
+ const dataAttr = (_a3 = options2 == null ? void 0 : options2.dataAttribute) != null ? _a3 : "data-annotate";
437
437
  if (fp.dataAnnotate) {
438
438
  const el = document.querySelector(`[${dataAttr}="${fp.dataAnnotate}"]`);
439
439
  if (el) return el;
@@ -472,10 +472,10 @@ function parseSourceLocation(raw) {
472
472
  return { file, line, column: isNaN(column) ? 0 : column };
473
473
  }
474
474
  function resolveSource(fp) {
475
- var _a, _b;
475
+ var _a3, _b;
476
476
  if (fp.sourceLocation) {
477
477
  const parsed = parseSourceLocation(fp.sourceLocation);
478
- if (parsed) return __spreadProps(__spreadValues({}, parsed), { component: (_a = fp.componentName) != null ? _a : null });
478
+ if (parsed) return __spreadProps(__spreadValues({}, parsed), { component: (_a3 = fp.componentName) != null ? _a3 : null });
479
479
  }
480
480
  if (fp.detectedSource) {
481
481
  const parsed = parseSourceLocation(fp.detectedSource);
@@ -487,7 +487,7 @@ var TEMPLATE_GLOB = "*.{tsx,jsx,vue,svelte,html}";
487
487
  var CSS_MODULE_GLOB = "*.module.{css,scss,less}";
488
488
  var COMPONENT_GLOB = "*.{tsx,jsx,vue,ts,js}";
489
489
  function buildSearchHints(fp) {
490
- var _a, _b;
490
+ var _a3, _b;
491
491
  const grepQueries = [];
492
492
  if (fp.dataAnnotate) {
493
493
  grepQueries.push({ query: `data-annotate="${fp.dataAnnotate}"`, glob: TEMPLATE_GLOB, confidence: "high" });
@@ -507,7 +507,7 @@ function buildSearchHints(fp) {
507
507
  if (fp.role) {
508
508
  grepQueries.push({ query: `role="${fp.role}"`, glob: TEMPLATE_GLOB, confidence: "medium" });
509
509
  }
510
- if ((_a = fp.cssModules) == null ? void 0 : _a.length) {
510
+ if ((_a3 = fp.cssModules) == null ? void 0 : _a3.length) {
511
511
  for (const mod of fp.cssModules) {
512
512
  grepQueries.push({ query: `.${mod.localName}`, glob: CSS_MODULE_GLOB, confidence: "medium" });
513
513
  grepQueries.push({ query: `styles.${mod.localName}`, glob: COMPONENT_GLOB, confidence: "medium" });
@@ -574,8 +574,6 @@ var CSS = `
574
574
 
575
575
  .remarq-toolbar {
576
576
  position: fixed;
577
- bottom: 16px;
578
- right: 16px;
579
577
  z-index: 2147483647;
580
578
  display: flex;
581
579
  gap: 4px;
@@ -589,7 +587,12 @@ var CSS = `
589
587
  color: var(--remarq-text);
590
588
  }
591
589
 
592
- .remarq-toolbar.remarq-minimized { padding: 4px; }
590
+ .remarq-toolbar.remarq-pos-bottom-right { bottom: 16px; right: 16px; }
591
+ .remarq-toolbar.remarq-pos-bottom-left { bottom: 16px; left: 16px; flex-direction: row-reverse; }
592
+ .remarq-toolbar.remarq-pos-top-right { top: 16px; right: 16px; }
593
+ .remarq-toolbar.remarq-pos-top-left { top: 16px; left: 16px; flex-direction: row-reverse; }
594
+
595
+ .remarq-toolbar.remarq-minimized { padding: 8px; }
593
596
 
594
597
  .remarq-toolbar-btn {
595
598
  display: flex;
@@ -742,8 +745,6 @@ var CSS = `
742
745
 
743
746
  .remarq-detached-panel {
744
747
  position: fixed;
745
- bottom: 60px;
746
- right: 16px;
747
748
  z-index: 2147483646;
748
749
  width: 280px;
749
750
  max-height: 300px;
@@ -757,6 +758,11 @@ var CSS = `
757
758
  color: var(--remarq-text);
758
759
  }
759
760
 
761
+ .remarq-detached-panel.remarq-pos-bottom-right { bottom: 60px; right: 16px; }
762
+ .remarq-detached-panel.remarq-pos-bottom-left { bottom: 60px; left: 16px; }
763
+ .remarq-detached-panel.remarq-pos-top-right { top: 60px; right: 16px; }
764
+ .remarq-detached-panel.remarq-pos-top-left { top: 60px; left: 16px; }
765
+
760
766
  .remarq-detached-header {
761
767
  padding: 8px 12px;
762
768
  border-bottom: 1px solid var(--remarq-border);
@@ -811,6 +817,20 @@ var CSS = `
811
817
  overflow: hidden;
812
818
  }
813
819
 
820
+ .remarq-pos-top-right .remarq-export-menu,
821
+ .remarq-pos-top-left .remarq-export-menu {
822
+ bottom: auto;
823
+ top: 100%;
824
+ margin-bottom: 0;
825
+ margin-top: 4px;
826
+ }
827
+
828
+ .remarq-pos-bottom-left .remarq-export-menu,
829
+ .remarq-pos-top-left .remarq-export-menu {
830
+ right: auto;
831
+ left: 0;
832
+ }
833
+
814
834
  .remarq-export-menu button {
815
835
  display: block;
816
836
  width: 100%;
@@ -914,6 +934,70 @@ var CSS = `
914
934
  .remarq-toolbar-btn:disabled { opacity: 0.3; cursor: default; }
915
935
  .remarq-toolbar-btn:disabled:hover { background: transparent; }
916
936
 
937
+ .remarq-shortcuts-backdrop {
938
+ position: fixed;
939
+ top: 0;
940
+ left: 0;
941
+ width: 100%;
942
+ height: 100%;
943
+ background: rgba(0, 0, 0, 0.4);
944
+ z-index: 2147483647;
945
+ display: flex;
946
+ align-items: center;
947
+ justify-content: center;
948
+ }
949
+
950
+ .remarq-shortcuts-modal {
951
+ background: var(--remarq-bg);
952
+ border: 1px solid var(--remarq-border);
953
+ border-radius: 12px;
954
+ box-shadow: var(--remarq-shadow);
955
+ padding: 20px 24px;
956
+ min-width: 300px;
957
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
958
+ color: var(--remarq-text);
959
+ }
960
+
961
+ .remarq-shortcuts-title {
962
+ font-size: 15px;
963
+ font-weight: 600;
964
+ margin-bottom: 12px;
965
+ padding-bottom: 8px;
966
+ border-bottom: 1px solid var(--remarq-border);
967
+ }
968
+
969
+ .remarq-shortcuts-row {
970
+ display: flex;
971
+ align-items: center;
972
+ gap: 12px;
973
+ padding: 4px 0;
974
+ font-size: 13px;
975
+ }
976
+
977
+ .remarq-shortcuts-key {
978
+ display: inline-flex;
979
+ align-items: center;
980
+ justify-content: center;
981
+ min-width: 28px;
982
+ padding: 2px 8px;
983
+ background: var(--remarq-bg-secondary);
984
+ border: 1px solid var(--remarq-border);
985
+ border-radius: 4px;
986
+ font-family: inherit;
987
+ font-size: 12px;
988
+ font-weight: 600;
989
+ white-space: nowrap;
990
+ }
991
+
992
+ .remarq-shortcuts-context {
993
+ font-size: 10px;
994
+ color: var(--remarq-text-secondary);
995
+ background: var(--remarq-bg-secondary);
996
+ border-radius: 3px;
997
+ padding: 1px 6px;
998
+ margin-left: auto;
999
+ }
1000
+
917
1001
  .remarq-popup-hint {
918
1002
  font-size: 11px;
919
1003
  color: var(--remarq-text-secondary);
@@ -949,9 +1033,9 @@ function removeStyles() {
949
1033
  var THEME_KEY = "remarq:theme";
950
1034
  var ThemeManager = class {
951
1035
  constructor(parent, initialTheme) {
952
- var _a;
1036
+ var _a3;
953
1037
  const persisted = this.loadTheme();
954
- this.theme = (_a = initialTheme != null ? initialTheme : persisted) != null ? _a : "light";
1038
+ this.theme = (_a3 = initialTheme != null ? initialTheme : persisted) != null ? _a3 : "light";
955
1039
  this.container = document.createElement("div");
956
1040
  this.container.setAttribute("data-remarq-theme", this.theme);
957
1041
  parent.appendChild(this.container);
@@ -988,14 +1072,18 @@ var ThemeManager = class {
988
1072
  };
989
1073
 
990
1074
  // src/ui/toolbar.ts
1075
+ var _a;
1076
+ var isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test((_a = navigator.platform) != null ? _a : "");
1077
+ var modKey = isMac ? "\u2325" : "Alt";
991
1078
  var TOOLTIPS = {
992
- inspect: "Inspect element",
1079
+ inspect: `Inspect element (${modKey}+I)`,
993
1080
  spacing: "Spacing overlay (S)",
994
- copy: "Copy as Markdown",
1081
+ copy: "Copy as Markdown (C)",
995
1082
  export: "Export",
996
1083
  import: "Import JSON",
997
1084
  clear: "Clear all",
998
1085
  theme: "Toggle theme",
1086
+ help: "Keyboard shortcuts (?)",
999
1087
  minimize: "Minimize"
1000
1088
  };
1001
1089
  var ICONS = {
@@ -1006,17 +1094,19 @@ var ICONS = {
1006
1094
  import: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 10V2M4 6l4 4 4-4M2 12h12"/></svg>',
1007
1095
  clear: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 4h10M6 4V3h4v1M5 4v9h6V4"/></svg>',
1008
1096
  theme: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="3"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg>',
1097
+ help: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="6"/><path d="M6 6.5a2 2 0 0 1 3.5 1.5c0 1-1.5 1-1.5 2"/><circle cx="8" cy="12" r="0.5" fill="currentColor" stroke="none"/></svg>',
1009
1098
  minimize: '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 8h8"/></svg>'
1010
1099
  };
1011
1100
  var Toolbar = class {
1012
- constructor(container, callbacks) {
1101
+ constructor(container, callbacks, position = "bottom-right") {
1013
1102
  this.container = container;
1014
1103
  this.callbacks = callbacks;
1104
+ this.position = position;
1015
1105
  this.exportMenu = null;
1016
1106
  this.minimized = false;
1017
1107
  this.buttons = [];
1018
1108
  this.toolbarEl = document.createElement("div");
1019
- this.toolbarEl.className = "remarq-toolbar";
1109
+ this.toolbarEl.className = `remarq-toolbar remarq-pos-${position}`;
1020
1110
  this.inspectBtn = this.createButton("inspect", ICONS.inspect, () => callbacks.onInspect());
1021
1111
  this.badgeEl = document.createElement("span");
1022
1112
  this.badgeEl.className = "remarq-badge";
@@ -1037,8 +1127,9 @@ var Toolbar = class {
1037
1127
  const importBtn = this.createButton("import", ICONS.import, () => this.fileInput.click());
1038
1128
  const clearBtn = this.createButton("clear", ICONS.clear, () => callbacks.onClear());
1039
1129
  const themeBtn = this.createButton("theme", ICONS.theme, () => callbacks.onThemeToggle());
1130
+ const helpBtn = this.createButton("help", ICONS.help, () => callbacks.onHelp());
1040
1131
  const minimizeBtn = this.createButton("minimize", ICONS.minimize, () => this.toggleMinimize());
1041
- this.buttons = [this.inspectBtn, this.spacingBtn, copyBtn, exportBtn, importBtn, clearBtn, themeBtn];
1132
+ this.buttons = [this.inspectBtn, this.spacingBtn, copyBtn, exportBtn, importBtn, clearBtn, themeBtn, helpBtn];
1042
1133
  this.toolbarEl.appendChild(this.inspectBtn);
1043
1134
  this.toolbarEl.appendChild(this.spacingBtn);
1044
1135
  this.toolbarEl.appendChild(copyBtn);
@@ -1046,6 +1137,7 @@ var Toolbar = class {
1046
1137
  this.toolbarEl.appendChild(importBtn);
1047
1138
  this.toolbarEl.appendChild(clearBtn);
1048
1139
  this.toolbarEl.appendChild(themeBtn);
1140
+ this.toolbarEl.appendChild(helpBtn);
1049
1141
  this.toolbarEl.appendChild(minimizeBtn);
1050
1142
  this.toolbarEl.appendChild(this.fileInput);
1051
1143
  container.appendChild(this.toolbarEl);
@@ -1077,11 +1169,11 @@ var Toolbar = class {
1077
1169
  this.toolbarEl.remove();
1078
1170
  }
1079
1171
  createButton(action, icon, handler) {
1080
- var _a;
1172
+ var _a3;
1081
1173
  const btn = document.createElement("button");
1082
1174
  btn.className = "remarq-toolbar-btn";
1083
1175
  btn.setAttribute("data-remarq-action", action);
1084
- btn.title = (_a = TOOLTIPS[action]) != null ? _a : "";
1176
+ btn.title = (_a3 = TOOLTIPS[action]) != null ? _a3 : "";
1085
1177
  btn.innerHTML = icon;
1086
1178
  btn.addEventListener("click", handler);
1087
1179
  return btn;
@@ -1203,11 +1295,11 @@ function describeElement(el) {
1203
1295
  return parts.join(" ");
1204
1296
  }
1205
1297
  function getDirectText(el) {
1206
- var _a, _b, _c;
1298
+ var _a3, _b, _c;
1207
1299
  let text = "";
1208
1300
  for (const node of Array.from(el.childNodes)) {
1209
1301
  if (node.nodeType === Node.TEXT_NODE) {
1210
- text += (_a = node.textContent) != null ? _a : "";
1302
+ text += (_a3 = node.textContent) != null ? _a3 : "";
1211
1303
  }
1212
1304
  }
1213
1305
  text = text.trim();
@@ -1725,8 +1817,8 @@ var MarkerManager = class {
1725
1817
  markerEl.textContent = String(this.counter);
1726
1818
  markerEl.title = annotation.comment;
1727
1819
  markerEl.addEventListener("click", () => {
1728
- var _a;
1729
- (_a = this.onClick) == null ? void 0 : _a.call(this, annotation.id);
1820
+ var _a3;
1821
+ (_a3 = this.onClick) == null ? void 0 : _a3.call(this, annotation.id);
1730
1822
  });
1731
1823
  this.container.appendChild(markerEl);
1732
1824
  this.markers.set(annotation.id, { annotation, target, markerEl });
@@ -1796,6 +1888,7 @@ var MarkerManager = class {
1796
1888
  // src/ui/toast.ts
1797
1889
  var currentToast = null;
1798
1890
  var currentTimer = null;
1891
+ var fadeTimer = null;
1799
1892
  function showToast(container, message, duration = 3e3) {
1800
1893
  hideToast();
1801
1894
  const toast = document.createElement("div");
@@ -1806,7 +1899,7 @@ function showToast(container, message, duration = 3e3) {
1806
1899
  currentTimer = setTimeout(() => {
1807
1900
  if (currentToast) {
1808
1901
  currentToast.classList.add("remarq-toast-fade");
1809
- setTimeout(() => hideToast(), 300);
1902
+ fadeTimer = setTimeout(() => hideToast(), 300);
1810
1903
  }
1811
1904
  }, duration);
1812
1905
  }
@@ -1815,6 +1908,10 @@ function hideToast() {
1815
1908
  clearTimeout(currentTimer);
1816
1909
  currentTimer = null;
1817
1910
  }
1911
+ if (fadeTimer) {
1912
+ clearTimeout(fadeTimer);
1913
+ fadeTimer = null;
1914
+ }
1818
1915
  if (currentToast) {
1819
1916
  currentToast.remove();
1820
1917
  currentToast = null;
@@ -1823,16 +1920,17 @@ function hideToast() {
1823
1920
 
1824
1921
  // src/ui/detached-panel.ts
1825
1922
  var DetachedPanel = class {
1826
- constructor(container, onDelete) {
1923
+ constructor(container, onDelete, position = "bottom-right") {
1827
1924
  this.container = container;
1828
1925
  this.onDelete = onDelete;
1926
+ this.position = position;
1829
1927
  this.panelEl = null;
1830
1928
  }
1831
1929
  update(otherBreakpoint, detached) {
1832
1930
  this.remove();
1833
1931
  if (otherBreakpoint.length === 0 && detached.length === 0) return;
1834
1932
  const panel = document.createElement("div");
1835
- panel.className = "remarq-detached-panel";
1933
+ panel.className = `remarq-detached-panel remarq-pos-${this.position}`;
1836
1934
  if (otherBreakpoint.length > 0) {
1837
1935
  this.renderSection(panel, `Other viewport (${otherBreakpoint.length})`, otherBreakpoint, "other");
1838
1936
  }
@@ -1880,8 +1978,8 @@ var DetachedPanel = class {
1880
1978
  deleteBtn.className = "remarq-detached-delete";
1881
1979
  deleteBtn.textContent = "\xD7";
1882
1980
  deleteBtn.addEventListener("click", () => {
1883
- var _a;
1884
- (_a = this.onDelete) == null ? void 0 : _a.call(this, ann.id);
1981
+ var _a3;
1982
+ (_a3 = this.onDelete) == null ? void 0 : _a3.call(this, ann.id);
1885
1983
  });
1886
1984
  item.appendChild(deleteBtn);
1887
1985
  }
@@ -1896,6 +1994,76 @@ var DetachedPanel = class {
1896
1994
  }
1897
1995
  };
1898
1996
 
1997
+ // src/ui/shortcuts-modal.ts
1998
+ var _a2;
1999
+ var isMac2 = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test((_a2 = navigator.platform) != null ? _a2 : "");
2000
+ var modKey2 = isMac2 ? "\u2325" : "Alt";
2001
+ var SHORTCUTS = [
2002
+ { key: `${modKey2}+I`, description: "Toggle inspect mode" },
2003
+ { key: "S", description: "Toggle spacing overlay", context: "inspect" },
2004
+ { key: "C", description: "Copy all annotations to clipboard", context: "inspect" },
2005
+ { key: "Esc", description: "Exit inspect mode / close popup" },
2006
+ { key: "?", description: "Show this help" },
2007
+ { key: "Enter", description: "Submit annotation", context: "popup" },
2008
+ { key: "Shift+Enter", description: "New line", context: "popup" }
2009
+ ];
2010
+ var modalEl = null;
2011
+ var keyHandler = null;
2012
+ function showShortcutsModal(container) {
2013
+ if (modalEl) {
2014
+ hideShortcutsModal();
2015
+ return;
2016
+ }
2017
+ const backdrop = document.createElement("div");
2018
+ backdrop.className = "remarq-shortcuts-backdrop";
2019
+ const modal = document.createElement("div");
2020
+ modal.className = "remarq-shortcuts-modal";
2021
+ const title = document.createElement("div");
2022
+ title.className = "remarq-shortcuts-title";
2023
+ title.textContent = "Keyboard Shortcuts";
2024
+ modal.appendChild(title);
2025
+ for (const s of SHORTCUTS) {
2026
+ const row = document.createElement("div");
2027
+ row.className = "remarq-shortcuts-row";
2028
+ const key = document.createElement("kbd");
2029
+ key.className = "remarq-shortcuts-key";
2030
+ key.textContent = s.key;
2031
+ const desc = document.createElement("span");
2032
+ desc.textContent = s.description;
2033
+ row.appendChild(key);
2034
+ row.appendChild(desc);
2035
+ if ("context" in s && s.context) {
2036
+ const badge = document.createElement("span");
2037
+ badge.className = "remarq-shortcuts-context";
2038
+ badge.textContent = s.context;
2039
+ row.appendChild(badge);
2040
+ }
2041
+ modal.appendChild(row);
2042
+ }
2043
+ backdrop.appendChild(modal);
2044
+ container.appendChild(backdrop);
2045
+ modalEl = backdrop;
2046
+ backdrop.addEventListener("click", (e) => {
2047
+ if (e.target === backdrop) hideShortcutsModal();
2048
+ });
2049
+ keyHandler = (e) => {
2050
+ if (e.key === "Escape" || e.key === "?") {
2051
+ hideShortcutsModal();
2052
+ }
2053
+ };
2054
+ document.addEventListener("keydown", keyHandler);
2055
+ }
2056
+ function hideShortcutsModal() {
2057
+ if (modalEl) {
2058
+ modalEl.remove();
2059
+ modalEl = null;
2060
+ }
2061
+ if (keyHandler) {
2062
+ document.removeEventListener("keydown", keyHandler);
2063
+ keyHandler = null;
2064
+ }
2065
+ }
2066
+
1899
2067
  // src/spa.ts
1900
2068
  var RouteObserver = class {
1901
2069
  constructor() {
@@ -1963,10 +2131,10 @@ var refreshScheduled = false;
1963
2131
  var savedCursor = "";
1964
2132
  var elementCache = /* @__PURE__ */ new Map();
1965
2133
  function describeTarget(el) {
1966
- var _a, _b, _c, _d;
2134
+ var _a3, _b, _c, _d;
1967
2135
  const parts = [];
1968
2136
  if (el.id) parts.push(`#${el.id}`);
1969
- const dataAnnotate = el.getAttribute((_a = options.dataAttribute) != null ? _a : "data-annotate");
2137
+ const dataAnnotate = el.getAttribute((_a3 = options.dataAttribute) != null ? _a3 : "data-annotate");
1970
2138
  const dataTestId = el.getAttribute("data-testid") || el.getAttribute("data-test") || el.getAttribute("data-cy");
1971
2139
  if (dataAnnotate) parts.push(`[${dataAnnotate}]`);
1972
2140
  else if (dataTestId) parts.push(`[${dataTestId}]`);
@@ -2101,8 +2269,9 @@ function handleInspectHover(e) {
2101
2269
  overlay.updateTooltipPosition(e.clientX, e.clientY);
2102
2270
  }
2103
2271
  function handleInspectKeydown(e) {
2104
- var _a, _b;
2105
- const tag = (_a = e.target) == null ? void 0 : _a.tagName;
2272
+ var _a3, _b;
2273
+ if (options.shortcuts === false) return;
2274
+ const tag = (_a3 = e.target) == null ? void 0 : _a3.tagName;
2106
2275
  if (tag === "INPUT" || tag === "TEXTAREA" || ((_b = e.target) == null ? void 0 : _b.isContentEditable)) return;
2107
2276
  if (e.key === "Escape" && inspecting) {
2108
2277
  setInspecting(false);
@@ -2114,13 +2283,20 @@ function handleInspectKeydown(e) {
2114
2283
  toolbar.setSpacingActive(spacingMode);
2115
2284
  if (!spacingMode) spacingOverlay.hide();
2116
2285
  }
2117
- if (e.key === "i") {
2286
+ if (e.altKey && e.code === "KeyI") {
2287
+ e.preventDefault();
2118
2288
  setInspecting(!inspecting);
2119
2289
  if (!inspecting) {
2120
2290
  overlay.hide();
2121
2291
  spacingOverlay.hide();
2122
2292
  }
2123
2293
  }
2294
+ if (e.key === "c" && inspecting) {
2295
+ copyToClipboard();
2296
+ }
2297
+ if (e.key === "?") {
2298
+ showShortcutsModal(themeManager.container);
2299
+ }
2124
2300
  }
2125
2301
  function setInspecting(value) {
2126
2302
  if (value && !inspecting) {
@@ -2141,7 +2317,7 @@ function setInspecting(value) {
2141
2317
  }
2142
2318
  }
2143
2319
  function handleMarkerClick(annotationId) {
2144
- var _a;
2320
+ var _a3;
2145
2321
  const ann = storage.getAll().find((a) => a.id === annotationId);
2146
2322
  if (!ann) return;
2147
2323
  const el = resolveElement(ann);
@@ -2150,7 +2326,7 @@ function handleMarkerClick(annotationId) {
2150
2326
  popup.showDetail(
2151
2327
  {
2152
2328
  tag: ann.fingerprint.tagName,
2153
- text: (_a = ann.fingerprint.textContent) != null ? _a : "",
2329
+ text: (_a3 = ann.fingerprint.textContent) != null ? _a3 : "",
2154
2330
  comment: ann.comment,
2155
2331
  status: ann.status
2156
2332
  },
@@ -2199,7 +2375,7 @@ function generateMarkdown() {
2199
2375
  if (!anns.length) return "";
2200
2376
  const lines = [`## Annotations \u2014 ${route} (${anns.length})`, ""];
2201
2377
  anns.forEach((ann, i) => {
2202
- var _a, _b;
2378
+ var _a3, _b;
2203
2379
  const fp = ann.fingerprint;
2204
2380
  lines.push(`### ${i + 1}. [${ann.status}] "${ann.comment}"`);
2205
2381
  let elDesc = `Element: <${fp.tagName}>`;
@@ -2230,7 +2406,7 @@ function generateMarkdown() {
2230
2406
  if (fp.textContent) {
2231
2407
  lines.push(`- \`"${fp.textContent}"\` \u2014 text content in templates`);
2232
2408
  }
2233
- if ((_a = fp.cssModules) == null ? void 0 : _a.length) {
2409
+ if ((_a3 = fp.cssModules) == null ? void 0 : _a3.length) {
2234
2410
  for (const mod of fp.cssModules) {
2235
2411
  lines.push(`- \`.${mod.localName}\` \u2014 in CSS Module file (likely \`${mod.moduleHint}.module.*\`)`);
2236
2412
  lines.push(`- \`styles.${mod.localName}\` \u2014 in component JS/TS`);
@@ -2269,13 +2445,36 @@ function exportJSON() {
2269
2445
  showToast(themeManager.container, "Exported as JSON");
2270
2446
  }
2271
2447
  function copyToClipboard() {
2448
+ var _a3;
2272
2449
  const md = generateMarkdown();
2273
- if (!md) return;
2274
- navigator.clipboard.writeText(md).then(() => {
2450
+ if (!md) {
2451
+ showToast(themeManager.container, "No annotations to copy");
2452
+ return;
2453
+ }
2454
+ if ((_a3 = navigator.clipboard) == null ? void 0 : _a3.writeText) {
2455
+ navigator.clipboard.writeText(md).then(() => {
2456
+ showToast(themeManager.container, "Copied to clipboard");
2457
+ }).catch(() => {
2458
+ fallbackCopy(md);
2459
+ });
2460
+ } else {
2461
+ fallbackCopy(md);
2462
+ }
2463
+ }
2464
+ function fallbackCopy(text) {
2465
+ const textarea = document.createElement("textarea");
2466
+ textarea.value = text;
2467
+ textarea.style.position = "fixed";
2468
+ textarea.style.opacity = "0";
2469
+ document.body.appendChild(textarea);
2470
+ textarea.select();
2471
+ try {
2472
+ document.execCommand("copy");
2275
2473
  showToast(themeManager.container, "Copied to clipboard");
2276
- }).catch(() => {
2277
- console.warn("[web-remarq] Clipboard write failed");
2278
- });
2474
+ } catch (e) {
2475
+ showToast(themeManager.container, "Failed to copy");
2476
+ }
2477
+ textarea.remove();
2279
2478
  }
2280
2479
  function exportAgent() {
2281
2480
  const anns = storage.getAll();
@@ -2295,10 +2494,13 @@ function copyAgentToClipboard() {
2295
2494
  }
2296
2495
  function setupMutationObserver() {
2297
2496
  mutationObserver = new MutationObserver((mutations) => {
2497
+ let hasExternalMutation = false;
2298
2498
  for (const m of mutations) {
2299
- if (m.target instanceof HTMLElement && m.target.closest("[data-remarq-theme]")) return;
2499
+ if (m.target instanceof HTMLElement && m.target.closest("[data-remarq-theme]")) continue;
2500
+ hasExternalMutation = true;
2501
+ break;
2300
2502
  }
2301
- scheduleRefresh();
2503
+ if (hasExternalMutation) scheduleRefresh();
2302
2504
  });
2303
2505
  mutationObserver.observe(document.body, {
2304
2506
  childList: true,
@@ -2309,6 +2511,7 @@ function setupMutationObserver() {
2309
2511
  }
2310
2512
  var WebRemarq = {
2311
2513
  init(opts) {
2514
+ var _a3;
2312
2515
  if (initialized) return;
2313
2516
  options = opts != null ? opts : {};
2314
2517
  try {
@@ -2319,11 +2522,12 @@ var WebRemarq = {
2319
2522
  spacingOverlay = new SpacingOverlay(themeManager.container);
2320
2523
  popup = new Popup(themeManager.container);
2321
2524
  markers = new MarkerManager(themeManager.container, handleMarkerClick);
2525
+ const position = (_a3 = options.position) != null ? _a3 : "bottom-right";
2322
2526
  detachedPanel = new DetachedPanel(themeManager.container, (id) => {
2323
2527
  elementCache.delete(id);
2324
2528
  storage.remove(id);
2325
2529
  refreshMarkers();
2326
- });
2530
+ }, position);
2327
2531
  toolbar = new Toolbar(themeManager.container, {
2328
2532
  onInspect: () => setInspecting(!inspecting),
2329
2533
  onSpacingToggle: () => {
@@ -2336,8 +2540,8 @@ var WebRemarq = {
2336
2540
  onExportMd: exportMarkdown,
2337
2541
  onExportJson: exportJSON,
2338
2542
  onImport: () => {
2339
- var _a;
2340
- const file = (_a = toolbar.getFileInput().files) == null ? void 0 : _a[0];
2543
+ var _a4;
2544
+ const file = (_a4 = toolbar.getFileInput().files) == null ? void 0 : _a4[0];
2341
2545
  if (file) {
2342
2546
  WebRemarq.import(file);
2343
2547
  }
@@ -2348,8 +2552,9 @@ var WebRemarq = {
2348
2552
  refreshMarkers();
2349
2553
  showToast(themeManager.container, "All annotations cleared");
2350
2554
  },
2351
- onThemeToggle: () => themeManager.toggle()
2352
- });
2555
+ onThemeToggle: () => themeManager.toggle(),
2556
+ onHelp: () => showShortcutsModal(themeManager.container)
2557
+ }, position);
2353
2558
  if (storage.isMemoryOnly) {
2354
2559
  toolbar.setMemoryWarning(true);
2355
2560
  }
@@ -2379,6 +2584,7 @@ var WebRemarq = {
2379
2584
  document.body.style.cursor = savedCursor;
2380
2585
  }
2381
2586
  hideToast();
2587
+ hideShortcutsModal();
2382
2588
  destroyViewportListener();
2383
2589
  unsubRoute == null ? void 0 : unsubRoute();
2384
2590
  routeObserver == null ? void 0 : routeObserver.destroy();