web-remarq 0.4.1 → 0.4.3

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,6 +587,11 @@ var CSS = `
589
587
  color: var(--remarq-text);
590
588
  }
591
589
 
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
+
592
595
  .remarq-toolbar.remarq-minimized { padding: 4px; }
593
596
 
594
597
  .remarq-toolbar-btn {
@@ -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();
@@ -1566,6 +1658,12 @@ var Popup = class {
1566
1658
  });
1567
1659
  actions.appendChild(resolveBtn);
1568
1660
  }
1661
+ const copyBtn = document.createElement("button");
1662
+ copyBtn.textContent = "Copy";
1663
+ copyBtn.addEventListener("click", () => {
1664
+ callbacks.onCopy();
1665
+ });
1666
+ actions.appendChild(copyBtn);
1569
1667
  const deleteBtn = document.createElement("button");
1570
1668
  deleteBtn.textContent = "Delete";
1571
1669
  deleteBtn.addEventListener("click", () => {
@@ -1719,17 +1817,19 @@ var MarkerManager = class {
1719
1817
  markerEl.textContent = String(this.counter);
1720
1818
  markerEl.title = annotation.comment;
1721
1819
  markerEl.addEventListener("click", () => {
1722
- var _a;
1723
- (_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);
1724
1822
  });
1725
1823
  this.container.appendChild(markerEl);
1726
1824
  this.markers.set(annotation.id, { annotation, target, markerEl });
1825
+ this.applyOutline(target, annotation.status);
1727
1826
  this.updatePosition(annotation.id);
1728
1827
  }
1729
1828
  removeMarker(id) {
1730
1829
  const entry = this.markers.get(id);
1731
1830
  if (entry) {
1732
1831
  entry.markerEl.remove();
1832
+ this.removeOutline(entry.target);
1733
1833
  this.markers.delete(id);
1734
1834
  }
1735
1835
  }
@@ -1743,6 +1843,7 @@ var MarkerManager = class {
1743
1843
  clear() {
1744
1844
  for (const entry of this.markers.values()) {
1745
1845
  entry.markerEl.remove();
1846
+ this.removeOutline(entry.target);
1746
1847
  }
1747
1848
  this.markers.clear();
1748
1849
  this.counter = 0;
@@ -1754,6 +1855,15 @@ var MarkerManager = class {
1754
1855
  }
1755
1856
  this.clear();
1756
1857
  }
1858
+ applyOutline(target, status) {
1859
+ const color = status === "pending" ? "#f97316" : "rgba(34, 197, 94, 0.5)";
1860
+ target.style.outline = `2px solid ${color}`;
1861
+ target.style.outlineOffset = "2px";
1862
+ }
1863
+ removeOutline(target) {
1864
+ target.style.outline = "";
1865
+ target.style.outlineOffset = "";
1866
+ }
1757
1867
  updatePosition(id) {
1758
1868
  const entry = this.markers.get(id);
1759
1869
  if (!entry) return;
@@ -1805,16 +1915,17 @@ function hideToast() {
1805
1915
 
1806
1916
  // src/ui/detached-panel.ts
1807
1917
  var DetachedPanel = class {
1808
- constructor(container, onDelete) {
1918
+ constructor(container, onDelete, position = "bottom-right") {
1809
1919
  this.container = container;
1810
1920
  this.onDelete = onDelete;
1921
+ this.position = position;
1811
1922
  this.panelEl = null;
1812
1923
  }
1813
1924
  update(otherBreakpoint, detached) {
1814
1925
  this.remove();
1815
1926
  if (otherBreakpoint.length === 0 && detached.length === 0) return;
1816
1927
  const panel = document.createElement("div");
1817
- panel.className = "remarq-detached-panel";
1928
+ panel.className = `remarq-detached-panel remarq-pos-${this.position}`;
1818
1929
  if (otherBreakpoint.length > 0) {
1819
1930
  this.renderSection(panel, `Other viewport (${otherBreakpoint.length})`, otherBreakpoint, "other");
1820
1931
  }
@@ -1862,8 +1973,8 @@ var DetachedPanel = class {
1862
1973
  deleteBtn.className = "remarq-detached-delete";
1863
1974
  deleteBtn.textContent = "\xD7";
1864
1975
  deleteBtn.addEventListener("click", () => {
1865
- var _a;
1866
- (_a = this.onDelete) == null ? void 0 : _a.call(this, ann.id);
1976
+ var _a3;
1977
+ (_a3 = this.onDelete) == null ? void 0 : _a3.call(this, ann.id);
1867
1978
  });
1868
1979
  item.appendChild(deleteBtn);
1869
1980
  }
@@ -1878,6 +1989,76 @@ var DetachedPanel = class {
1878
1989
  }
1879
1990
  };
1880
1991
 
1992
+ // src/ui/shortcuts-modal.ts
1993
+ var _a2;
1994
+ var isMac2 = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test((_a2 = navigator.platform) != null ? _a2 : "");
1995
+ var modKey2 = isMac2 ? "\u2325" : "Alt";
1996
+ var SHORTCUTS = [
1997
+ { key: `${modKey2}+I`, description: "Toggle inspect mode" },
1998
+ { key: "S", description: "Toggle spacing overlay", context: "inspect" },
1999
+ { key: "C", description: "Copy all annotations to clipboard", context: "inspect" },
2000
+ { key: "Esc", description: "Exit inspect mode / close popup" },
2001
+ { key: "?", description: "Show this help" },
2002
+ { key: "Enter", description: "Submit annotation", context: "popup" },
2003
+ { key: "Shift+Enter", description: "New line", context: "popup" }
2004
+ ];
2005
+ var modalEl = null;
2006
+ var keyHandler = null;
2007
+ function showShortcutsModal(container) {
2008
+ if (modalEl) {
2009
+ hideShortcutsModal();
2010
+ return;
2011
+ }
2012
+ const backdrop = document.createElement("div");
2013
+ backdrop.className = "remarq-shortcuts-backdrop";
2014
+ const modal = document.createElement("div");
2015
+ modal.className = "remarq-shortcuts-modal";
2016
+ const title = document.createElement("div");
2017
+ title.className = "remarq-shortcuts-title";
2018
+ title.textContent = "Keyboard Shortcuts";
2019
+ modal.appendChild(title);
2020
+ for (const s of SHORTCUTS) {
2021
+ const row = document.createElement("div");
2022
+ row.className = "remarq-shortcuts-row";
2023
+ const key = document.createElement("kbd");
2024
+ key.className = "remarq-shortcuts-key";
2025
+ key.textContent = s.key;
2026
+ const desc = document.createElement("span");
2027
+ desc.textContent = s.description;
2028
+ row.appendChild(key);
2029
+ row.appendChild(desc);
2030
+ if ("context" in s && s.context) {
2031
+ const badge = document.createElement("span");
2032
+ badge.className = "remarq-shortcuts-context";
2033
+ badge.textContent = s.context;
2034
+ row.appendChild(badge);
2035
+ }
2036
+ modal.appendChild(row);
2037
+ }
2038
+ backdrop.appendChild(modal);
2039
+ container.appendChild(backdrop);
2040
+ modalEl = backdrop;
2041
+ backdrop.addEventListener("click", (e) => {
2042
+ if (e.target === backdrop) hideShortcutsModal();
2043
+ });
2044
+ keyHandler = (e) => {
2045
+ if (e.key === "Escape" || e.key === "?") {
2046
+ hideShortcutsModal();
2047
+ }
2048
+ };
2049
+ document.addEventListener("keydown", keyHandler);
2050
+ }
2051
+ function hideShortcutsModal() {
2052
+ if (modalEl) {
2053
+ modalEl.remove();
2054
+ modalEl = null;
2055
+ }
2056
+ if (keyHandler) {
2057
+ document.removeEventListener("keydown", keyHandler);
2058
+ keyHandler = null;
2059
+ }
2060
+ }
2061
+
1881
2062
  // src/spa.ts
1882
2063
  var RouteObserver = class {
1883
2064
  constructor() {
@@ -1945,10 +2126,10 @@ var refreshScheduled = false;
1945
2126
  var savedCursor = "";
1946
2127
  var elementCache = /* @__PURE__ */ new Map();
1947
2128
  function describeTarget(el) {
1948
- var _a, _b, _c, _d;
2129
+ var _a3, _b, _c, _d;
1949
2130
  const parts = [];
1950
2131
  if (el.id) parts.push(`#${el.id}`);
