@designfever/web-review-kit 0.4.0 → 0.4.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.
@@ -185,6 +185,7 @@ function ensureReviewShellStyle() {
185
185
 
186
186
  /* Semantic aliases consumed by the existing shell chrome. */
187
187
  --df-review-bg: var(--df-review-color-canvas);
188
+ --df-review-surface: var(--df-review-color-surface);
188
189
  --df-review-topbar: var(--df-review-color-surface);
189
190
  --df-review-panel: var(--df-review-color-panel);
190
191
  --df-review-panel-strong: var(--df-review-color-panel-strong);
@@ -539,10 +540,13 @@ function ensureReviewShellStyle() {
539
540
 
540
541
  .df-review-sitemap-list {
541
542
  --df-review-sitemap-grid-template: minmax(190px, 1fr) 74px 78px 64px minmax(108px, 160px);
543
+ position: relative;
542
544
  display: grid;
543
545
  align-content: start;
544
546
  min-height: 0;
545
- overflow: auto;
547
+ overflow-x: hidden;
548
+ overflow-y: auto;
549
+ overscroll-behavior: contain;
546
550
  padding: 8px;
547
551
  }
548
552
 
@@ -557,11 +561,14 @@ function ensureReviewShellStyle() {
557
561
  .df-review-sitemap-table-head {
558
562
  position: sticky;
559
563
  top: 0;
560
- z-index: 1;
564
+ z-index: 3;
561
565
  min-height: 32px;
562
566
  border-bottom: 1px solid var(--df-review-line);
563
567
  padding: 0 10px;
564
- background: var(--df-review-surface);
568
+ background: var(--df-review-panel);
569
+ box-shadow:
570
+ 0 -8px 0 0 var(--df-review-panel),
571
+ 0 1px 0 var(--df-review-line);
565
572
  color: var(--df-review-muted);
566
573
  font-size: var(--df-review-font-size-xs);
567
574
  font-weight: 720;
@@ -632,10 +639,13 @@ function ensureReviewShellStyle() {
632
639
  .df-review-sitemap-row.is-summary {
633
640
  position: sticky;
634
641
  bottom: 0;
635
- z-index: 1;
642
+ z-index: 3;
636
643
  border-top: 1px solid var(--df-review-line);
637
644
  border-bottom: 0;
638
- background: var(--df-review-surface);
645
+ background: var(--df-review-panel);
646
+ box-shadow:
647
+ 0 8px 0 0 var(--df-review-panel),
648
+ 0 -1px 0 var(--df-review-line);
639
649
  }
640
650
 
641
651
  .df-review-sitemap-row.is-folder {
@@ -2489,8 +2499,12 @@ function ensureReviewShellStyle() {
2489
2499
  .df-review-source-candidate-list {
2490
2500
  display: grid;
2491
2501
  gap: 0;
2502
+ max-height: min(220px, calc(100vh - 96px));
2492
2503
  min-height: 0;
2493
- overflow: auto;
2504
+ overflow-x: hidden;
2505
+ overflow-y: auto;
2506
+ padding-right: 2px;
2507
+ scrollbar-gutter: stable;
2494
2508
  }
2495
2509
 
2496
2510
  .df-review-source-candidate {
@@ -5655,6 +5669,97 @@ var FigmaIcon = () => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
5655
5669
  }
5656
5670
  );
5657
5671
 
5672
+ // src/react-shell/target/target.ts
5673
+ var HIDE_SCROLLBAR_STYLE_ID = "df-review-hide-scrollbar";
5674
+ var FIGMA_POINTER_LOCK_STYLE_ID = "df-review-figma-pointer-lock";
5675
+ var setTargetScrollbarHidden = (targetDocument, hidden) => {
5676
+ if (!targetDocument) return;
5677
+ const existing = targetDocument.getElementById(HIDE_SCROLLBAR_STYLE_ID);
5678
+ if (hidden) {
5679
+ if (existing) return;
5680
+ const style = targetDocument.createElement("style");
5681
+ style.id = HIDE_SCROLLBAR_STYLE_ID;
5682
+ style.textContent = "html{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}";
5683
+ targetDocument.head?.appendChild(style);
5684
+ } else {
5685
+ existing?.remove();
5686
+ }
5687
+ };
5688
+ var setTargetFigmaOverlayLocked = (targetDocument, locked) => {
5689
+ if (!targetDocument) return;
5690
+ const existing = targetDocument.getElementById(FIGMA_POINTER_LOCK_STYLE_ID);
5691
+ if (locked) {
5692
+ if (existing) return;
5693
+ const style = targetDocument.createElement("style");
5694
+ style.id = FIGMA_POINTER_LOCK_STYLE_ID;
5695
+ style.textContent = [
5696
+ ".helper-figma-root,",
5697
+ ".helper-figma-root *,",
5698
+ ".helper-figma-loading-backdrop,",
5699
+ ".helper-figma-loading-backdrop * {",
5700
+ "pointer-events: none !important;",
5701
+ "}"
5702
+ ].join("\n");
5703
+ targetDocument.head?.appendChild(style);
5704
+ } else {
5705
+ existing?.remove();
5706
+ }
5707
+ };
5708
+ var isEditableEventTarget = (event) => {
5709
+ const path = event.composedPath?.() ?? [];
5710
+ const element = path[0] ?? event.target;
5711
+ if (!element || typeof element.tagName !== "string") return false;
5712
+ const tag = element.tagName;
5713
+ return tag === "INPUT" || tag === "TEXTAREA" || element.isContentEditable === true;
5714
+ };
5715
+ var TRUE_STORAGE_VALUES = /* @__PURE__ */ new Set([
5716
+ "1",
5717
+ "true",
5718
+ "on",
5719
+ "show",
5720
+ "shown",
5721
+ "visible",
5722
+ "enabled",
5723
+ "yes"
5724
+ ]);
5725
+ var OVERLAY_STORAGE_KEYS = {
5726
+ grid: ["isHelp", "df-review-grid-overlay", "dfReviewGridOverlay"],
5727
+ figma: ["isFigmaHelp", "df-review-figma-overlay", "dfReviewFigmaOverlay"]
5728
+ };
5729
+ var isStoredOverlayEnabled = (value) => TRUE_STORAGE_VALUES.has(value?.trim().toLowerCase() ?? "");
5730
+ var getCookieValue = (targetDocument, name) => {
5731
+ const cookies = targetDocument?.cookie ? targetDocument.cookie.split(";") : [];
5732
+ const prefix = `${name}=`;
5733
+ const match = cookies.map((cookie) => cookie.trim()).find((cookie) => cookie.startsWith(prefix));
5734
+ return match ? decodeURIComponent(match.slice(prefix.length)) : null;
5735
+ };
5736
+ var getStorageValue = (storage, key) => {
5737
+ try {
5738
+ return storage?.getItem(key) ?? null;
5739
+ } catch {
5740
+ return null;
5741
+ }
5742
+ };
5743
+ var getStoredOverlayState = (targetDocument, overlay) => {
5744
+ const targetWindow = targetDocument?.defaultView;
5745
+ return OVERLAY_STORAGE_KEYS[overlay].some((key) => {
5746
+ if (isStoredOverlayEnabled(getCookieValue(targetDocument, key))) {
5747
+ return true;
5748
+ }
5749
+ return isStoredOverlayEnabled(getStorageValue(targetWindow?.localStorage, key)) || isStoredOverlayEnabled(getStorageValue(targetWindow?.sessionStorage, key));
5750
+ });
5751
+ };
5752
+ var getTargetOverlayState = (targetDocument) => ({
5753
+ grid: Boolean(
5754
+ targetDocument?.body.classList.contains("is-help") || targetDocument?.querySelector(".helper.onShow") || getStoredOverlayState(targetDocument, "grid")
5755
+ ),
5756
+ figma: Boolean(
5757
+ targetDocument?.querySelector(
5758
+ ".helper-figma-root, .helper-figma-loading-backdrop"
5759
+ ) || getStoredOverlayState(targetDocument, "figma")
5760
+ )
5761
+ });
5762
+
5658
5763
  // src/react-shell/topbar.tsx
5659
5764
  var import_jsx_runtime16 = require("react/jsx-runtime");
5660
5765
  var ReviewScopeIcon2 = ({ scope }) => {
@@ -6278,8 +6383,8 @@ function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", enviro
6278
6383
  source: getDomSourceHint(target)
6279
6384
  };
6280
6385
  }
6281
- function getElementViewportSelection(anchor, environment) {
6282
- const element = getAnchorElement(anchor, environment);
6386
+ function getElementViewportSelection(anchor, environment, preferredSelection) {
6387
+ const element = getAnchorElement(anchor, environment, preferredSelection);
6283
6388
  if (!element) return void 0;
6284
6389
  const rect = element.getBoundingClientRect();
6285
6390
  if (rect.width <= 0 || rect.height <= 0) return void 0;
@@ -6318,22 +6423,35 @@ function getAnchorCandidates(anchor) {
6318
6423
  ...anchor.candidates ?? []
6319
6424
  ]);
6320
6425
  }
6321
- function resolveAnchorElement(anchor, environment) {
6426
+ function resolveAnchorElement(anchor, environment, preferredSelection) {
6322
6427
  const matches = getAnchorCandidates(anchor).flatMap((candidate) => {
6323
- const match = queryBestAnchorCandidate(
6324
- candidate,
6325
- candidate.textFingerprint ?? anchor.textFingerprint,
6326
- environment
6327
- );
6328
- if (!match) return [];
6329
- const confidence = roundRatio(
6330
- (candidate.confidence ?? 0.5) * match.score
6331
- );
6332
- return [{
6333
- element: match.element,
6334
- candidate,
6335
- confidence
6336
- }];
6428
+ const textFingerprint = candidate.textFingerprint ?? anchor.textFingerprint;
6429
+ if (!preferredSelection) {
6430
+ const match = queryBestAnchorCandidate(
6431
+ candidate,
6432
+ textFingerprint,
6433
+ environment
6434
+ );
6435
+ if (!match) return [];
6436
+ const confidence = roundRatio(
6437
+ (candidate.confidence ?? 0.5) * match.score
6438
+ );
6439
+ return [{
6440
+ element: match.element,
6441
+ candidate,
6442
+ confidence
6443
+ }];
6444
+ }
6445
+ return queryAnchorElements(candidate.selector, environment).map((element) => {
6446
+ const confidence = roundRatio(
6447
+ (candidate.confidence ?? 0.5) * getTextFingerprintScore(textFingerprint, getTextFingerprint(element)) * getSelectionMatchScore(element, preferredSelection)
6448
+ );
6449
+ return {
6450
+ element,
6451
+ candidate,
6452
+ confidence
6453
+ };
6454
+ });
6337
6455
  });
6338
6456
  return matches.sort((a, b) => b.confidence - a.confidence)[0];
6339
6457
  }
@@ -6343,8 +6461,8 @@ function cssEscape(value) {
6343
6461
  }
6344
6462
  return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
6345
6463
  }
6346
- function getAnchorElement(anchor, environment) {
6347
- return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment)?.element;
6464
+ function getAnchorElement(anchor, environment, preferredSelection) {
6465
+ return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment, preferredSelection)?.element;
6348
6466
  }
6349
6467
  function createAnchorCandidates(target, configuredAttribute) {
6350
6468
  const targetCandidates = [];
@@ -6708,6 +6826,38 @@ function getTextFingerprintScore(expected, actual) {
6708
6826
  const matches = expectedTokens.filter((token) => actualTokens.has(token));
6709
6827
  return clamp(matches.length / expectedTokens.length, 0.25, 0.76);
6710
6828
  }
6829
+ function getSelectionMatchScore(element, selection) {
6830
+ const rect = element.getBoundingClientRect();
6831
+ if (rect.width <= 0 || rect.height <= 0) return 0.05;
6832
+ const overlapLeft = Math.max(rect.left, selection.left);
6833
+ const overlapTop = Math.max(rect.top, selection.top);
6834
+ const overlapRight = Math.min(rect.right, selection.left + selection.width);
6835
+ const overlapBottom = Math.min(rect.bottom, selection.top + selection.height);
6836
+ const overlapWidth = Math.max(0, overlapRight - overlapLeft);
6837
+ const overlapHeight = Math.max(0, overlapBottom - overlapTop);
6838
+ const overlapArea = overlapWidth * overlapHeight;
6839
+ if (overlapArea > 0) {
6840
+ const selectionArea = Math.max(1, selection.width * selection.height);
6841
+ const rectArea = Math.max(1, rect.width * rect.height);
6842
+ return 1 + overlapArea / Math.min(selectionArea, rectArea);
6843
+ }
6844
+ const rectCenterX = rect.left + rect.width / 2;
6845
+ const rectCenterY = rect.top + rect.height / 2;
6846
+ const selectionCenterX = selection.left + selection.width / 2;
6847
+ const selectionCenterY = selection.top + selection.height / 2;
6848
+ const distance = Math.hypot(
6849
+ rectCenterX - selectionCenterX,
6850
+ rectCenterY - selectionCenterY
6851
+ );
6852
+ const basis = Math.max(
6853
+ 1,
6854
+ rect.width,
6855
+ rect.height,
6856
+ selection.width,
6857
+ selection.height
6858
+ );
6859
+ return clamp(1 / (1 + distance / basis), 0.05, 0.95);
6860
+ }
6711
6861
  function getFingerprintTokens(value) {
6712
6862
  return value.toLowerCase().split(/[\s/|,.:;()[\]{}"'`~!?<>]+/).map((token) => token.trim()).filter((token) => token.length > 1);
6713
6863
  }
@@ -7838,32 +7988,6 @@ function createStyleElement() {
7838
7988
  pointer-events: none;
7839
7989
  }
7840
7990
 
7841
- .dfwr-adjust-hud {
7842
- position: fixed;
7843
- z-index: 5;
7844
- display: inline-flex;
7845
- align-items: center;
7846
- min-height: 22px;
7847
- padding: 0 8px;
7848
- border: 1px solid rgba(99, 215, 199, 0.72);
7849
- border-radius: var(--df-review-radius-sm);
7850
- background: rgba(21, 25, 29, 0.92);
7851
- box-shadow:
7852
- 0 0 0 3px rgba(99, 215, 199, 0.14),
7853
- 0 8px 18px rgba(0, 0, 0, 0.26);
7854
- color: #63d7c7;
7855
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
7856
- font-size: var(--df-review-font-size-2xs);
7857
- font-weight: 800;
7858
- line-height: 1;
7859
- pointer-events: none;
7860
- white-space: nowrap;
7861
- }
7862
-
7863
- .dfwr-adjust-hud[hidden] {
7864
- display: none;
7865
- }
7866
-
7867
7991
  .dfwr-empty,
7868
7992
  .dfwr-error {
7869
7993
  margin: 0;
@@ -8226,7 +8350,6 @@ var WebReviewKitView = class {
8226
8350
  if (event.button !== 0) return;
8227
8351
  cancel(event);
8228
8352
  });
8229
- layer.addEventListener("click", cancel);
8230
8353
  return layer;
8231
8354
  }
8232
8355
  getDraftAdjustmentMetrics(draft) {
@@ -8406,14 +8529,6 @@ var WebReviewKitView = class {
8406
8529
  if (!draft.selection) return void 0;
8407
8530
  return toViewportSelection(draft.selection.viewport);
8408
8531
  }
8409
- formatDraftAdjustmentStatus(draft) {
8410
- const metrics = this.getDraftAdjustmentMetrics(draft);
8411
- return [
8412
- `x ${this.formatSignedPx(metrics.x)}`,
8413
- `y ${this.formatSignedPx(metrics.y)}`,
8414
- `scale ${this.formatSignedPx(metrics.scale)}`
8415
- ].join(" / ");
8416
- }
8417
8532
  getDraftAdjustmentMetricLines(draft) {
8418
8533
  const metrics = this.getDraftAdjustmentMetrics(draft);
8419
8534
  return [
@@ -8425,6 +8540,7 @@ var WebReviewKitView = class {
8425
8540
  }
8426
8541
  withDraftAdjustmentComment(comment, draft) {
8427
8542
  if (!this.hasDraftAdjustment(draft)) return comment;
8543
+ const trimmedComment = comment.trim();
8428
8544
  const metrics = this.getDraftAdjustmentMetrics(draft);
8429
8545
  const adjustment = [
8430
8546
  `${this.getAdjustmentLabel()}: x ${this.formatSignedPx(
@@ -8436,12 +8552,20 @@ var WebReviewKitView = class {
8436
8552
  metrics.viewportWidth
8437
8553
  )}/design ${Math.round(metrics.designWidth)})`
8438
8554
  ].join(" ");
8439
- return `${comment.trim()}
8440
- ${adjustment}`;
8555
+ return trimmedComment ? `${trimmedComment}
8556
+ ${adjustment}` : adjustment;
8441
8557
  }
8442
8558
  getStyleableDraftElement(draft, environment) {
8559
+ if (draft.previewElement && draft.previewElement.ownerDocument === environment.document && "style" in draft.previewElement) {
8560
+ return draft.previewElement;
8561
+ }
8443
8562
  if (!draft.anchor) return void 0;
8444
- const element = resolveAnchorElement(draft.anchor, environment)?.element;
8563
+ const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
8564
+ const element = resolveAnchorElement(
8565
+ draft.anchor,
8566
+ environment,
8567
+ preferredSelection
8568
+ )?.element;
8445
8569
  if (!element) return void 0;
8446
8570
  if ("style" in element) return element;
8447
8571
  return void 0;
@@ -8461,39 +8585,77 @@ ${adjustment}`;
8461
8585
  this.restoreDraftPreview();
8462
8586
  }
8463
8587
  if (!this.draftPreview) {
8464
- const computedTransform = environment.window.getComputedStyle(element).transform;
8588
+ const computedStyle = environment.window.getComputedStyle(element);
8589
+ const clone = element.cloneNode(true);
8590
+ this.removeDuplicateIds(clone);
8591
+ this.copyComputedStyle(element, clone, environment);
8592
+ this.positionDraftPreviewClone(clone, element, computedStyle);
8593
+ environment.document.body?.appendChild(clone);
8465
8594
  this.draftPreview = {
8466
8595
  element,
8467
- transform: element.style.transform,
8468
- transformOrigin: element.style.transformOrigin,
8469
- transition: element.style.transition,
8470
- willChange: element.style.willChange,
8471
- baseTransform: element.style.transform || (computedTransform && computedTransform !== "none" ? computedTransform : "")
8596
+ clone,
8597
+ visibility: element.style.visibility
8472
8598
  };
8599
+ element.style.visibility = "hidden";
8473
8600
  }
8474
8601
  const metrics = this.getDraftAdjustmentMetrics(draft);
8475
8602
  const translate = `translate(${this.toCssNumber(metrics.cssX)}px, ${this.toCssNumber(
8476
8603
  metrics.cssY
8477
8604
  )}px)`;
8478
8605
  const scale = metrics.scaleFactor === 1 ? "" : `scale(${this.toCssNumber(metrics.scaleFactor)})`;
8479
- element.style.transition = "none";
8480
- element.style.willChange = "transform";
8481
- element.style.transformOrigin = "top left";
8482
- element.style.transform = [
8483
- this.draftPreview.baseTransform,
8484
- translate,
8485
- scale
8486
- ].filter(Boolean).join(" ");
8606
+ this.draftPreview.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
8487
8607
  }
8488
8608
  restoreDraftPreview() {
8489
8609
  if (!this.draftPreview) return;
8490
- const { element, transform, transformOrigin, transition, willChange } = this.draftPreview;
8491
- element.style.transform = transform;
8492
- element.style.transformOrigin = transformOrigin;
8493
- element.style.transition = transition;
8494
- element.style.willChange = willChange;
8610
+ const { element, clone, visibility } = this.draftPreview;
8611
+ clone.remove();
8612
+ element.style.visibility = visibility;
8495
8613
  this.draftPreview = void 0;
8496
8614
  }
8615
+ positionDraftPreviewClone(clone, element, computedStyle) {
8616
+ const rect = element.getBoundingClientRect();
8617
+ clone.setAttribute("data-dfwr-adjust-preview", "true");
8618
+ clone.setAttribute("aria-hidden", "true");
8619
+ clone.style.position = "fixed";
8620
+ clone.style.left = `${this.toCssNumber(rect.left)}px`;
8621
+ clone.style.top = `${this.toCssNumber(rect.top)}px`;
8622
+ clone.style.right = "auto";
8623
+ clone.style.bottom = "auto";
8624
+ clone.style.width = `${this.toCssNumber(rect.width)}px`;
8625
+ clone.style.height = `${this.toCssNumber(rect.height)}px`;
8626
+ clone.style.maxWidth = "none";
8627
+ clone.style.maxHeight = "none";
8628
+ clone.style.margin = "0";
8629
+ clone.style.boxSizing = "border-box";
8630
+ clone.style.display = this.getDraftPreviewDisplay(computedStyle.display);
8631
+ clone.style.zIndex = "2147483646";
8632
+ clone.style.pointerEvents = "none";
8633
+ clone.style.transition = "none";
8634
+ clone.style.willChange = "transform";
8635
+ clone.style.transformOrigin = "top left";
8636
+ clone.style.transform = "none";
8637
+ }
8638
+ getDraftPreviewDisplay(display) {
8639
+ if (display === "inline" || display === "contents") return "inline-block";
8640
+ return display || "block";
8641
+ }
8642
+ copyComputedStyle(element, clone, environment) {
8643
+ const computedStyle = environment.window.getComputedStyle(element);
8644
+ for (let index = 0; index < computedStyle.length; index += 1) {
8645
+ const property = computedStyle.item(index);
8646
+ clone.style.setProperty(
8647
+ property,
8648
+ computedStyle.getPropertyValue(property),
8649
+ computedStyle.getPropertyPriority(property)
8650
+ );
8651
+ }
8652
+ }
8653
+ removeDuplicateIds(element) {
8654
+ element.removeAttribute("id");
8655
+ element.querySelectorAll("[id]").forEach((child) => {
8656
+ child.removeAttribute("id");
8657
+ });
8658
+ }
8497
8659
  toCssNumber(value) {
8498
8660
  return Math.round(value * 1e3) / 1e3;
8499
8661
  }
@@ -8655,8 +8817,8 @@ ${adjustment}`;
8655
8817
  });
8656
8818
  const saveDraft = () => {
8657
8819
  const comment = textarea.value.trim();
8658
- if (!comment) return;
8659
8820
  const currentDraft = this.state.noteDraft ?? draft;
8821
+ if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
8660
8822
  void this.config.actions.createItem({
8661
8823
  kind: "note",
8662
8824
  comment: this.withDraftAdjustmentComment(comment, currentDraft),
@@ -8666,13 +8828,8 @@ ${adjustment}`;
8666
8828
  selection: currentDraft.selection
8667
8829
  });
8668
8830
  };
8669
- const adjustmentHud = isElementDraft ? this.createAdjustmentHud(draft, environment) : void 0;
8670
- if (adjustmentHud) {
8671
- group.append(adjustmentHud);
8672
- }
8673
8831
  const adjustmentControls = isElementDraft ? this.createAdjustmentControls({
8674
8832
  draft,
8675
- hud: adjustmentHud,
8676
8833
  pin,
8677
8834
  popover,
8678
8835
  selectionHighlight,
@@ -8800,7 +8957,6 @@ ${adjustment}`;
8800
8957
  }
8801
8958
  createAdjustmentControls({
8802
8959
  draft,
8803
- hud,
8804
8960
  pin,
8805
8961
  popover,
8806
8962
  selectionHighlight,
@@ -8838,7 +8994,6 @@ ${adjustment}`;
8838
8994
  scaleStatus.textContent = scaleLine;
8839
8995
  this.syncDraftAdjustmentUi({
8840
8996
  draft: nextDraft,
8841
- hud,
8842
8997
  pin,
8843
8998
  selectionHighlight
8844
8999
  });
@@ -8899,16 +9054,8 @@ ${adjustment}`;
8899
9054
  if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
8900
9055
  return void 0;
8901
9056
  }
8902
- createAdjustmentHud(draft, environment) {
8903
- const hud = document.createElement("div");
8904
- hud.className = "dfwr-adjust-hud";
8905
- hud.setAttribute("aria-hidden", "true");
8906
- this.syncAdjustmentHud(hud, draft, environment);
8907
- return hud;
8908
- }
8909
9057
  syncDraftAdjustmentUi({
8910
9058
  draft,
8911
- hud,
8912
9059
  pin,
8913
9060
  selectionHighlight
8914
9061
  }) {
@@ -8933,26 +9080,8 @@ ${adjustment}`;
8933
9080
  selectionHighlight.style.width = `${rect.width}px`;
8934
9081
  selectionHighlight.style.height = `${rect.height}px`;
8935
9082
  }
8936
- if (hud) {
8937
- this.syncAdjustmentHud(hud, draft, environment);
8938
- }
8939
9083
  this.syncDraftPreview(draft);
8940
9084
  }
8941
- syncAdjustmentHud(hud, draft, environment) {
8942
- if (!draft.selection) return;
8943
- const rect = toHostSelection(
8944
- this.getAdjustedDraftSelection(
8945
- toViewportSelection(draft.selection.viewport),
8946
- draft
8947
- ),
8948
- environment
8949
- );
8950
- const isVisible = draft.adjustment?.isActive === true || this.hasDraftAdjustment(draft);
8951
- hud.hidden = !isVisible;
8952
- hud.textContent = this.formatDraftAdjustmentStatus(draft);
8953
- hud.style.left = `${Math.max(4, rect.left)}px`;
8954
- hud.style.top = `${Math.max(4, rect.top - 28)}px`;
8955
- }
8956
9085
  createAreaForm() {
8957
9086
  const form = document.createElement("form");
8958
9087
  form.className = "dfwr-form";
@@ -9076,12 +9205,18 @@ ${adjustment}`;
9076
9205
  save.className = "dfwr-button is-primary";
9077
9206
  save.type = "button";
9078
9207
  save.textContent = saveLabel;
9079
- save.addEventListener("click", onSave);
9208
+ save.addEventListener("click", (event) => {
9209
+ event.preventDefault();
9210
+ event.stopPropagation();
9211
+ onSave();
9212
+ });
9080
9213
  const cancel = document.createElement("button");
9081
9214
  cancel.className = "dfwr-button";
9082
9215
  cancel.type = "button";
9083
9216
  cancel.textContent = "Cancel";
9084
- cancel.addEventListener("click", () => {
9217
+ cancel.addEventListener("click", (event) => {
9218
+ event.preventDefault();
9219
+ event.stopPropagation();
9085
9220
  this.config.actions.setModeState("idle");
9086
9221
  this.config.actions.clearDrafts();
9087
9222
  this.config.actions.render();
@@ -9468,7 +9603,7 @@ ${formatItemMeta(item)}`;
9468
9603
 
9469
9604
  // src/core/web.review.kit.app.ts
9470
9605
  var ROOT_ID = "df-web-review-kit-root";
9471
- function isEditableEventTarget(event) {
9606
+ function isEditableEventTarget2(event) {
9472
9607
  const path = event.composedPath?.() ?? [];
9473
9608
  const element = path[0] ?? event.target;
9474
9609
  if (!element || typeof element.tagName !== "string") return false;
@@ -9507,7 +9642,7 @@ var WebReviewKitApp = class {
9507
9642
  event.stopPropagation();
9508
9643
  return;
9509
9644
  }
9510
- if (isEditableEventTarget(event) || !isHotkey(event, this.hotkey)) return;
9645
+ if (isEditableEventTarget2(event) || !isHotkey(event, this.hotkey)) return;
9511
9646
  event.preventDefault();
9512
9647
  event.stopPropagation();
9513
9648
  this.toggle();
@@ -9779,13 +9914,26 @@ var WebReviewKitApp = class {
9779
9914
  const viewport = getViewportSize(environment);
9780
9915
  const nextPoint = clampPoint(point, environment);
9781
9916
  const draft = await this.withOverlayHidden(() => {
9917
+ const pointSelection = getPointSelection(nextPoint);
9918
+ const targetElement = environment.document.elementFromPoint(
9919
+ nextPoint.x,
9920
+ nextPoint.y
9921
+ );
9922
+ const previewElement = targetElement && "style" in targetElement ? targetElement : void 0;
9923
+ const targetRect = targetElement?.getBoundingClientRect();
9924
+ const clickedSelection = targetRect && targetRect.width > 0 && targetRect.height > 0 ? {
9925
+ left: targetRect.left,
9926
+ top: targetRect.top,
9927
+ width: targetRect.width,
9928
+ height: targetRect.height
9929
+ } : void 0;
9782
9930
  const anchor = getDomAnchorFromPoint(
9783
9931
  nextPoint,
9784
9932
  this.options.anchors?.attribute,
9785
9933
  environment
9786
9934
  );
9787
- const elementSelection = anchor ? getElementViewportSelection(anchor, environment) : void 0;
9788
- const selection = elementSelection ?? getPointSelection(nextPoint);
9935
+ const elementSelection = anchor ? clickedSelection ?? getElementViewportSelection(anchor, environment, pointSelection) : void 0;
9936
+ const selection = elementSelection ?? pointSelection;
9789
9937
  const markerPoint = elementSelection ? { x: selection.left, y: selection.top } : getSelectionCenter(selection);
9790
9938
  const reviewSelection = elementSelection ? {
9791
9939
  viewport: toPublicSelection(elementSelection),
@@ -9804,7 +9952,8 @@ var WebReviewKitApp = class {
9804
9952
  anchor,
9805
9953
  marker,
9806
9954
  selection: reviewSelection,
9807
- comment
9955
+ comment,
9956
+ previewElement
9808
9957
  };
9809
9958
  });
9810
9959
  this.noteDraft = draft;
@@ -9932,39 +10081,6 @@ function createNoopController() {
9932
10081
  };
9933
10082
  }
9934
10083
 
9935
- // src/react-shell/target/target.ts
9936
- var HIDE_SCROLLBAR_STYLE_ID = "df-review-hide-scrollbar";
9937
- var setTargetScrollbarHidden = (targetDocument, hidden) => {
9938
- if (!targetDocument) return;
9939
- const existing = targetDocument.getElementById(HIDE_SCROLLBAR_STYLE_ID);
9940
- if (hidden) {
9941
- if (existing) return;
9942
- const style = targetDocument.createElement("style");
9943
- style.id = HIDE_SCROLLBAR_STYLE_ID;
9944
- style.textContent = "html{scrollbar-width:none}html::-webkit-scrollbar,body::-webkit-scrollbar{width:0;height:0;display:none}";
9945
- targetDocument.head?.appendChild(style);
9946
- } else {
9947
- existing?.remove();
9948
- }
9949
- };
9950
- var isEditableEventTarget2 = (event) => {
9951
- const path = event.composedPath?.() ?? [];
9952
- const element = path[0] ?? event.target;
9953
- if (!element || typeof element.tagName !== "string") return false;
9954
- const tag = element.tagName;
9955
- return tag === "INPUT" || tag === "TEXTAREA" || element.isContentEditable === true;
9956
- };
9957
- var getTargetOverlayState = (targetDocument) => ({
9958
- grid: Boolean(
9959
- targetDocument?.body.classList.contains("is-help") || targetDocument?.querySelector(".helper.onShow")
9960
- ),
9961
- figma: Boolean(
9962
- targetDocument?.querySelector(
9963
- ".helper-figma-root, .helper-figma-loading-backdrop"
9964
- )
9965
- )
9966
- });
9967
-
9968
10084
  // src/react-shell/hooks/review.frame.navigation.ts
9969
10085
  var bindReviewFrameNavigation = ({
9970
10086
  pageTargets,
@@ -10228,17 +10344,32 @@ var useReviewKitLifecycle = ({
10228
10344
 
10229
10345
  // src/react-shell/hooks/use.review.target.overlay.ts
10230
10346
  var import_react9 = require("react");
10347
+ var TARGET_OVERLAY_REFRESH_DELAYS = [80, 240, 600];
10231
10348
  var useReviewTargetOverlay = ({
10232
10349
  iframeRef,
10233
10350
  isFigmaOverlayAvailable,
10234
10351
  targetOverlayState,
10235
10352
  onTargetOverlayStateChange
10236
10353
  }) => {
10237
- const refreshTargetOverlayState = (0, import_react9.useCallback)(() => {
10238
- onTargetOverlayStateChange(
10239
- getTargetOverlayState(iframeRef.current?.contentDocument ?? void 0)
10354
+ const refreshTimersRef = (0, import_react9.useRef)([]);
10355
+ const clearRefreshTimers = (0, import_react9.useCallback)(() => {
10356
+ refreshTimersRef.current.forEach((timer) => window.clearTimeout(timer));
10357
+ refreshTimersRef.current = [];
10358
+ }, []);
10359
+ const updateTargetOverlayState = (0, import_react9.useCallback)(() => {
10360
+ const state = getTargetOverlayState(
10361
+ iframeRef.current?.contentDocument ?? void 0
10240
10362
  );
10363
+ onTargetOverlayStateChange(state);
10364
+ return state;
10241
10365
  }, [iframeRef, onTargetOverlayStateChange]);
10366
+ const refreshTargetOverlayState = (0, import_react9.useCallback)(() => {
10367
+ clearRefreshTimers();
10368
+ updateTargetOverlayState();
10369
+ refreshTimersRef.current = TARGET_OVERLAY_REFRESH_DELAYS.map(
10370
+ (delay) => window.setTimeout(updateTargetOverlayState, delay)
10371
+ );
10372
+ }, [clearRefreshTimers, updateTargetOverlayState]);
10242
10373
  const dispatchTargetOverlayHotkey = (0, import_react9.useCallback)(
10243
10374
  (overlay) => {
10244
10375
  const targetWindow = iframeRef.current?.contentWindow;
@@ -10274,19 +10405,17 @@ var useReviewTargetOverlay = ({
10274
10405
  );
10275
10406
  const closeTargetOverlay = (0, import_react9.useCallback)(
10276
10407
  (overlay) => {
10277
- const currentState = getTargetOverlayState(
10278
- iframeRef.current?.contentDocument ?? void 0
10279
- );
10280
- onTargetOverlayStateChange(currentState);
10408
+ const currentState = updateTargetOverlayState();
10281
10409
  if (!currentState[overlay]) return false;
10282
10410
  return dispatchTargetOverlayHotkey(overlay);
10283
10411
  },
10284
- [dispatchTargetOverlayHotkey, iframeRef, onTargetOverlayStateChange]
10412
+ [dispatchTargetOverlayHotkey, updateTargetOverlayState]
10285
10413
  );
10286
10414
  (0, import_react9.useEffect)(() => {
10287
10415
  if (isFigmaOverlayAvailable || !targetOverlayState.figma) return;
10288
10416
  closeTargetOverlay("figma");
10289
10417
  }, [closeTargetOverlay, isFigmaOverlayAvailable, targetOverlayState.figma]);
10418
+ (0, import_react9.useEffect)(() => clearRefreshTimers, [clearRefreshTimers]);
10290
10419
  return {
10291
10420
  closeTargetOverlay,
10292
10421
  refreshTargetOverlayState,
@@ -11544,7 +11673,7 @@ var useReviewShellHotkeys = ({
11544
11673
  if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) {
11545
11674
  return;
11546
11675
  }
11547
- if (isEditableEventTarget2(event)) return;
11676
+ if (isEditableEventTarget(event)) return;
11548
11677
  const actions = {
11549
11678
  r: () => {
11550
11679
  if (isRulerAvailable) onToggleRuler();
@@ -12304,7 +12433,6 @@ var ReviewShell = ({
12304
12433
  });
12305
12434
  const {
12306
12435
  clearSelectedItem,
12307
- closeTargetOverlay,
12308
12436
  initReviewKit,
12309
12437
  reloadReviewKit,
12310
12438
  restoreReviewItem,
@@ -12442,9 +12570,6 @@ var ReviewShell = ({
12442
12570
  const writeMode = getReviewModeWriteMode(nextMode);
12443
12571
  if (writeMode && !activeAdapterEntry.writeModes.includes(writeMode)) return;
12444
12572
  closeRuler();
12445
- if (nextMode === "element") {
12446
- closeTargetOverlay("figma");
12447
- }
12448
12573
  setControllerReviewMode(nextMode);
12449
12574
  };
12450
12575
  useReviewShellHotkeys({
@@ -12481,6 +12606,13 @@ var ReviewShell = ({
12481
12606
  );
12482
12607
  setTargetFigmaState(config ? { targetSrc, config } : null);
12483
12608
  }, [iframeRef, targetSrc]);
12609
+ (0, import_react19.useEffect)(() => {
12610
+ const targetDocument = iframeRef.current?.contentDocument;
12611
+ setTargetFigmaOverlayLocked(targetDocument, mode === "element");
12612
+ return () => {
12613
+ setTargetFigmaOverlayLocked(targetDocument, false);
12614
+ };
12615
+ }, [iframeRef, mode, targetSrc]);
12484
12616
  const clearSourceInspector = (0, import_react19.useCallback)(() => {
12485
12617
  sourceInspectorInteractionRef.current = false;
12486
12618
  setSourceInspectorState(null);
@@ -12627,6 +12759,13 @@ var ReviewShell = ({
12627
12759
  cursor: crosshair !important;
12628
12760
  }
12629
12761
 
12762
+ html[${optionAttribute}="true"] .helper-figma-root,
12763
+ html[${optionAttribute}="true"] .helper-figma-root *,
12764
+ html[${optionAttribute}="true"] .helper-figma-loading-backdrop,
12765
+ html[${optionAttribute}="true"] .helper-figma-loading-backdrop * {
12766
+ pointer-events: none !important;
12767
+ }
12768
+
12630
12769
  html[${optionAttribute}="true"] body::before {
12631
12770
  position: fixed !important;
12632
12771
  z-index: 2147483647 !important;
@@ -12853,8 +12992,18 @@ var ReviewShell = ({
12853
12992
  const loadTargetFrame = (0, import_react19.useCallback)(() => {
12854
12993
  initReviewKit();
12855
12994
  refreshTargetFigmaConfig();
12995
+ setTargetFigmaOverlayLocked(
12996
+ iframeRef.current?.contentDocument,
12997
+ mode === "element"
12998
+ );
12856
12999
  bindSourceOpenShortcut();
12857
- }, [bindSourceOpenShortcut, initReviewKit, refreshTargetFigmaConfig]);
13000
+ }, [
13001
+ bindSourceOpenShortcut,
13002
+ iframeRef,
13003
+ initReviewKit,
13004
+ mode,
13005
+ refreshTargetFigmaConfig
13006
+ ]);
12858
13007
  (0, import_react19.useEffect)(() => {
12859
13008
  const frame = window.requestAnimationFrame(bindSourceOpenShortcut);
12860
13009
  return () => window.cancelAnimationFrame(frame);