@useclickly/react 1.0.1 → 1.0.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
@@ -1,5 +1,5 @@
1
1
  // packages/react/src/Clickly.tsx
2
- import { useEffect as useEffect7, useState as useState7 } from "react";
2
+ import { useEffect as useEffect8, useState as useState7 } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
 
5
5
  // packages/core/dist/index.js
@@ -1016,10 +1016,10 @@ function hasFixedAncestor(el) {
1016
1016
  }
1017
1017
 
1018
1018
  // packages/react/src/internal/ClicklyRoot.tsx
1019
- import { useEffect as useEffect6, useState as useState6 } from "react";
1019
+ import { useEffect as useEffect7, useState as useState6 } from "react";
1020
1020
 
1021
1021
  // packages/react/src/internal/Toolbar.tsx
1022
- import { useRef as useRef4, useState as useState3 } from "react";
1022
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
1023
1023
 
1024
1024
  // packages/react/src/hooks/useDraggable.ts
1025
1025
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -1332,6 +1332,18 @@ function Icon({
1332
1332
  }
1333
1333
  );
1334
1334
  }
1335
+ var IconFreeze = () => /* @__PURE__ */ jsxs(Icon, { size: 15, children: [
1336
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "2", x2: "12", y2: "22" }),
1337
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "12", x2: "22", y2: "12" }),
1338
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "5", x2: "19", y2: "19" }),
1339
+ /* @__PURE__ */ jsx("line", { x1: "19", y1: "5", x2: "5", y2: "19" }),
1340
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "2", fill: "currentColor", stroke: "none" })
1341
+ ] });
1342
+ var IconInfo = () => /* @__PURE__ */ jsxs(Icon, { size: 14, children: [
1343
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
1344
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "8", x2: "12", y2: "8", strokeWidth: "2.5" }),
1345
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "12", x2: "12", y2: "16" })
1346
+ ] });
1335
1347
  var IconCursor = () => /* @__PURE__ */ jsx(Icon, { children: /* @__PURE__ */ jsx("path", { d: "M4 4l6 16 2-7 7-2z" }) });