1951
- const dataAnnotate = el.getAttribute((_a = options.dataAttribute) != null ? _a : "data-annotate");
2132
+ const dataAnnotate = el.getAttribute((_a3 = options.dataAttribute) != null ? _a3 : "data-annotate");
1952
2133
  const dataTestId = el.getAttribute("data-testid") || el.getAttribute("data-test") || el.getAttribute("data-cy");
1953
2134
  if (dataAnnotate) parts.push(`[${dataAnnotate}]`);
1954
2135
  else if (dataTestId) parts.push(`[${dataTestId}]`);
@@ -2083,8 +2264,9 @@ function handleInspectHover(e) {
2083
2264
  overlay.updateTooltipPosition(e.clientX, e.clientY);
2084
2265
  }
2085
2266
  function handleInspectKeydown(e) {
2086
- var _a, _b;
2087
- const tag = (_a = e.target) == null ? void 0 : _a.tagName;
2267
+ var _a3, _b;
2268
+ if (options.shortcuts === false) return;
2269
+ const tag = (_a3 = e.target) == null ? void 0 : _a3.tagName;
2088
2270
  if (tag === "INPUT" || tag === "TEXTAREA" || ((_b = e.target) == null ? void 0 : _b.isContentEditable)) return;
2089
2271
  if (e.key === "Escape" && inspecting) {
2090
2272
  setInspecting(false);
@@ -2096,13 +2278,20 @@ function handleInspectKeydown(e) {
2096
2278
  toolbar.setSpacingActive(spacingMode);
2097
2279
  if (!spacingMode) spacingOverlay.hide();
2098
2280
  }
2099
- if (e.key === "i") {
2281
+ if (e.key === "i" && e.altKey) {
2282
+ e.preventDefault();
2100
2283
  setInspecting(!inspecting);
2101
2284
  if (!inspecting) {
2102
2285
  overlay.hide();
2103
2286
  spacingOverlay.hide();
2104
2287
  }
2105
2288
  }
2289
+ if (e.key === "c" && inspecting) {
2290
+ copyToClipboard();
2291
+ }
2292
+ if (e.key === "?") {
2293
+ showShortcutsModal(themeManager.container);
2294
+ }
2106
2295
  }
2107
2296
  function setInspecting(value) {
2108
2297
  if (value && !inspecting) {
@@ -2123,7 +2312,7 @@ function setInspecting(value) {
2123
2312
  }
2124
2313
  }
2125
2314
  function handleMarkerClick(annotationId) {
2126
- var _a;
2315
+ var _a3;
2127
2316
  const ann = storage.getAll().find((a) => a.id === annotationId);
2128
2317
  if (!ann) return;
2129
2318
  const el = resolveElement(ann);
@@ -2132,7 +2321,7 @@ function handleMarkerClick(annotationId) {
2132
2321
  popup.showDetail(
2133
2322
  {
2134
2323
  tag: ann.fingerprint.tagName,
2135
- text: (_a = ann.fingerprint.textContent) != null ? _a : "",
2324
+ text: (_a3 = ann.fingerprint.textContent) != null ? _a3 : "",
2136
2325
  comment: ann.comment,
2137
2326
  status: ann.status
2138
2327
  },
@@ -2156,6 +2345,21 @@ function handleMarkerClick(annotationId) {
2156
2345
  onEdit: (newComment) => {
2157
2346
  storage.update(ann.id, { comment: newComment });
2158
2347
  refreshMarkers();
2348
+ },
2349
+ onCopy: () => {
2350
+ const fp = ann.fingerprint;
2351
+ const lines = [
2352
+ `[${ann.status}] "${ann.comment}"`,
2353
+ `Element: <${fp.tagName}>${fp.textContent ? ` "${fp.textContent}"` : ""}`,
2354
+ `Route: ${ann.route}`,
2355
+ `Viewport: ${ann.viewportBucket}px`
2356
+ ];
2357
+ if (fp.sourceLocation) lines.push(`Source: ${fp.sourceLocation}`);
2358
+ navigator.clipboard.writeText(lines.join("\n")).then(() => {
2359
+ showToast(themeManager.container, "Annotation copied");
2360
+ }).catch(() => {
2361
+ console.warn("[web-remarq] Clipboard write failed");
2362
+ });
2159
2363
  }
2160
2364
  }
2161
2365
  );
@@ -2166,7 +2370,7 @@ function generateMarkdown() {
2166
2370
  if (!anns.length) return "";
2167
2371
  const lines = [`## Annotations \u2014 ${route} (${anns.length})`, ""];
2168
2372
  anns.forEach((ann, i) => {
2169
- var _a, _b;
2373
+ var _a3, _b;
2170
2374
  const fp = ann.fingerprint;
2171
2375
  lines.push(`### ${i + 1}. [${ann.status}] "${ann.comment}"`);
2172
2376
  let elDesc = `Element: <${fp.tagName}>`;
@@ -2197,7 +2401,7 @@ function generateMarkdown() {
2197
2401
  if (fp.textContent) {
2198
2402
  lines.push(`- \`"${fp.textContent}"\` \u2014 text content in templates`);
2199
2403
  }
2200
- if ((_a = fp.cssModules) == null ? void 0 : _a.length) {
2404
+ if ((_a3 = fp.cssModules) == null ? void 0 : _a3.length) {
2201
2405
  for (const mod of fp.cssModules) {
2202
2406
  lines.push(`- \`.${mod.localName}\` \u2014 in CSS Module file (likely \`${mod.moduleHint}.module.*\`)`);
2203
2407
  lines.push(`- \`styles.${mod.localName}\` \u2014 in component JS/TS`);
@@ -2276,6 +2480,7 @@ function setupMutationObserver() {
2276
2480
  }
2277
2481
  var WebRemarq = {
2278
2482
  init(opts) {
2483
+ var _a3;
2279
2484
  if (initialized) return;
2280
2485
  options = opts != null ? opts : {};
2281
2486
  try {
@@ -2286,11 +2491,12 @@ var WebRemarq = {
2286
2491
  spacingOverlay = new SpacingOverlay(themeManager.container);
2287
2492
  popup = new Popup(themeManager.container);
2288
2493
  markers = new MarkerManager(themeManager.container, handleMarkerClick);
2494
+ const position = (_a3 = options.position) != null ? _a3 : "bottom-right";
2289
2495
  detachedPanel = new DetachedPanel(themeManager.container, (id) => {
2290
2496
  elementCache.delete(id);
2291
2497
  storage.remove(id);
2292
2498
  refreshMarkers();
2293
- });
2499
+ }, position);
2294
2500
  toolbar = new Toolbar(themeManager.container, {
2295
2501
  onInspect: () => setInspecting(!inspecting),
2296
2502
  onSpacingToggle: () => {
@@ -2303,8 +2509,8 @@ var WebRemarq = {
2303
2509
  onExportMd: exportMarkdown,
2304
2510
  onExportJson: exportJSON,
2305
2511
  onImport: () => {
2306
- var _a;
2307
- const file = (_a = toolbar.getFileInput().files) == null ? void 0 : _a[0];
2512
+ var _a4;
2513
+ const file = (_a4 = toolbar.getFileInput().files) == null ? void 0 : _a4[0];
2308
2514
  if (file) {
2309
2515
  WebRemarq.import(file);
2310
2516
  }
@@ -2315,8 +2521,9 @@ var WebRemarq = {
2315
2521
  refreshMarkers();
2316
2522
  showToast(themeManager.container, "All annotations cleared");
2317
2523
  },
2318
- onThemeToggle: () => themeManager.toggle()
2319
- });
2524
+ onThemeToggle: () => themeManager.toggle(),
2525
+ onHelp: () => showShortcutsModal(themeManager.container)
2526
+ }, position);
2320
2527
  if (storage.isMemoryOnly) {
2321
2528
  toolbar.setMemoryWarning(true);
2322
2529
  }
@@ -2346,6 +2553,7 @@ var WebRemarq = {
2346
2553
  document.body.style.cursor = savedCursor;
2347
2554
  }
2348
2555
  hideToast();
2556
+ hideShortcutsModal();
2349
2557
  destroyViewportListener();
2350
2558
  unsubRoute == null ? void 0 : unsubRoute();
2351
2559
  routeObserver == null ? void 0 : routeObserver.destroy();