@designfever/web-review-kit 0.8.1 → 0.8.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.cjs CHANGED
@@ -248,7 +248,7 @@ function normalizeItemForSupabaseCreate(item, source, options) {
248
248
  const normalizedStatus = normalizeReviewItemStatus(item.status);
249
249
  const routeKey = item.routeKey || item.normalizedPath || "/";
250
250
  const viewport = item.viewport ?? { width: 390, height: 720 };
251
- const nextItem = {
251
+ return {
252
252
  ...item,
253
253
  id,
254
254
  reviewNumber: void 0,
@@ -269,10 +269,6 @@ function normalizeItemForSupabaseCreate(item, source, options) {
269
269
  createdAt: now,
270
270
  updatedAt: now
271
271
  };
272
- return {
273
- ...nextItem,
274
- externalIssueUrl: nextItem.externalIssueUrl ?? buildSupabaseReviewUrl(nextItem, source, options)
275
- };
276
272
  }
277
273
  async function createItemWithRpc(item, source, options) {
278
274
  const rpcName = options.createRpc ?? DEFAULT_SUPABASE_CREATE_REVIEW_ITEM_RPC;
@@ -625,38 +621,69 @@ async function createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOpt
625
621
  const requestFetch = fetchOption ?? globalThis.fetch;
626
622
  if (!requestFetch) throw new Error("Figma client rendering requires fetch.");
627
623
  const renderFormat = options.renderFormat ?? "png";
628
- const response = await requestFetch(
629
- createReviewFigmaImageApiUrl({
630
- apiBaseUrl: options.apiBaseUrl,
631
- fileKey: ref.fileKey,
632
- nodeId: ref.nodeId,
633
- format: renderFormat,
634
- scale: options.renderScale,
635
- useAbsoluteBounds: options.useAbsoluteBounds
636
- }),
637
- {
638
- headers: {
639
- "X-Figma-Token": token
624
+ const timeoutMs = options.timeoutMs ?? 1e4;
625
+ const imageUrl = await withStageTimeout(
626
+ async (signal) => {
627
+ const response = await requestFetch(
628
+ createReviewFigmaImageApiUrl({
629
+ apiBaseUrl: options.apiBaseUrl,
630
+ fileKey: ref.fileKey,
631
+ nodeId: ref.nodeId,
632
+ format: renderFormat,
633
+ scale: options.renderScale,
634
+ useAbsoluteBounds: options.useAbsoluteBounds
635
+ }),
636
+ {
637
+ headers: {
638
+ "X-Figma-Token": token
639
+ },
640
+ signal
641
+ }
642
+ );
643
+ const body = await response.json().catch(() => null);
644
+ if (!response.ok) {
645
+ throw new Error(
646
+ body?.err || `Figma image render failed with ${response.status}`
647
+ );
640
648
  }
641
- }
649
+ const nextImageUrl = body?.images?.[ref.nodeId];
650
+ if (!nextImageUrl) {
651
+ throw new Error(
652
+ `Figma image render returned no URL for ${ref.nodeId}.`
653
+ );
654
+ }
655
+ return nextImageUrl;
656
+ },
657
+ timeoutMs,
658
+ "Figma API request timed out."
642
659
  );
643
- const body = await response.json().catch(() => null);
644
- if (!response.ok) {
645
- throw new Error(body?.err || `Figma image render failed with ${response.status}`);
646
- }
647
- const imageUrl = body?.images?.[ref.nodeId];
648
- if (!imageUrl) throw new Error(`Figma image render returned no URL for ${ref.nodeId}.`);
649
- const imageResponse = await requestFetch(imageUrl);
650
- if (!imageResponse.ok) {
651
- throw new Error(`Figma image download failed with ${imageResponse.status}`);
652
- }
653
- const originalBlob = await imageResponse.blob();
660
+ const originalBlob = await withStageTimeout(
661
+ async (signal) => {
662
+ const imageResponse = await requestFetch(imageUrl, { signal });
663
+ if (!imageResponse.ok) {
664
+ throw new Error(
665
+ `Figma image download failed with ${imageResponse.status}`
666
+ );
667
+ }
668
+ return imageResponse.blob();
669
+ },
670
+ timeoutMs,
671
+ "Figma image download timed out."
672
+ );
673
+ return withStageTimeout(
674
+ () => createProcessedFigmaAsset(originalBlob, renderFormat, options),
675
+ timeoutMs,
676
+ "Figma image processing timed out."
677
+ );
678
+ }
679
+ async function createProcessedFigmaAsset(originalBlob, renderFormat, options) {
654
680
  const originalMimeType = normalizeClientImageMimeType(originalBlob.type) ?? getReviewFigmaImageMimeType(renderFormat === "jpg" ? "jpg" : "png");
655
681
  const originalFormat = getReviewFigmaImageFormatFromMimeType(originalMimeType) ?? (renderFormat === "jpg" ? "jpg" : "png");
656
- const dimensions = await readImageBlobDimensions(originalBlob);
682
+ const image = await loadImageBlob(originalBlob);
683
+ const dimensions = readImageDimensions(image);
657
684
  const shouldConvertToWebp = options.convertToWebp ?? true;
658
- const convertedBlob = shouldConvertToWebp ? await convertImageBlobToWebp(
659
- originalBlob,
685
+ const convertedBlob = shouldConvertToWebp ? await convertImageToWebp(
686
+ image,
660
687
  options.webpQuality ?? 0.9,
661
688
  dimensions
662
689
  ).catch(() => null) : null;
@@ -678,22 +705,17 @@ function createReviewFigmaClientRenderedAsset({
678
705
  token,
679
706
  ...options
680
707
  }) {
681
- return withTimeout(
682
- createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),
683
- options.timeoutMs ?? 1e4
684
- );
708
+ return createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption);
685
709
  }
686
- async function readImageBlobDimensions(blob) {
687
- const image = await loadImageBlob(blob);
710
+ function readImageDimensions(image) {
688
711
  return {
689
712
  width: image.naturalWidth || image.width,
690
713
  height: image.naturalHeight || image.height
691
714
  };
692
715
  }
693
- async function convertImageBlobToWebp(blob, quality, dimensions) {
716
+ async function convertImageToWebp(image, quality, dimensions) {
694
717
  if (typeof document === "undefined") return null;
695
718
  if (!dimensions.width || !dimensions.height) return null;
696
- const image = await loadImageBlob(blob);
697
719
  const canvas = document.createElement("canvas");
698
720
  canvas.width = dimensions.width;
699
721
  canvas.height = dimensions.height;
@@ -745,18 +767,26 @@ function normalizeClientImageMimeType(value) {
745
767
  }
746
768
  return null;
747
769
  }
748
- async function withTimeout(promise, timeoutMs) {
770
+ async function withStageTimeout(run, timeoutMs, message) {
771
+ const controller = new AbortController();
772
+ let didTimeout = false;
749
773
  let timeoutId;
750
774
  try {
751
- return await Promise.race([
752
- promise,
753
- new Promise((_, reject) => {
754
- timeoutId = setTimeout(
755
- () => reject(new Error("Figma client rendering timed out.")),
756
- timeoutMs
757
- );
758
- })
759
- ]);
775
+ try {
776
+ return await Promise.race([
777
+ run(controller.signal),
778
+ new Promise((_, reject) => {
779
+ timeoutId = setTimeout(() => {
780
+ didTimeout = true;
781
+ controller.abort();
782
+ reject(new Error(message));
783
+ }, timeoutMs);
784
+ })
785
+ ]);
786
+ } catch (error) {
787
+ if (didTimeout) throw new Error(message);
788
+ throw error;
789
+ }
760
790
  } finally {
761
791
  if (timeoutId) clearTimeout(timeoutId);
762
792
  }
@@ -962,7 +992,7 @@ async function uploadFigmaAsset({
962
992
  const requestFetch = fetchOption ?? globalThis.fetch;
963
993
  if (!requestFetch) throw new Error("Figma asset upload requires fetch.");
964
994
  const { blob, mimeType } = decodeAssetDataUrl(asset);
965
- const response = await withTimeout2(
995
+ const response = await withTimeout(
966
996
  requestFetch(
967
997
  createAssetUploadUrl(endpoint, {
968
998
  imageFormat: asset.imageFormat,
@@ -1091,7 +1121,7 @@ function decodeAssetDataUrl(asset) {
1091
1121
  mimeType
1092
1122
  };
1093
1123
  }
1094
- async function withTimeout2(promise, timeoutMs, message) {
1124
+ async function withTimeout(promise, timeoutMs, message) {
1095
1125
  let timeoutId;
1096
1126
  try {
1097
1127
  return await Promise.race([
@@ -1945,6 +1975,15 @@ function isMeaningfulClass(value) {
1945
1975
  ) && !normalized.startsWith("mq-");
1946
1976
  }
1947
1977
 
1978
+ // src/core/error.ts
1979
+ function getErrorMessage(error, fallback) {
1980
+ if (error instanceof Error) return error.message;
1981
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
1982
+ return error.message;
1983
+ }
1984
+ return fallback;
1985
+ }
1986
+
1948
1987
  // src/core/id.ts
1949
1988
  function createId() {
1950
1989
  if ("randomUUID" in crypto) return crypto.randomUUID();
@@ -1980,6 +2019,13 @@ var HOTKEY_KEY_ALIASES = {
1980
2019
  n: ["\u315C"],
1981
2020
  m: ["\u3161"]
1982
2021
  };
2022
+ function isEditableEventTarget(event) {
2023
+ const path = event.composedPath?.() ?? [];
2024
+ const element = path[0] ?? event.target;
2025
+ if (!element || typeof element.tagName !== "string") return false;
2026
+ const tag = element.tagName;
2027
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || element.isContentEditable === true;
2028
+ }
1983
2029
  function isHotkey(event, hotkey) {
1984
2030
  const parts = hotkey.split("+").map((part) => part.trim().toLowerCase()).filter(Boolean);
1985
2031
  const key = parts.find(
@@ -2426,6 +2472,7 @@ var DraftPreviewController = class {
2426
2472
  };
2427
2473
  element.style.visibility = "hidden";
2428
2474
  }
2475
+ syncDraftPreviewCloneRect(this.snapshot.clone, element);
2429
2476
  const metrics = this.config.getMetrics(draft);
2430
2477
  const translate = `translate(${toCssNumber(metrics.cssX)}px, ${toCssNumber(
2431
2478
  metrics.cssY
@@ -2450,16 +2497,11 @@ var DraftPreviewController = class {
2450
2497
  }
2451
2498
  };
2452
2499
  function positionDraftPreviewClone(clone, element, computedStyle) {
2453
- const rect = element.getBoundingClientRect();
2454
2500
  clone.setAttribute("data-dfwr-adjust-preview", "true");
2455
2501
  clone.setAttribute("aria-hidden", "true");
2456
2502
  clone.style.position = "fixed";
2457
- clone.style.left = `${toCssNumber(rect.left)}px`;
2458
- clone.style.top = `${toCssNumber(rect.top)}px`;
2459
2503
  clone.style.right = "auto";
2460
2504
  clone.style.bottom = "auto";
2461
- clone.style.width = `${toCssNumber(rect.width)}px`;
2462
- clone.style.height = `${toCssNumber(rect.height)}px`;
2463
2505
  clone.style.maxWidth = "none";
2464
2506
  clone.style.maxHeight = "none";
2465
2507
  clone.style.margin = "0";
@@ -2471,6 +2513,14 @@ function positionDraftPreviewClone(clone, element, computedStyle) {
2471
2513
  clone.style.willChange = "transform";
2472
2514
  clone.style.transformOrigin = "top left";
2473
2515
  clone.style.transform = "none";
2516
+ syncDraftPreviewCloneRect(clone, element);
2517
+ }
2518
+ function syncDraftPreviewCloneRect(clone, element) {
2519
+ const rect = element.getBoundingClientRect();
2520
+ clone.style.left = `${toCssNumber(rect.left)}px`;
2521
+ clone.style.top = `${toCssNumber(rect.top)}px`;
2522
+ clone.style.width = `${toCssNumber(rect.width)}px`;
2523
+ clone.style.height = `${toCssNumber(rect.height)}px`;
2474
2524
  }
2475
2525
  function getDraftPreviewDisplay(display) {
2476
2526
  if (display === "inline" || display === "contents") return "inline-block";
@@ -2546,11 +2596,11 @@ function createStyleElement() {
2546
2596
  --df-review-color-text: #f7f7f2;
2547
2597
  --df-review-color-text-muted: rgba(247, 247, 242, 0.62);
2548
2598
  --df-review-color-text-subtle: rgba(247, 247, 242, 0.46);
2549
- --df-review-color-accent: #d7ff5f;
2599
+ --df-review-color-accent: #7cc7ff;
2550
2600
  --df-review-color-accent-contrast: #171b1e;
2551
- --df-review-color-accent-soft: rgba(215, 255, 95, 0.16);
2552
- --df-review-color-accent-ring: rgba(215, 255, 95, 0.6);
2553
- --df-review-color-area: #63d7c7;
2601
+ --df-review-color-accent-soft: rgba(124, 199, 255, 0.16);
2602
+ --df-review-color-accent-ring: rgba(124, 199, 255, 0.6);
2603
+ --df-review-color-area: #7cc7ff;
2554
2604
  --df-review-color-error: #ffb7a7;
2555
2605
  --df-review-shadow-panel: 0 18px 48px rgba(0, 0, 0, 0.34);
2556
2606
  --df-review-shadow-popover: 0 16px 38px rgba(0, 0, 0, 0.32);
@@ -2736,9 +2786,9 @@ function createStyleElement() {
2736
2786
  .dfwr-selection-highlight {
2737
2787
  position: fixed;
2738
2788
  z-index: 1;
2739
- border: 2px solid #d7ff5f;
2789
+ border: 2px solid #7cc7ff;
2740
2790
  border-radius: var(--df-review-radius-xs);
2741
- background: rgba(215, 255, 95, 0.08);
2791
+ background: rgba(124, 199, 255, 0.08);
2742
2792
  box-shadow:
2743
2793
  0 0 0 1px rgba(31, 36, 40, 0.72),
2744
2794
  0 0 0 9999px rgba(0, 0, 0, 0.12),
@@ -2747,8 +2797,8 @@ function createStyleElement() {
2747
2797
  }
2748
2798
 
2749
2799
  .dfwr-selection-highlight.is-draft {
2750
- border-color: #63d7c7;
2751
- background: rgba(99, 215, 199, 0.1);
2800
+ border-color: #7cc7ff;
2801
+ background: rgba(124, 199, 255, 0.1);
2752
2802
  box-shadow:
2753
2803
  0 0 0 1px rgba(31, 36, 40, 0.72),
2754
2804
  0 0 0 9999px rgba(0, 0, 0, 0.08),
@@ -2759,9 +2809,9 @@ function createStyleElement() {
2759
2809
  .dfwr-dom-hover {
2760
2810
  position: fixed;
2761
2811
  z-index: 2;
2762
- border: 1px solid #d7ff5f;
2812
+ border: 1px solid #7cc7ff;
2763
2813
  border-radius: var(--df-review-radius-xs);
2764
- background: rgba(215, 255, 95, 0.1);
2814
+ background: rgba(124, 199, 255, 0.1);
2765
2815
  box-shadow:
2766
2816
  0 0 0 1px rgba(31, 36, 40, 0.72),
2767
2817
  0 0 0 9999px rgba(0, 0, 0, 0.08);
@@ -2942,12 +2992,12 @@ function createStyleElement() {
2942
2992
  }
2943
2993
 
2944
2994
  .dfwr-area-preview-layer .dfwr-bound-marker {
2945
- border-color: #63d7c7;
2995
+ border-color: #7cc7ff;
2946
2996
  background: var(--df-review-color-panel);
2947
2997
  box-shadow:
2948
- 0 0 0 5px rgba(99, 215, 199, 0.2),
2998
+ 0 0 0 5px rgba(124, 199, 255, 0.2),
2949
2999
  0 12px 26px rgba(0, 0, 0, 0.3);
2950
- color: #63d7c7;
3000
+ color: #7cc7ff;
2951
3001
  }
2952
3002
 
2953
3003
  .dfwr-bound-marker-icon {
@@ -3030,7 +3080,7 @@ function createStyleElement() {
3030
3080
  border-radius: var(--df-review-radius-pill);
3031
3081
  background: var(--df-review-color-accent);
3032
3082
  box-shadow:
3033
- 0 0 0 4px rgba(215, 255, 95, 0.22),
3083
+ 0 0 0 4px rgba(124, 199, 255, 0.22),
3034
3084
  0 8px 18px rgba(0, 0, 0, 0.28);
3035
3085
  cursor: grab;
3036
3086
  pointer-events: auto;
@@ -3048,7 +3098,7 @@ function createStyleElement() {
3048
3098
  pointer-events: auto;
3049
3099
  color: var(--df-review-color-text);
3050
3100
  background: var(--df-review-color-panel);
3051
- border: 1px solid rgba(215, 255, 95, 0.56);
3101
+ border: 1px solid rgba(124, 199, 255, 0.56);
3052
3102
  border-radius: var(--df-review-radius-md);
3053
3103
  box-shadow: var(--df-review-shadow-popover);
3054
3104
  }
@@ -3057,7 +3107,7 @@ function createStyleElement() {
3057
3107
  .dfwr-area-draft.is-composer {
3058
3108
  max-height: min(360px, calc(100vh - 32px));
3059
3109
  overflow: auto;
3060
- border-color: rgba(99, 215, 199, 0.56);
3110
+ border-color: rgba(124, 199, 255, 0.56);
3061
3111
  }
3062
3112
 
3063
3113
  .dfwr-shell.is-docked-composer .dfwr-dom-popover.is-docked-composer,
@@ -3094,7 +3144,7 @@ function createStyleElement() {
3094
3144
 
3095
3145
  .dfwr-draft-drag-handle:hover,
3096
3146
  .dfwr-draft-drag-handle:focus-visible {
3097
- background: rgba(215, 255, 95, 0.62);
3147
+ background: rgba(124, 199, 255, 0.62);
3098
3148
  }
3099
3149
 
3100
3150
  .dfwr-draft-drag-handle:active {
@@ -3113,7 +3163,7 @@ function createStyleElement() {
3113
3163
  pointer-events: auto;
3114
3164
  color: var(--df-review-color-text);
3115
3165
  background: var(--df-review-color-panel);
3116
- border: 1px solid rgba(215, 255, 95, 0.56);
3166
+ border: 1px solid rgba(124, 199, 255, 0.56);
3117
3167
  border-radius: var(--df-review-radius-md);
3118
3168
  box-shadow: var(--df-review-shadow-popover);
3119
3169
  }
@@ -3332,7 +3382,7 @@ function createStyleElement() {
3332
3382
  }
3333
3383
 
3334
3384
  .dfwr-adjust-panel.is-active {
3335
- border-color: rgba(215, 255, 95, 0.5);
3385
+ border-color: rgba(124, 199, 255, 0.5);
3336
3386
  background: var(--df-review-color-accent-soft);
3337
3387
  }
3338
3388
 
@@ -3371,7 +3421,7 @@ function createStyleElement() {
3371
3421
  .dfwr-adjust-toggle:hover,
3372
3422
  .dfwr-adjust-toggle:focus-visible,
3373
3423
  .dfwr-adjust-toggle.is-active {
3374
- border-color: rgba(215, 255, 95, 0.68);
3424
+ border-color: rgba(124, 199, 255, 0.68);
3375
3425
  background: var(--df-review-color-accent-soft);
3376
3426
  outline: none;
3377
3427
  }
@@ -3434,7 +3484,7 @@ function createStyleElement() {
3434
3484
  }
3435
3485
 
3436
3486
  .dfwr-item:focus-visible {
3437
- outline: 2px solid rgba(215, 255, 95, 0.72);
3487
+ outline: 2px solid rgba(124, 199, 255, 0.72);
3438
3488
  outline-offset: 4px;
3439
3489
  }
3440
3490
 
@@ -3544,8 +3594,8 @@ function createStyleElement() {
3544
3594
  z-index: 2;
3545
3595
  width: 0;
3546
3596
  height: 0;
3547
- border: 1px solid #d7ff5f;
3548
- background: rgba(215, 255, 95, 0.16);
3597
+ border: 1px solid #7cc7ff;
3598
+ background: rgba(124, 199, 255, 0.16);
3549
3599
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.18);
3550
3600
  }
3551
3601
 
@@ -4622,6 +4672,12 @@ function createDomDraftLayer(context, draft, options = {}) {
4622
4672
  pin.setAttribute("aria-label", "Move DOM point");
4623
4673
  pin.style.left = `${hostPoint.x}px`;
4624
4674
  pin.style.top = `${hostPoint.y}px`;
4675
+ if (draft.isSelectionOnly) {
4676
+ pin.classList.add("is-selection-only");
4677
+ pin.tabIndex = -1;
4678
+ group.append(pin);
4679
+ return { layer: group, composer: void 0 };
4680
+ }
4625
4681
  const popover = document.createElement("div");
4626
4682
  const position = getPopoverPosition(hostPoint, environment);
4627
4683
  popover.className = [
@@ -4744,7 +4800,7 @@ function createDomDraftLayer(context, draft, options = {}) {
4744
4800
  attachments: currentDraft.attachments
4745
4801
  });
4746
4802
  };
4747
- const adjustmentControls = isElementDraft ? createAdjustmentControls(context, {
4803
+ const adjustmentControls = isElementDraft && !options.dockComposer ? createAdjustmentControls(context, {
4748
4804
  draft,
4749
4805
  pin,
4750
4806
  popover,
@@ -5390,7 +5446,7 @@ var WebReviewKitView = class {
5390
5446
  this.draftPreview = new DraftPreviewController({
5391
5447
  getEnvironment: () => this.config.getEnvironment(),
5392
5448
  getMetrics: (draft) => getDraftAdjustmentMetrics(draft, presets()),
5393
- hasAdjustment: (draft) => hasDraftAdjustment(draft, presets())
5449
+ hasAdjustment: (draft) => draft.adjustment?.preview !== false && hasDraftAdjustment(draft, presets())
5394
5450
  });
5395
5451
  this.draftContext = {
5396
5452
  config: this.config,
@@ -5411,8 +5467,11 @@ var WebReviewKitView = class {
5411
5467
  shadow.replaceChildren();
5412
5468
  shadow.append(createStyleElement());
5413
5469
  shadow.append(hiddenItemsStyle);
5414
- const hasDismissableDraft = Boolean(state.domDraft || state.areaDraft);
5415
- const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
5470
+ const hasSelectionOnlyDraft = state.domDraft?.isSelectionOnly === true;
5471
+ const hasDismissableDraft = Boolean(
5472
+ state.domDraft && !hasSelectionOnlyDraft || state.areaDraft
5473
+ );
5474
+ const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && !hasSelectionOnlyDraft && Boolean(this.getShellComposerHost());
5416
5475
  let dockedComposer;
5417
5476
  const shell = document.createElement("div");
5418
5477
  shell.className = [
@@ -5534,13 +5593,7 @@ var WebReviewKitView = class {
5534
5593
 
5535
5594
  // src/core/web.review.kit.app.ts
5536
5595
  var ROOT_ID = "df-web-review-kit-root";
5537
- function isEditableEventTarget(event) {
5538
- const path = event.composedPath?.() ?? [];
5539
- const element = path[0] ?? event.target;
5540
- if (!element || typeof element.tagName !== "string") return false;
5541
- const tag = element.tagName;
5542
- return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || element.isContentEditable === true;
5543
- }
5596
+ var VIEWPORT_SCROLL_OPTIONS = { capture: true, passive: true };
5544
5597
  function createWebReviewKit(options) {
5545
5598
  if (typeof window === "undefined" || typeof document === "undefined") {
5546
5599
  return createNoopController();
@@ -5553,6 +5606,8 @@ function createWebReviewKit(options) {
5553
5606
  toggle: () => app.toggle(),
5554
5607
  setMode: (mode) => app.setMode(mode),
5555
5608
  startElementReview: (element, comment) => app.startElementReview(element, comment),
5609
+ selectElement: (element) => app.selectElement(element),
5610
+ adjustElementSelection: (delta, adjustmentOptions) => app.adjustElementSelection(delta, adjustmentOptions),
5556
5611
  getMode: () => app.getMode(),
5557
5612
  highlightItem: (itemId) => app.highlightItem(itemId),
5558
5613
  setHiddenItemIds: (itemIds) => app.setHiddenItemIds(itemIds),
@@ -5573,6 +5628,12 @@ var WebReviewKitApp = class {
5573
5628
  this.isSelectingArea = false;
5574
5629
  this.handleKeyDown = (event) => {
5575
5630
  if (event.key === "Escape" && this.cancelMode()) {
5631
+ const activeElement = document.activeElement;
5632
+ if (activeElement instanceof HTMLButtonElement && activeElement.matches(
5633
+ ".df-review-mode-button, .df-review-section-outline-link.is-dom-select"
5634
+ )) {
5635
+ activeElement.blur();
5636
+ }
5576
5637
  event.preventDefault();
5577
5638
  event.stopPropagation();
5578
5639
  return;
@@ -5587,6 +5648,7 @@ var WebReviewKitApp = class {
5587
5648
  this.renderFrame = window.requestAnimationFrame(() => {
5588
5649
  this.renderFrame = void 0;
5589
5650
  if (this.isDraftComposerFocused()) return;
5651
+ this.syncDomDraftViewportGeometry();
5590
5652
  this.render();
5591
5653
  });
5592
5654
  };
@@ -5644,16 +5706,48 @@ var WebReviewKitApp = class {
5644
5706
  this.shadow = this.root.attachShadow({ mode: "open" });
5645
5707
  document.body.appendChild(this.root);
5646
5708
  document.addEventListener("keydown", this.handleKeyDown, true);
5647
- window.addEventListener("scroll", this.handleViewportChange, true);
5709
+ window.addEventListener(
5710
+ "scroll",
5711
+ this.handleViewportChange,
5712
+ VIEWPORT_SCROLL_OPTIONS
5713
+ );
5648
5714
  window.addEventListener("resize", this.handleViewportChange);
5715
+ this.targetViewportWindow = this.getEnvironment()?.window;
5716
+ if (this.targetViewportWindow && this.targetViewportWindow !== window) {
5717
+ this.targetViewportWindow.addEventListener(
5718
+ "scroll",
5719
+ this.handleViewportChange,
5720
+ VIEWPORT_SCROLL_OPTIONS
5721
+ );
5722
+ this.targetViewportWindow.addEventListener(
5723
+ "resize",
5724
+ this.handleViewportChange
5725
+ );
5726
+ }
5649
5727
  this.render();
5650
5728
  }
5651
5729
  destroy() {
5652
5730
  this.view.clearDraftPreview();
5653
5731
  this.clearDrafts();
5654
5732
  document.removeEventListener("keydown", this.handleKeyDown, true);
5655
- window.removeEventListener("scroll", this.handleViewportChange, true);
5733
+ window.removeEventListener(
5734
+ "scroll",
5735
+ this.handleViewportChange,
5736
+ VIEWPORT_SCROLL_OPTIONS
5737
+ );
5656
5738
  window.removeEventListener("resize", this.handleViewportChange);
5739
+ if (this.targetViewportWindow && this.targetViewportWindow !== window) {
5740
+ this.targetViewportWindow.removeEventListener(
5741
+ "scroll",
5742
+ this.handleViewportChange,
5743
+ VIEWPORT_SCROLL_OPTIONS
5744
+ );
5745
+ this.targetViewportWindow.removeEventListener(
5746
+ "resize",
5747
+ this.handleViewportChange
5748
+ );
5749
+ }
5750
+ this.targetViewportWindow = void 0;
5657
5751
  if (this.renderFrame) {
5658
5752
  window.cancelAnimationFrame(this.renderFrame);
5659
5753
  this.renderFrame = void 0;
@@ -5698,6 +5792,33 @@ var WebReviewKitApp = class {
5698
5792
  this.isSelectingArea = false;
5699
5793
  await this.bindElementDraftToElement(element, { comment });
5700
5794
  }
5795
+ async selectElement(element) {
5796
+ if (!this.isOpen) {
5797
+ this.isOpen = true;
5798
+ }
5799
+ this.setModeState("element");
5800
+ this.clearDrafts();
5801
+ this.isSelectingArea = false;
5802
+ await this.bindElementDraftToElement(element, {
5803
+ isSelectionOnly: true
5804
+ });
5805
+ }
5806
+ adjustElementSelection(delta, options) {
5807
+ if (!this.domDraft?.selection) return false;
5808
+ const current = this.domDraft.adjustment;
5809
+ this.domDraft = {
5810
+ ...this.domDraft,
5811
+ adjustment: {
5812
+ x: (current?.x ?? 0) + (delta.x ?? 0),
5813
+ y: (current?.y ?? 0) + (delta.y ?? 0),
5814
+ scale: (current?.scale ?? 0) + (delta.scale ?? 0),
5815
+ isActive: true,
5816
+ preview: options?.preview ?? current?.preview
5817
+ }
5818
+ };
5819
+ this.render();
5820
+ return true;
5821
+ }
5701
5822
  getMode() {
5702
5823
  return this.mode;
5703
5824
  }
@@ -5777,6 +5898,33 @@ var WebReviewKitApp = class {
5777
5898
  this.render();
5778
5899
  return true;
5779
5900
  }
5901
+ syncDomDraftViewportGeometry() {
5902
+ const draft = this.domDraft;
5903
+ const element = draft?.previewElement;
5904
+ const environment = this.getEnvironment();
5905
+ if (!draft?.selection || !element?.isConnected || !environment || element.ownerDocument !== environment.document) {
5906
+ return;
5907
+ }
5908
+ const rect = element.getBoundingClientRect();
5909
+ if (rect.width <= 0 || rect.height <= 0) return;
5910
+ const selection = {
5911
+ left: rect.left,
5912
+ top: rect.top,
5913
+ width: rect.width,
5914
+ height: rect.height
5915
+ };
5916
+ this.domDraft = {
5917
+ ...draft,
5918
+ marker: {
5919
+ ...draft.marker,
5920
+ viewport: roundPoint({ x: rect.left, y: rect.top })
5921
+ },
5922
+ selection: {
5923
+ ...draft.selection,
5924
+ viewport: toPublicSelection(selection)
5925
+ }
5926
+ };
5927
+ }
5780
5928
  isDraftComposerFocused() {
5781
5929
  if (!this.domDraft && !this.areaDraft) return false;
5782
5930
  const composerHost = this.getEnvironment()?.composerHost;
@@ -6000,46 +6148,28 @@ var WebReviewKitApp = class {
6000
6148
  this.root.style.display = previousDisplay;
6001
6149
  }
6002
6150
  }
6003
- async captureDomDraft(input) {
6004
- if (this.isCapturingViewport) return;
6005
- const environment = this.getEnvironment();
6006
- const draft = this.domDraft;
6007
- if (!draft) return;
6008
- if (!environment?.captureViewport) {
6009
- this.draftError = "Viewport capture helper is not available.";
6010
- this.render();
6011
- return;
6012
- }
6013
- const captureInput = this.createViewportCaptureInput(
6014
- environment,
6151
+ captureDomDraft(input) {
6152
+ return this.captureDraft(
6015
6153
  input,
6016
- input.selection?.viewport
6154
+ () => this.domDraft,
6155
+ (draft) => {
6156
+ this.domDraft = draft;
6157
+ }
6158
+ );
6159
+ }
6160
+ captureAreaDraft(input) {
6161
+ return this.captureDraft(
6162
+ input,
6163
+ () => this.areaDraft,
6164
+ (draft) => {
6165
+ this.areaDraft = draft;
6166
+ }
6017
6167
  );
6018
- this.draftError = "";
6019
- this.isCapturingViewport = true;
6020
- this.render();
6021
- try {
6022
- const result = await environment.captureViewport(captureInput);
6023
- const attachment = this.createCaptureDraftAttachment(result, captureInput);
6024
- const currentDraft = this.domDraft ?? draft;
6025
- this.domDraft = {
6026
- ...currentDraft,
6027
- attachments: [...currentDraft.attachments ?? [], attachment]
6028
- };
6029
- } catch (error) {
6030
- this.draftError = this.getErrorMessage(
6031
- error,
6032
- "Failed to capture viewport."
6033
- );
6034
- } finally {
6035
- this.isCapturingViewport = false;
6036
- this.render();
6037
- }
6038
6168
  }
6039
- async captureAreaDraft(input) {
6169
+ async captureDraft(input, getDraft, setDraft) {
6040
6170
  if (this.isCapturingViewport) return;
6041
6171
  const environment = this.getEnvironment();
6042
- const draft = this.areaDraft;
6172
+ const draft = getDraft();
6043
6173
  if (!draft) return;
6044
6174
  if (!environment?.captureViewport) {
6045
6175
  this.draftError = "Viewport capture helper is not available.";
@@ -6057,16 +6187,13 @@ var WebReviewKitApp = class {
6057
6187
  try {
6058
6188
  const result = await environment.captureViewport(captureInput);
6059
6189
  const attachment = this.createCaptureDraftAttachment(result, captureInput);
6060
- const currentDraft = this.areaDraft ?? draft;
6061
- this.areaDraft = {
6190
+ const currentDraft = getDraft() ?? draft;
6191
+ setDraft({
6062
6192
  ...currentDraft,
6063
6193
  attachments: [...currentDraft.attachments ?? [], attachment]
6064
- };
6194
+ });
6065
6195
  } catch (error) {
6066
- this.draftError = this.getErrorMessage(
6067
- error,
6068
- "Failed to capture viewport."
6069
- );
6196
+ this.draftError = getErrorMessage(error, "Failed to capture viewport.");
6070
6197
  } finally {
6071
6198
  this.isCapturingViewport = false;
6072
6199
  this.render();
@@ -6205,17 +6332,10 @@ var WebReviewKitApp = class {
6205
6332
  );
6206
6333
  }
6207
6334
  getCreateItemErrorMessage(error, wasUploadingAttachments) {
6208
- const message = this.getErrorMessage(error, "Failed to save QA.");
6335
+ const message = getErrorMessage(error, "Failed to save QA.");
6209
6336
  const reason = error && typeof error === "object" && "reason" in error && typeof error.reason === "string" ? ` (${error.reason})` : "";
6210
6337
  return wasUploadingAttachments && reason ? `Attachment upload failed${reason}: ${message}` : message;
6211
6338
  }
6212
- getErrorMessage(error, fallback) {
6213
- if (error instanceof Error) return error.message;
6214
- if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
6215
- return error.message;
6216
- }
6217
- return fallback;
6218
- }
6219
6339
  async restoreItem(item) {
6220
6340
  this.setModeState("idle");
6221
6341
  this.clearDrafts();
@@ -6248,6 +6368,11 @@ function createNoopController() {
6248
6368
  },
6249
6369
  async startElementReview() {
6250
6370
  },
6371
+ async selectElement() {
6372
+ },
6373
+ adjustElementSelection() {
6374
+ return false;
6375
+ },
6251
6376
  getMode() {
6252
6377
  return "idle";
6253
6378
  },