1336
1348
  var IconLayers = () => /* @__PURE__ */ jsxs(Icon, { children: [
1337
1349
  /* @__PURE__ */ jsx("path", { d: "M12 2l9 5-9 5-9-5 9-5z" }),
@@ -1550,7 +1562,46 @@ function AnnotationCard({
1550
1562
 
1551
1563
  // packages/react/src/internal/Toolbar.tsx
1552
1564
  import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1553
- var TOOLBAR_SIZE = { width: 320, height: 44 };
1565
+ var TOOLBAR_SIZE = { width: 360, height: 44 };
1566
+ var SHORTCUTS = [
1567
+ { keys: ["\u2318", "\u21E7", "F"], label: "Toggle toolbar" },
1568
+ { keys: ["1"], label: "Single select mode" },
1569
+ { keys: ["2"], label: "Multi select mode" },
1570
+ { keys: ["3"], label: "Area drag mode" },
1571
+ { keys: ["\u21B5"], label: "Annotate pinned elements" },
1572
+ { keys: ["Esc"], label: "Cancel / clear / close" },
1573
+ { keys: ["\u2318", "\u21B5"], label: "Submit annotation" },
1574
+ { keys: ["C"], label: "Copy all annotations" },
1575
+ { keys: ["X"], label: "Clear all annotations" }
1576
+ ];
1577
+ function ShortcutsPanel() {
1578
+ const [visible, setVisible] = useState3(false);
1579
+ return /* @__PURE__ */ jsxs4(
1580
+ "span",
1581
+ {
1582
+ className: "clickly-tip shortcuts-trigger",
1583
+ onMouseEnter: () => setVisible(true),
1584
+ onMouseLeave: () => setVisible(false),
1585
+ children: [
1586
+ /* @__PURE__ */ jsx4(
1587
+ "button",
1588
+ {
1589
+ className: "clickly-btn icon-only",
1590
+ "aria-label": "Show keyboard shortcuts",
1591
+ children: /* @__PURE__ */ jsx4(IconInfo, {})
1592
+ }
1593
+ ),
1594
+ visible && /* @__PURE__ */ jsxs4("div", { className: "shortcuts-panel", role: "tooltip", children: [
1595
+ /* @__PURE__ */ jsx4("div", { className: "shortcuts-title", children: "Keyboard shortcuts" }),
1596
+ SHORTCUTS.map((s) => /* @__PURE__ */ jsxs4("div", { className: "shortcuts-row", children: [
1597
+ /* @__PURE__ */ jsx4("span", { className: "shortcuts-label", children: s.label }),
1598
+ /* @__PURE__ */ jsx4("span", { className: "shortcuts-keys", children: s.keys.map((k) => /* @__PURE__ */ jsx4("kbd", { children: k }, k)) })
1599
+ ] }, s.label))
1600
+ ] })
1601
+ ]
1602
+ }
1603
+ );
1604
+ }
1554
1605
  function Tip({
1555
1606
  label,
1556
1607
  shortcut,
@@ -1571,7 +1622,38 @@ function Toolbar({ engine, onCollapse }) {
1571
1622
  const outputDetail = useSettings((s) => s.outputDetail);
1572
1623
  const [showSettings, setShowSettings] = useState3(false);
1573
1624
  const [showList, setShowList] = useState3(false);
1625
+ const [frozen, setFrozen] = useState3(false);
1574
1626
  const anchorRef = useRef4(null);
1627
+ useEffect4(() => {
1628
+ const STYLE_ID = "clickly-freeze-animations";
1629
+ const gsap = window.gsap;
1630
+ if (frozen) {
1631
+ if (!document.getElementById(STYLE_ID)) {
1632
+ const el = document.createElement("style");
1633
+ el.id = STYLE_ID;
1634
+ el.textContent = `
1635
+ *, *::before, *::after {
1636
+ animation-play-state: paused !important;
1637
+ transition-duration: 0ms !important;
1638
+ transition-delay: 0ms !important;
1639
+ }
1640
+ `;
1641
+ document.head.appendChild(el);
1642
+ }
1643
+ if (gsap?.globalTimeline) {
1644
+ gsap.globalTimeline.pause();
1645
+ }
1646
+ } else {
1647
+ document.getElementById(STYLE_ID)?.remove();
1648
+ if (gsap?.globalTimeline) {
1649
+ gsap.globalTimeline.resume();
1650
+ }
1651
+ }
1652
+ return () => {
1653
+ document.getElementById(STYLE_ID)?.remove();
1654
+ if (gsap?.globalTimeline) gsap.globalTimeline.resume();
1655
+ };
1656
+ }, [frozen]);
1575
1657
  const { position, handleProps } = useDraggable(
1576
1658
  {
1577
1659
  x: Math.max(8, window.innerWidth - TOOLBAR_SIZE.width - 16),
@@ -1654,6 +1736,16 @@ function Toolbar({ engine, onCollapse }) {
1654
1736
  ) }),
1655
1737
  /* @__PURE__ */ jsx4("div", { className: "divider" })
1656
1738
  ] }),
1739
+ /* @__PURE__ */ jsx4(Tip, { label: frozen ? "Unfreeze animations" : "Freeze animations", children: /* @__PURE__ */ jsx4(
1740
+ "button",
1741
+ {
1742
+ className: `clickly-btn icon-only${frozen ? " is-freeze" : ""}`,
1743
+ onClick: () => setFrozen((v) => !v),
1744
+ "aria-label": frozen ? "Unfreeze page animations" : "Freeze page animations",
1745
+ "aria-pressed": frozen,
1746
+ children: /* @__PURE__ */ jsx4(IconFreeze, {})
1747
+ }
1748
+ ) }),
1657
1749
  /* @__PURE__ */ jsx4(Tip, { label: "Annotations", shortcut: "L", children: /* @__PURE__ */ jsxs4(
1658
1750
  "button",
1659
1751
  {
@@ -1687,6 +1779,7 @@ function Toolbar({ engine, onCollapse }) {
1687
1779
  }
1688
1780
  ) }),
1689
1781
  /* @__PURE__ */ jsx4("div", { className: "divider" }),
1782
+ /* @__PURE__ */ jsx4(ShortcutsPanel, {}),
1690
1783
  /* @__PURE__ */ jsx4(Tip, { label: "Settings", children: /* @__PURE__ */ jsx4(
1691
1784
  "button",
1692
1785
  {
@@ -1747,7 +1840,7 @@ function CollapsedFAB({ onExpand }) {
1747
1840
  }
1748
1841
 
1749
1842
  // packages/react/src/internal/AnnotationPopup.tsx
1750
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef5, useState as useState4 } from "react";
1843
+ import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
1751
1844
  import { nanoid } from "nanoid";
1752
1845
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1753
1846
  var POPUP_W = 320;
@@ -1765,7 +1858,7 @@ function AnnotationPopup({ engine }) {
1765
1858
  const targetElRef = useRef5(null);
1766
1859
  const taRef = useRef5(null);
1767
1860
  const popupRef = useRef5(null);
1768
- useEffect4(() => {
1861
+ useEffect5(() => {
1769
1862
  if (state.kind === "annotating") {
1770
1863
  setComment("");
1771
1864
  setShowStyles(false);
@@ -1782,7 +1875,7 @@ function AnnotationPopup({ engine }) {
1782
1875
  requestAnimationFrame(() => taRef.current?.focus());
1783
1876
  }
1784
1877
  }, [state.kind, engine]);
1785
- useEffect4(() => {
1878
+ useEffect5(() => {
1786
1879
  if (state.kind !== "annotating") return;
1787
1880
  const onDown = (e) => {
1788
1881
  if (popupRef.current && e.composedPath().includes(popupRef.current)) return;
@@ -1900,10 +1993,10 @@ function AnnotationPopup({ engine }) {
1900
1993
  if (left + POPUP_W > vw) left = Math.max(8, vw - POPUP_W - 8);
1901
1994
  setPlacement({ top, left, label });
1902
1995
  }, [state, engine]);
1903
- useEffect4(() => {
1996
+ useEffect5(() => {
1904
1997
  calcPlacement();
1905
1998
  }, [calcPlacement]);
1906
- useEffect4(() => {
1999
+ useEffect5(() => {
1907
2000
  if (state.kind !== "annotating") return;
1908
2001
  const onScroll = () => calcPlacement();
1909
2002
  window.addEventListener("scroll", onScroll, { capture: true, passive: true });
@@ -2087,12 +2180,12 @@ function describeSelection(sel) {
2087
2180
  }
2088
2181
 
2089
2182
  // packages/react/src/internal/AnnotationPins.tsx
2090
- import { useEffect as useEffect5, useRef as useRef6, useState as useState5 } from "react";
2183
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState5 } from "react";
2091
2184
  import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2092
2185
  function AnnotationPins() {
2093
2186
  const annotations = useAnnotationsList();
2094
2187
  const [, setVersion] = useState5(0);
2095
- useEffect5(() => {
2188
+ useEffect6(() => {
2096
2189
  let raf = null;
2097
2190
  const update = () => {
2098
2191
  if (raf !== null) return;
@@ -2153,24 +2246,36 @@ function pinLabel(annotation) {
2153
2246
  const tag = raw.split(/[.#\s]/)[0] ?? "";
2154
2247
  return (TAG_LABELS3[tag] ?? tag) || "element";
2155
2248
  }
2249
+ function parseStyles(raw) {
2250
+ if (!raw) return [];
2251
+ return raw.split(";").map((s) => s.trim()).filter(Boolean).map((s) => {
2252
+ const colon = s.indexOf(":");
2253
+ if (colon === -1) return null;
2254
+ return [s.slice(0, colon).trim(), s.slice(colon + 1).trim()];
2255
+ }).filter(Boolean);
2256
+ }
2156
2257
  function Pin({ number, annotation }) {
2157
2258
  const remove = useAnnotations((s) => s.remove);
2158
2259
  const update = useAnnotations((s) => s.update);
2159
2260
  const [hovered, setHovered] = useState5(false);
2160
2261
  const [editing, setEditing] = useState5(false);
2161
2262
  const [draft, setDraft] = useState5(annotation.comment);
2263
+ const [showStyles, setShowStyles] = useState5(false);
2162
2264
  const taRef = useRef6(null);
2163
2265
  const editRef = useRef6(null);
2164
- const pos = resolvePosition(annotation);
2165
- if (!pos) return null;
2266
+ const styleEntries = parseStyles(annotation.computedStyles);
2166
2267
  const label = pinLabel(annotation);
2268
+ const headerLabel = annotation.isMultiSelect ? `${label} (multi-select)` : `${label}: "${annotation.elementPath}"`;
2167
2269
  const openEdit = () => {
2168
2270
  setDraft(annotation.comment);
2169
2271
  setEditing(true);
2170
2272
  setHovered(false);
2171
2273
  requestAnimationFrame(() => taRef.current?.focus());
2172
2274
  };
2173
- const closeEdit = () => setEditing(false);
2275
+ const closeEdit = () => {
2276
+ setEditing(false);
2277
+ setShowStyles(false);
2278
+ };
2174
2279
  const save = () => {
2175
2280
  const trimmed = draft.trim();
2176
2281
  if (trimmed) update(annotation.id, { comment: trimmed });
@@ -2180,7 +2285,7 @@ function Pin({ number, annotation }) {
2180
2285
  if (e.key === "Escape") closeEdit();
2181
2286
  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) save();
2182
2287
  };
2183
- useEffect5(() => {
2288
+ useEffect6(() => {
2184
2289
  if (!editing) return;
2185
2290
  const onDown = (e) => {
2186
2291
  if (editRef.current && e.composedPath().includes(editRef.current)) return;
@@ -2189,6 +2294,27 @@ function Pin({ number, annotation }) {
2189
2294
  window.addEventListener("pointerdown", onDown, true);
2190
2295
  return () => window.removeEventListener("pointerdown", onDown, true);
2191
2296
  }, [editing]);
2297
+ useEffect6(() => {
2298
+ if (!editing) return;
2299
+ let el = null;
2300
+ if (annotation.elementPath) {
2301
+ try {
2302
+ el = document.querySelector(annotation.elementPath);
2303
+ } catch {
2304
+ }
2305
+ }
2306
+ if (!el) return;
2307
+ const prevOutline = el.style.outline;
2308
+ const prevOffset = el.style.outlineOffset;
2309
+ el.style.outline = "2px solid #10b981";
2310
+ el.style.outlineOffset = "3px";
2311
+ return () => {
2312
+ el.style.outline = prevOutline;
2313
+ el.style.outlineOffset = prevOffset;
2314
+ };
2315
+ }, [editing, annotation.elementPath]);
2316
+ const pos = resolvePosition(annotation);
2317
+ if (!pos) return null;
2192
2318
  return /* @__PURE__ */ jsxs7(
2193
2319
  "div",
2194
2320
  {
@@ -2219,21 +2345,42 @@ function Pin({ number, annotation }) {
2219
2345
  className: "pin-edit",
2220
2346
  onClick: (e) => e.stopPropagation(),
2221
2347
  children: [
2222
- /* @__PURE__ */ jsx7("div", { className: "pin-edit-header", children: /* @__PURE__ */ jsxs7("span", { className: "pin-edit-label", children: [
2223
- label,
2224
- ": ",
2225
- /* @__PURE__ */ jsx7("span", { className: "pin-edit-path", children: annotation.elementPath })
2226
- ] }) }),
2348
+ /* @__PURE__ */ jsxs7(
2349
+ "div",
2350
+ {
2351
+ className: "popup-header",
2352
+ role: "button",
2353
+ "aria-expanded": showStyles,
2354
+ onClick: (e) => {
2355
+ e.stopPropagation();
2356
+ setShowStyles((v) => !v);
2357
+ },
2358
+ children: [
2359
+ /* @__PURE__ */ jsx7("span", { className: "popup-chevron", children: showStyles ? "\u25BE" : "\u203A" }),
2360
+ /* @__PURE__ */ jsx7("span", { className: "popup-label", children: headerLabel })
2361
+ ]
2362
+ }
2363
+ ),
2364
+ showStyles && styleEntries.length > 0 && /* @__PURE__ */ jsxs7("div", { className: "popup-styles", children: [
2365
+ styleEntries.map(([k, v]) => /* @__PURE__ */ jsxs7("div", { className: "style-row", children: [
2366
+ /* @__PURE__ */ jsx7("span", { className: "style-key", children: k }),
2367
+ /* @__PURE__ */ jsx7("span", { className: "style-val", children: v })
2368
+ ] }, k)),
2369
+ annotation.suggestedCss && /* @__PURE__ */ jsxs7("div", { className: "style-hint", style: { color: "#7c3aed", borderTopColor: "rgba(124,58,237,0.2)" }, children: [
2370
+ /* @__PURE__ */ jsx7("strong", { children: "Suggested changes:" }),
2371
+ /* @__PURE__ */ jsx7("br", {}),
2372
+ annotation.suggestedCss
2373
+ ] })
2374
+ ] }),
2227
2375
  /* @__PURE__ */ jsx7(
2228
2376
  "textarea",
2229
2377
  {
2230
2378
  ref: taRef,
2231
- className: "pin-edit-textarea",
2232
2379
  value: draft,
2233
2380
  onChange: (e) => setDraft(e.target.value),
2234
2381
  onKeyDown,
2235
- placeholder: "Describe the issue\u2026",
2236
- rows: 3
2382
+ placeholder: "Describe the issue\u2026 (\u2318/Ctrl + Enter to save)",
2383
+ "aria-label": "Annotation comment"
2237
2384
  }
2238
2385
  ),
2239
2386
  /* @__PURE__ */ jsxs7("div", { className: "pin-edit-actions", children: [
@@ -2246,9 +2393,9 @@ function Pin({ number, annotation }) {
2246
2393
  children: /* @__PURE__ */ jsx7(IconTrash, {})
2247
2394
  }
2248
2395
  ),
2249
- /* @__PURE__ */ jsxs7("div", { className: "pin-edit-right", children: [
2250
- /* @__PURE__ */ jsx7("button", { className: "pin-edit-cancel", onClick: closeEdit, children: "Cancel" }),
2251
- /* @__PURE__ */ jsx7("button", { className: "pin-edit-save", onClick: save, children: "Save" })
2396
+ /* @__PURE__ */ jsxs7("div", { className: "row", style: { margin: 0, flex: 1, justifyContent: "flex-end" }, children: [
2397
+ /* @__PURE__ */ jsx7("button", { className: "ghost", onClick: closeEdit, children: "Cancel" }),
2398
+ /* @__PURE__ */ jsx7("button", { className: "primary", onClick: save, children: "Save" })
2252
2399
  ] })
2253
2400
  ] })
2254
2401
  ]
@@ -2270,10 +2417,7 @@ function resolvePosition(a) {
2270
2417
  }
2271
2418
  }
2272
2419
  if (a.boundingBox) {
2273
- return {
2274
- x: a.boundingBox.x + a.boundingBox.width - 11,
2275
- y: a.boundingBox.y - 11
2276
- };
2420
+ return { x: a.boundingBox.x + a.boundingBox.width - 11, y: a.boundingBox.y - 11 };
2277
2421
  }
2278
2422
  return null;
2279
2423
  }
@@ -2289,17 +2433,17 @@ function ClicklyRoot({
2289
2433
  const outputDetail = useSettings((s) => s.outputDetail);
2290
2434
  const markerColor = useSettings((s) => s.markerColor);
2291
2435
  const engineState = useEngineState(engine);
2292
- useEffect6(() => {
2436
+ useEffect7(() => {
2293
2437
  host.style.setProperty("--clickly-hover", markerColor);
2294
2438
  }, [host, markerColor]);
2295
- useEffect6(() => {
2439
+ useEffect7(() => {
2296
2440
  if (expanded) {
2297
2441
  if (engine.getSnapshot().kind === "idle") engine.activate("single");
2298
2442
  } else {
2299
2443
  if (engine.getSnapshot().kind !== "idle") engine.deactivate();
2300
2444
  }
2301
2445
  }, [expanded, engine]);
2302
- useEffect6(() => {
2446
+ useEffect7(() => {
2303
2447
  const active = engineState.kind !== "idle";
2304
2448
  if (active) document.body.setAttribute("data-clickly-active", "");
2305
2449
  else document.body.removeAttribute("data-clickly-active");
@@ -2317,7 +2461,7 @@ function ClicklyRoot({
2317
2461
  document.body.removeAttribute("data-clickly-annotating");
2318
2462
  };
2319
2463
  }, [engineState]);
2320
- useEffect6(() => {
2464
+ useEffect7(() => {
2321
2465
  const onKey = (e) => {
2322
2466
  const tag = e.target?.tagName;
2323
2467
  if (tag === "INPUT" || tag === "TEXTAREA") return;
@@ -2497,6 +2641,14 @@ var REACT_UI_CSS = `
2497
2641
  .clickly-btn.is-active:hover { background: #0284c7; }
2498
2642
  .clickly-btn[disabled] { opacity: 0.28; cursor: not-allowed; pointer-events: none; }
2499
2643
 
2644
+ /* Freeze-animations active state \u2014 icy blue */
2645
+ .clickly-btn.is-freeze {
2646
+ background: rgba(99, 179, 237, 0.18);
2647
+ color: #63b3ed;
2648
+ box-shadow: 0 0 0 1px rgba(99,179,237,0.35), 0 2px 8px rgba(99,179,237,0.2);
2649
+ }
2650
+ .clickly-btn.is-freeze:hover { background: rgba(99, 179, 237, 0.26); }
2651
+
2500
2652
  .clickly-btn.icon-only {
2501
2653
  width: 32px;
2502
2654
  padding: 0;
@@ -2574,6 +2726,91 @@ var REACT_UI_CSS = `
2574
2726
  color: #94a3b8;
2575
2727
  }
2576
2728
 
2729
+ /* \u2500\u2500\u2500 Shortcuts panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2730
+
2731
+ .shortcuts-trigger {
2732
+ position: relative;
2733
+ display: inline-flex;
2734
+ align-items: center;
2735
+ justify-content: center;
2736
+ }
2737
+
2738
+ .shortcuts-panel {
2739
+ position: absolute;
2740
+ bottom: calc(100% + 12px);
2741
+ left: 50%;
2742
+ transform: translateX(-50%);
2743
+ width: 260px;
2744
+ background: rgba(9, 14, 28, 0.97);
2745
+ border: 1px solid rgba(255,255,255,0.08);
2746
+ border-radius: 12px;
2747
+ box-shadow: 0 16px 40px rgba(0,0,0,0.45);
2748
+ padding: 10px;
2749
+ z-index: 10;
2750
+ animation: clickly-fade-in 100ms ease-out;
2751
+ pointer-events: none;
2752
+ }
2753
+
2754
+ /* Arrow pointing down */
2755
+ .shortcuts-panel::after {
2756
+ content: "";
2757
+ position: absolute;
2758
+ top: 100%;
2759
+ left: 50%;
2760
+ transform: translateX(-50%);
2761
+ border: 6px solid transparent;
2762
+ border-top-color: rgba(9, 14, 28, 0.97);
2763
+ }
2764
+
2765
+ .shortcuts-title {
2766
+ font: 600 11px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
2767
+ color: #64748b;
2768
+ text-transform: uppercase;
2769
+ letter-spacing: 0.06em;
2770
+ padding: 2px 4px 8px;
2771
+ border-bottom: 1px solid rgba(255,255,255,0.06);
2772
+ margin-bottom: 6px;
2773
+ }
2774
+
2775
+ .shortcuts-row {
2776
+ display: flex;
2777
+ align-items: center;
2778
+ justify-content: space-between;
2779
+ padding: 4px 4px;
2780
+ border-radius: 6px;
2781
+ gap: 8px;
2782
+ }
2783
+ .shortcuts-row:hover { background: rgba(255,255,255,0.04); }
2784
+
2785
+ .shortcuts-label {
2786
+ font: 12px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
2787
+ color: #94a3b8;
2788
+ flex: 1;
2789
+ min-width: 0;
2790
+ }
2791
+
2792
+ .shortcuts-keys {
2793
+ display: flex;
2794
+ gap: 3px;
2795
+ align-items: center;
2796
+ flex-shrink: 0;
2797
+ }
2798
+
2799
+ .shortcuts-keys kbd {
2800
+ display: inline-flex;
2801
+ align-items: center;
2802
+ justify-content: center;
2803
+ min-width: 20px;
2804
+ height: 20px;
2805
+ padding: 0 5px;
2806
+ background: rgba(255,255,255,0.08);
2807
+ border: 1px solid rgba(255,255,255,0.10);
2808
+ border-bottom-width: 2px;
2809
+ border-radius: 5px;
2810
+ font: 11px/1 ui-monospace, "SF Mono", Menlo, monospace;
2811
+ color: #e2e8f0;
2812
+ }
2813
+
2577
2814
  /* \u2500\u2500\u2500 Popup & popovers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2578
2815
 
2579
2816
  .clickly-popup, .clickly-popover {
@@ -2597,8 +2834,10 @@ var REACT_UI_CSS = `
2597
2834
  flex-direction: column;
2598
2835
  }
2599
2836
 
2600
- /* Collapsible header row \u2014 shows element label + CSS toggle */
2601
- .clickly-popup .popup-header {
2837
+ /* \u2500\u2500\u2500 Shared inner styles \u2014 apply to both popup and pin-edit \u2500\u2500\u2500 */
2838
+
2839
+ .clickly-popup .popup-header,
2840
+ .pin-edit .popup-header {
2602
2841
  display: flex;
2603
2842
  align-items: center;
2604
2843
  gap: 5px;
@@ -2612,9 +2851,11 @@ var REACT_UI_CSS = `
2612
2851
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
2613
2852
  transition: background 80ms ease;
2614
2853
  }
2615
- .clickly-popup .popup-header:hover { background: #f1f5f9; }
2854
+ .clickly-popup .popup-header:hover,
2855
+ .pin-edit .popup-header:hover { background: #f1f5f9; }
2616
2856
 
2617
- .clickly-popup .popup-chevron {
2857
+ .clickly-popup .popup-chevron,
2858
+ .pin-edit .popup-chevron {
2618
2859
  font-size: 12px;
2619
2860
  color: #94a3b8;
2620
2861
  flex-shrink: 0;
@@ -2622,7 +2863,8 @@ var REACT_UI_CSS = `
2622
2863
  text-align: center;
2623
2864
  }
2624
2865
 
2625
- .clickly-popup .popup-label {
2866
+ .clickly-popup .popup-label,
2867
+ .pin-edit .popup-label {
2626
2868
  flex: 1;
2627
2869
  overflow: hidden;
2628
2870
  white-space: nowrap;
@@ -2660,7 +2902,8 @@ var REACT_UI_CSS = `
2660
2902
  }
2661
2903
 
2662
2904
  /* Computed-styles panel */
2663
- .clickly-popup .popup-styles {
2905
+ .clickly-popup .popup-styles,
2906
+ .pin-edit .popup-styles {
2664
2907
  margin-bottom: 8px;
2665
2908
  padding: 8px;
2666
2909
  background: #f8fafc;
@@ -2673,7 +2916,8 @@ var REACT_UI_CSS = `
2673
2916
  overscroll-behavior: contain;
2674
2917
  }
2675
2918
 
2676
- .clickly-popup .style-row {
2919
+ .clickly-popup .style-row,
2920
+ .pin-edit .style-row {
2677
2921
  display: flex;
2678
2922
  align-items: center;
2679
2923
  gap: 6px;
@@ -2683,17 +2927,27 @@ var REACT_UI_CSS = `
2683
2927
  transition: background 80ms ease;
2684
2928
  }
2685
2929
 
2686
- .clickly-popup .style-row--changed {
2930
+ .clickly-popup .style-row--changed,
2931
+ .pin-edit .style-row--changed {
2687
2932
  background: rgba(245, 158, 11, 0.10);
2688
2933
  }
2689
2934
 
2690
- .clickly-popup .style-key {
2935
+ .clickly-popup .style-key,
2936
+ .pin-edit .style-key {
2691
2937
  color: #7c3aed;
2692
2938
  flex-shrink: 0;
2693
2939
  min-width: 90px;
2694
2940
  font-size: 11px;
2695
2941
  }
2696
2942
 
2943
+ .clickly-popup .style-val,
2944
+ .pin-edit .style-val {
2945
+ color: #0f172a;
2946
+ font-size: 11px;
2947
+ word-break: break-all;
2948
+ flex: 1;
2949
+ }
2950
+
2697
2951
  /* Editable value input \u2014 live CSS preview */
2698
2952
  .clickly-popup .style-val-input {
2699
2953
  flex: 1;
@@ -2732,8 +2986,9 @@ var REACT_UI_CSS = `
2732
2986
  background: rgba(245,158,11,0.12);
2733
2987
  }
2734
2988
 
2735
- /* Hint text at bottom of CSS panel when edits exist */
2736
- .clickly-popup .style-hint {
2989
+ /* Hint text at bottom of CSS panel */
2990
+ .clickly-popup .style-hint,
2991
+ .pin-edit .style-hint {
2737
2992
  margin-top: 6px;
2738
2993
  padding-top: 6px;
2739
2994
  border-top: 1px solid #e2e8f0;
@@ -2743,7 +2998,8 @@ var REACT_UI_CSS = `
2743
2998
  line-height: 1.4;
2744
2999
  }
2745
3000
 
2746
- .clickly-popup textarea {
3001
+ .clickly-popup textarea,
3002
+ .pin-edit textarea {
2747
3003
  width: 100%;
2748
3004
  min-height: 64px;
2749
3005
  max-height: 160px;
@@ -2754,33 +3010,40 @@ var REACT_UI_CSS = `
2754
3010
  font: inherit;
2755
3011
  color: inherit;
2756
3012
  }
2757
- .clickly-popup textarea:focus {
3013
+ .clickly-popup textarea:focus,
3014
+ .pin-edit textarea:focus {
2758
3015
  outline: none;
2759
3016
  border-color: #0ea5e9;
2760
3017
  box-shadow: 0 0 0 3px rgba(14,165,233,0.18);
2761
3018
  }
2762
3019
 
2763
- .clickly-popup .row {
3020
+ .clickly-popup .row,
3021
+ .pin-edit .row {
2764
3022
  display: flex;
2765
3023
  justify-content: flex-end;
2766
3024
  gap: 6px;
2767
3025
  margin-top: 10px;
2768
3026
  }
2769
3027
 
2770
- .clickly-popup .row .ghost {
3028
+ .clickly-popup .row .ghost,
3029
+ .pin-edit .row .ghost {
2771
3030
  background: transparent;
2772
3031
  color: #475569;
2773
3032
  border: 1px solid #e2e8f0;
2774
3033
  }
2775
- .clickly-popup .row .ghost:hover { background: #f8fafc; }
3034
+ .clickly-popup .row .ghost:hover,
3035
+ .pin-edit .row .ghost:hover { background: #f8fafc; }
2776
3036
 
2777
- .clickly-popup .row .primary {
3037
+ .clickly-popup .row .primary,
3038
+ .pin-edit .row .primary {
2778
3039
  background: #0ea5e9;
2779
3040
  color: #fff;
2780
3041
  border: none;
2781
3042
  }
2782
- .clickly-popup .row .primary:hover { background: #0284c7; }
2783
- .clickly-popup .row button {
3043
+ .clickly-popup .row .primary:hover,
3044
+ .pin-edit .row .primary:hover { background: #0284c7; }
3045
+ .clickly-popup .row button,
3046
+ .pin-edit .row button {
2784
3047
  height: 28px;
2785
3048
  padding: 0 12px;
2786
3049
  border-radius: 6px;
@@ -3061,119 +3324,77 @@ var REACT_UI_CSS = `
3061
3324
  overflow: hidden;
3062
3325
  }
3063
3326
 
3064
- /* \u2500\u2500\u2500 Pin edit popup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
3327
+ /* \u2500\u2500\u2500 Pin edit popup \u2014 same white card as .clickly-popup \u2500\u2500\u2500\u2500\u2500\u2500 */
3065
3328
 
3066
3329
  .pin-edit {
3330
+ /* Positioning: floats to the LEFT of the pin */
3067
3331
  position: absolute;
3068
- right: calc(100% + 10px);
3332
+ right: calc(100% + 12px);
3069
3333
  top: 50%;
3070
3334
  transform: translateY(-50%);
3071
- width: 260px;
3335
+ /* Appearance: identical to .clickly-popup */
3336
+ width: 300px;
3337
+ padding: 12px;
3072
3338
  background: #fff;
3073
- border-radius: 12px;
3074
- box-shadow: 0 12px 40px rgba(2,6,23,0.20), 0 0 0 1px rgba(15,23,42,0.07);
3075
- font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
3076
3339
  color: #0f172a;
3340
+ border-radius: 10px;
3341
+ box-shadow: 0 12px 32px rgba(2,6,23,0.18), 0 0 0 1px rgba(15,23,42,0.06);
3342
+ font: 13px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
3077
3343
  text-align: left;
3078
3344
  cursor: default;
3079
3345
  z-index: 11;
3080
3346
  animation: clickly-fade-in 120ms cubic-bezier(0.16,1,0.3,1);
3347
+ /* Allow inner popup-styles to scroll */
3348
+ max-height: 80vh;
3081
3349
  overflow: hidden;
3350
+ display: flex;
3351
+ flex-direction: column;
3082
3352
  }
3083
3353
 
3084
- .pin-edit-header {
3085
- padding: 10px 12px 8px;
3086
- border-bottom: 1px solid #f1f5f9;
3087
- }
3088
-
3089
- .pin-edit-label {
3090
- font-size: 11.5px;
3091
- font-weight: 600;
3092
- color: #475569;
3093
- }
3094
-
3095
- .pin-edit-path {
3096
- font-family: ui-monospace, "SF Mono", Menlo, monospace;
3097
- font-weight: 400;
3098
- color: #94a3b8;
3099
- font-size: 10.5px;
3100
- overflow: hidden;
3101
- white-space: nowrap;
3102
- text-overflow: ellipsis;
3103
- display: inline-block;
3104
- max-width: 160px;
3105
- vertical-align: bottom;
3106
- }
3107
-
3108
- .pin-edit-textarea {
3109
- display: block;
3354
+ /* Reuse .clickly-popup textarea inside pin-edit */
3355
+ .pin-edit textarea {
3110
3356
  width: 100%;
3111
- min-height: 72px;
3112
- padding: 10px 12px;
3113
- border: none;
3114
- border-bottom: 1px solid #f1f5f9;
3357
+ min-height: 64px;
3358
+ max-height: 120px;
3359
+ padding: 8px;
3360
+ border: 1px solid #e2e8f0;
3361
+ border-radius: 6px;
3115
3362
  resize: vertical;
3116
- font: 13px/1.5 inherit;
3117
- color: #0f172a;
3363
+ font: inherit;
3364
+ color: inherit;
3118
3365
  background: #fff;
3119
3366
  box-sizing: border-box;
3120
3367
  }
3121
- .pin-edit-textarea:focus {
3368
+ .pin-edit textarea:focus {
3122
3369
  outline: none;
3123
- background: #f8fafc;
3370
+ border-color: #0ea5e9;
3371
+ box-shadow: 0 0 0 3px rgba(14,165,233,0.18);
3124
3372
  }
3125
3373
 
3374
+ /* Actions row */
3126
3375
  .pin-edit-actions {
3127
3376
  display: flex;
3128
3377
  align-items: center;
3129
- justify-content: space-between;
3130
- padding: 8px 10px;
3131
- gap: 6px;
3132
- }
3133
-
3134
- .pin-edit-right {
3135
- display: flex;
3136
3378
  gap: 6px;
3379
+ margin-top: 10px;
3137
3380
  }
3138
3381
 
3139
3382
  .pin-edit-delete {
3140
3383
  display: grid;
3141
3384
  place-items: center;
3142
- width: 30px;
3143
- height: 30px;
3385
+ width: 28px;
3386
+ height: 28px;
3387
+ flex-shrink: 0;
3144
3388
  background: transparent;
3145
3389
  border: 1px solid #fee2e2;
3146
- border-radius: 8px;
3390
+ border-radius: 6px;
3147
3391
  color: #ef4444;
3148
3392
  cursor: pointer;
3149
3393
  padding: 0;
3150
3394
  transition: background 100ms, border-color 100ms;
3151
3395
  }
3152
3396
  .pin-edit-delete:hover { background: #fef2f2; border-color: #fca5a5; }
3153
- .pin-edit-delete svg { width: 14px; height: 14px; }
3154
-
3155
- .pin-edit-cancel, .pin-edit-save {
3156
- height: 30px;
3157
- padding: 0 12px;
3158
- border-radius: 8px;
3159
- font: 12px/1 inherit;
3160
- font-weight: 500;
3161
- cursor: pointer;
3162
- border: none;
3163
- transition: background 100ms;
3164
- }
3165
-
3166
- .pin-edit-cancel {
3167
- background: #f1f5f9;
3168
- color: #475569;
3169
- }
3170
- .pin-edit-cancel:hover { background: #e2e8f0; }
3171
-
3172
- .pin-edit-save {
3173
- background: #10b981;
3174
- color: #fff;
3175
- }
3176
- .pin-edit-save:hover { background: #059669; }
3397
+ .pin-edit-delete svg { width: 13px; height: 13px; }
3177
3398
 
3178
3399
  /* \u2500\u2500\u2500 Annotation list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
3179
3400
 
@@ -3366,7 +3587,7 @@ body[data-clickly-annotating] {
3366
3587
  import { jsx as jsx9 } from "react/jsx-runtime";
3367
3588
  function Clickly({ className } = {}) {
3368
3589
  const [mount, setMount] = useState7(null);
3369
- useEffect7(() => {
3590
+ useEffect8(() => {
3370
3591
  if (typeof window === "undefined" || typeof document === "undefined") return;
3371
3592
  let shadow = null;
3372
3593
  let engine = null;
@@ -3415,7 +3636,7 @@ function Clickly({ className } = {}) {
3415
3636
  document.body.removeAttribute("data-clickly-mode");
3416
3637
  };
3417
3638
  }, []);
3418
- useEffect7(() => {
3639
+ useEffect8(() => {
3419
3640
  if (!mount || !className) return;
3420
3641
  mount.shadow.host.className = className;
3421
3642
  return () => {