@designfever/web-review-kit 0.8.0 → 0.8.2

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
@@ -625,38 +625,69 @@ async function createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOpt
625
625
  const requestFetch = fetchOption ?? globalThis.fetch;
626
626
  if (!requestFetch) throw new Error("Figma client rendering requires fetch.");
627
627
  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
628
+ const timeoutMs = options.timeoutMs ?? 1e4;
629
+ const imageUrl = await withStageTimeout(
630
+ async (signal) => {
631
+ const response = await requestFetch(
632
+ createReviewFigmaImageApiUrl({
633
+ apiBaseUrl: options.apiBaseUrl,
634
+ fileKey: ref.fileKey,
635
+ nodeId: ref.nodeId,
636
+ format: renderFormat,
637
+ scale: options.renderScale,
638
+ useAbsoluteBounds: options.useAbsoluteBounds
639
+ }),
640
+ {
641
+ headers: {
642
+ "X-Figma-Token": token
643
+ },
644
+ signal
645
+ }
646
+ );
647
+ const body = await response.json().catch(() => null);
648
+ if (!response.ok) {
649
+ throw new Error(
650
+ body?.err || `Figma image render failed with ${response.status}`
651
+ );
640
652
  }
641
- }
653
+ const nextImageUrl = body?.images?.[ref.nodeId];
654
+ if (!nextImageUrl) {
655
+ throw new Error(
656
+ `Figma image render returned no URL for ${ref.nodeId}.`
657
+ );
658
+ }
659
+ return nextImageUrl;
660
+ },
661
+ timeoutMs,
662
+ "Figma API request timed out."
642
663
  );
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();
664
+ const originalBlob = await withStageTimeout(
665
+ async (signal) => {
666
+ const imageResponse = await requestFetch(imageUrl, { signal });
667
+ if (!imageResponse.ok) {
668
+ throw new Error(
669
+ `Figma image download failed with ${imageResponse.status}`
670
+ );
671
+ }
672
+ return imageResponse.blob();
673
+ },
674
+ timeoutMs,
675
+ "Figma image download timed out."
676
+ );
677
+ return withStageTimeout(
678
+ () => createProcessedFigmaAsset(originalBlob, renderFormat, options),
679
+ timeoutMs,
680
+ "Figma image processing timed out."
681
+ );
682
+ }
683
+ async function createProcessedFigmaAsset(originalBlob, renderFormat, options) {
654
684
  const originalMimeType = normalizeClientImageMimeType(originalBlob.type) ?? getReviewFigmaImageMimeType(renderFormat === "jpg" ? "jpg" : "png");
655
685
  const originalFormat = getReviewFigmaImageFormatFromMimeType(originalMimeType) ?? (renderFormat === "jpg" ? "jpg" : "png");
656
- const dimensions = await readImageBlobDimensions(originalBlob);
686
+ const image = await loadImageBlob(originalBlob);
687
+ const dimensions = readImageDimensions(image);
657
688
  const shouldConvertToWebp = options.convertToWebp ?? true;
658
- const convertedBlob = shouldConvertToWebp ? await convertImageBlobToWebp(
659
- originalBlob,
689
+ const convertedBlob = shouldConvertToWebp ? await convertImageToWebp(
690
+ image,
660
691
  options.webpQuality ?? 0.9,
661
692
  dimensions
662
693
  ).catch(() => null) : null;
@@ -678,22 +709,17 @@ function createReviewFigmaClientRenderedAsset({
678
709
  token,
679
710
  ...options
680
711
  }) {
681
- return withTimeout(
682
- createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption),
683
- options.timeoutMs ?? 1e4
684
- );
712
+ return createClientRenderedFigmaAsset(figmaUrl, token, options, fetchOption);
685
713
  }
686
- async function readImageBlobDimensions(blob) {
687
- const image = await loadImageBlob(blob);
714
+ function readImageDimensions(image) {
688
715
  return {
689
716
  width: image.naturalWidth || image.width,
690
717
  height: image.naturalHeight || image.height
691
718
  };
692
719
  }
693
- async function convertImageBlobToWebp(blob, quality, dimensions) {
720
+ async function convertImageToWebp(image, quality, dimensions) {
694
721
  if (typeof document === "undefined") return null;
695
722
  if (!dimensions.width || !dimensions.height) return null;
696
- const image = await loadImageBlob(blob);
697
723
  const canvas = document.createElement("canvas");
698
724
  canvas.width = dimensions.width;
699
725
  canvas.height = dimensions.height;
@@ -745,18 +771,26 @@ function normalizeClientImageMimeType(value) {
745
771
  }
746
772
  return null;
747
773
  }
748
- async function withTimeout(promise, timeoutMs) {
774
+ async function withStageTimeout(run, timeoutMs, message) {
775
+ const controller = new AbortController();
776
+ let didTimeout = false;
749
777
  let timeoutId;
750
778
  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
- ]);
779
+ try {
780
+ return await Promise.race([
781
+ run(controller.signal),
782
+ new Promise((_, reject) => {
783
+ timeoutId = setTimeout(() => {
784
+ didTimeout = true;
785
+ controller.abort();
786
+ reject(new Error(message));
787
+ }, timeoutMs);
788
+ })
789
+ ]);
790
+ } catch (error) {
791
+ if (didTimeout) throw new Error(message);
792
+ throw error;
793
+ }
760
794
  } finally {
761
795
  if (timeoutId) clearTimeout(timeoutId);
762
796
  }
@@ -962,7 +996,7 @@ async function uploadFigmaAsset({
962
996
  const requestFetch = fetchOption ?? globalThis.fetch;
963
997
  if (!requestFetch) throw new Error("Figma asset upload requires fetch.");
964
998
  const { blob, mimeType } = decodeAssetDataUrl(asset);
965
- const response = await withTimeout2(
999
+ const response = await withTimeout(
966
1000
  requestFetch(
967
1001
  createAssetUploadUrl(endpoint, {
968
1002
  imageFormat: asset.imageFormat,
@@ -1091,7 +1125,7 @@ function decodeAssetDataUrl(asset) {
1091
1125
  mimeType
1092
1126
  };
1093
1127
  }
1094
- async function withTimeout2(promise, timeoutMs, message) {
1128
+ async function withTimeout(promise, timeoutMs, message) {
1095
1129
  let timeoutId;
1096
1130
  try {
1097
1131
  return await Promise.race([
@@ -2426,6 +2460,7 @@ var DraftPreviewController = class {
2426
2460
  };
2427
2461
  element.style.visibility = "hidden";
2428
2462
  }
2463
+ syncDraftPreviewCloneRect(this.snapshot.clone, element);
2429
2464
  const metrics = this.config.getMetrics(draft);
2430
2465
  const translate = `translate(${toCssNumber(metrics.cssX)}px, ${toCssNumber(
2431
2466
  metrics.cssY
@@ -2450,16 +2485,11 @@ var DraftPreviewController = class {
2450
2485
  }
2451
2486
  };
2452
2487
  function positionDraftPreviewClone(clone, element, computedStyle) {
2453
- const rect = element.getBoundingClientRect();
2454
2488
  clone.setAttribute("data-dfwr-adjust-preview", "true");
2455
2489
  clone.setAttribute("aria-hidden", "true");
2456
2490
  clone.style.position = "fixed";
2457
- clone.style.left = `${toCssNumber(rect.left)}px`;
2458
- clone.style.top = `${toCssNumber(rect.top)}px`;
2459
2491
  clone.style.right = "auto";
2460
2492
  clone.style.bottom = "auto";
2461
- clone.style.width = `${toCssNumber(rect.width)}px`;
2462
- clone.style.height = `${toCssNumber(rect.height)}px`;
2463
2493
  clone.style.maxWidth = "none";
2464
2494
  clone.style.maxHeight = "none";
2465
2495
  clone.style.margin = "0";
@@ -2471,6 +2501,14 @@ function positionDraftPreviewClone(clone, element, computedStyle) {
2471
2501
  clone.style.willChange = "transform";
2472
2502
  clone.style.transformOrigin = "top left";
2473
2503
  clone.style.transform = "none";
2504
+ syncDraftPreviewCloneRect(clone, element);
2505
+ }
2506
+ function syncDraftPreviewCloneRect(clone, element) {
2507
+ const rect = element.getBoundingClientRect();
2508
+ clone.style.left = `${toCssNumber(rect.left)}px`;
2509
+ clone.style.top = `${toCssNumber(rect.top)}px`;
2510
+ clone.style.width = `${toCssNumber(rect.width)}px`;
2511
+ clone.style.height = `${toCssNumber(rect.height)}px`;
2474
2512
  }
2475
2513
  function getDraftPreviewDisplay(display) {
2476
2514
  if (display === "inline" || display === "contents") return "inline-block";
@@ -2546,11 +2584,11 @@ function createStyleElement() {
2546
2584
  --df-review-color-text: #f7f7f2;
2547
2585
  --df-review-color-text-muted: rgba(247, 247, 242, 0.62);
2548
2586
  --df-review-color-text-subtle: rgba(247, 247, 242, 0.46);
2549
- --df-review-color-accent: #d7ff5f;
2587
+ --df-review-color-accent: #7cc7ff;
2550
2588
  --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;
2589
+ --df-review-color-accent-soft: rgba(124, 199, 255, 0.16);
2590
+ --df-review-color-accent-ring: rgba(124, 199, 255, 0.6);
2591
+ --df-review-color-area: #7cc7ff;
2554
2592
  --df-review-color-error: #ffb7a7;
2555
2593
  --df-review-shadow-panel: 0 18px 48px rgba(0, 0, 0, 0.34);
2556
2594
  --df-review-shadow-popover: 0 16px 38px rgba(0, 0, 0, 0.32);
@@ -2736,9 +2774,9 @@ function createStyleElement() {
2736
2774
  .dfwr-selection-highlight {
2737
2775
  position: fixed;
2738
2776
  z-index: 1;
2739
- border: 2px solid #d7ff5f;
2777
+ border: 2px solid #7cc7ff;
2740
2778
  border-radius: var(--df-review-radius-xs);
2741
- background: rgba(215, 255, 95, 0.08);
2779
+ background: rgba(124, 199, 255, 0.08);
2742
2780
  box-shadow:
2743
2781
  0 0 0 1px rgba(31, 36, 40, 0.72),
2744
2782
  0 0 0 9999px rgba(0, 0, 0, 0.12),
@@ -2747,8 +2785,8 @@ function createStyleElement() {
2747
2785
  }
2748
2786
 
2749
2787
  .dfwr-selection-highlight.is-draft {
2750
- border-color: #63d7c7;
2751
- background: rgba(99, 215, 199, 0.1);
2788
+ border-color: #7cc7ff;
2789
+ background: rgba(124, 199, 255, 0.1);
2752
2790
  box-shadow:
2753
2791
  0 0 0 1px rgba(31, 36, 40, 0.72),
2754
2792
  0 0 0 9999px rgba(0, 0, 0, 0.08),
@@ -2759,9 +2797,9 @@ function createStyleElement() {
2759
2797
  .dfwr-dom-hover {
2760
2798
  position: fixed;
2761
2799
  z-index: 2;
2762
- border: 1px solid #d7ff5f;
2800
+ border: 1px solid #7cc7ff;
2763
2801
  border-radius: var(--df-review-radius-xs);
2764
- background: rgba(215, 255, 95, 0.1);
2802
+ background: rgba(124, 199, 255, 0.1);
2765
2803
  box-shadow:
2766
2804
  0 0 0 1px rgba(31, 36, 40, 0.72),
2767
2805
  0 0 0 9999px rgba(0, 0, 0, 0.08);
@@ -2942,12 +2980,12 @@ function createStyleElement() {
2942
2980
  }
2943
2981
 
2944
2982
  .dfwr-area-preview-layer .dfwr-bound-marker {
2945
- border-color: #63d7c7;
2983
+ border-color: #7cc7ff;
2946
2984
  background: var(--df-review-color-panel);
2947
2985
  box-shadow:
2948
- 0 0 0 5px rgba(99, 215, 199, 0.2),
2986
+ 0 0 0 5px rgba(124, 199, 255, 0.2),
2949
2987
  0 12px 26px rgba(0, 0, 0, 0.3);
2950
- color: #63d7c7;
2988
+ color: #7cc7ff;
2951
2989
  }
2952
2990
 
2953
2991
  .dfwr-bound-marker-icon {
@@ -3030,7 +3068,7 @@ function createStyleElement() {
3030
3068
  border-radius: var(--df-review-radius-pill);
3031
3069
  background: var(--df-review-color-accent);
3032
3070
  box-shadow:
3033
- 0 0 0 4px rgba(215, 255, 95, 0.22),
3071
+ 0 0 0 4px rgba(124, 199, 255, 0.22),
3034
3072
  0 8px 18px rgba(0, 0, 0, 0.28);
3035
3073
  cursor: grab;
3036
3074
  pointer-events: auto;
@@ -3048,7 +3086,7 @@ function createStyleElement() {
3048
3086
  pointer-events: auto;
3049
3087
  color: var(--df-review-color-text);
3050
3088
  background: var(--df-review-color-panel);
3051
- border: 1px solid rgba(215, 255, 95, 0.56);
3089
+ border: 1px solid rgba(124, 199, 255, 0.56);
3052
3090
  border-radius: var(--df-review-radius-md);
3053
3091
  box-shadow: var(--df-review-shadow-popover);
3054
3092
  }
@@ -3057,7 +3095,7 @@ function createStyleElement() {
3057
3095
  .dfwr-area-draft.is-composer {
3058
3096
  max-height: min(360px, calc(100vh - 32px));
3059
3097
  overflow: auto;
3060
- border-color: rgba(99, 215, 199, 0.56);
3098
+ border-color: rgba(124, 199, 255, 0.56);
3061
3099
  }
3062
3100
 
3063
3101
  .dfwr-shell.is-docked-composer .dfwr-dom-popover.is-docked-composer,
@@ -3094,7 +3132,7 @@ function createStyleElement() {
3094
3132
 
3095
3133
  .dfwr-draft-drag-handle:hover,
3096
3134
  .dfwr-draft-drag-handle:focus-visible {
3097
- background: rgba(215, 255, 95, 0.62);
3135
+ background: rgba(124, 199, 255, 0.62);
3098
3136
  }
3099
3137
 
3100
3138
  .dfwr-draft-drag-handle:active {
@@ -3113,7 +3151,7 @@ function createStyleElement() {
3113
3151
  pointer-events: auto;
3114
3152
  color: var(--df-review-color-text);
3115
3153
  background: var(--df-review-color-panel);
3116
- border: 1px solid rgba(215, 255, 95, 0.56);
3154
+ border: 1px solid rgba(124, 199, 255, 0.56);
3117
3155
  border-radius: var(--df-review-radius-md);
3118
3156
  box-shadow: var(--df-review-shadow-popover);
3119
3157
  }
@@ -3332,7 +3370,7 @@ function createStyleElement() {
3332
3370
  }
3333
3371
 
3334
3372
  .dfwr-adjust-panel.is-active {
3335
- border-color: rgba(215, 255, 95, 0.5);
3373
+ border-color: rgba(124, 199, 255, 0.5);
3336
3374
  background: var(--df-review-color-accent-soft);
3337
3375
  }
3338
3376
 
@@ -3371,7 +3409,7 @@ function createStyleElement() {
3371
3409
  .dfwr-adjust-toggle:hover,
3372
3410
  .dfwr-adjust-toggle:focus-visible,
3373
3411
  .dfwr-adjust-toggle.is-active {
3374
- border-color: rgba(215, 255, 95, 0.68);
3412
+ border-color: rgba(124, 199, 255, 0.68);
3375
3413
  background: var(--df-review-color-accent-soft);
3376
3414
  outline: none;
3377
3415
  }
@@ -3434,7 +3472,7 @@ function createStyleElement() {
3434
3472
  }
3435
3473
 
3436
3474
  .dfwr-item:focus-visible {
3437
- outline: 2px solid rgba(215, 255, 95, 0.72);
3475
+ outline: 2px solid rgba(124, 199, 255, 0.72);
3438
3476
  outline-offset: 4px;
3439
3477
  }
3440
3478
 
@@ -3544,8 +3582,8 @@ function createStyleElement() {
3544
3582
  z-index: 2;
3545
3583
  width: 0;
3546
3584
  height: 0;
3547
- border: 1px solid #d7ff5f;
3548
- background: rgba(215, 255, 95, 0.16);
3585
+ border: 1px solid #7cc7ff;
3586
+ background: rgba(124, 199, 255, 0.16);
3549
3587
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.18);
3550
3588
  }
3551
3589
 
@@ -4622,6 +4660,12 @@ function createDomDraftLayer(context, draft, options = {}) {
4622
4660
  pin.setAttribute("aria-label", "Move DOM point");
4623
4661
  pin.style.left = `${hostPoint.x}px`;
4624
4662
  pin.style.top = `${hostPoint.y}px`;
4663
+ if (draft.isSelectionOnly) {
4664
+ pin.classList.add("is-selection-only");
4665
+ pin.tabIndex = -1;
4666
+ group.append(pin);
4667
+ return { layer: group, composer: void 0 };
4668
+ }
4625
4669
  const popover = document.createElement("div");
4626
4670
  const position = getPopoverPosition(hostPoint, environment);
4627
4671
  popover.className = [
@@ -4744,7 +4788,7 @@ function createDomDraftLayer(context, draft, options = {}) {
4744
4788
  attachments: currentDraft.attachments
4745
4789
  });
4746
4790
  };
4747
- const adjustmentControls = isElementDraft ? createAdjustmentControls(context, {
4791
+ const adjustmentControls = isElementDraft && !options.dockComposer ? createAdjustmentControls(context, {
4748
4792
  draft,
4749
4793
  pin,
4750
4794
  popover,
@@ -5390,7 +5434,7 @@ var WebReviewKitView = class {
5390
5434
  this.draftPreview = new DraftPreviewController({
5391
5435
  getEnvironment: () => this.config.getEnvironment(),
5392
5436
  getMetrics: (draft) => getDraftAdjustmentMetrics(draft, presets()),
5393
- hasAdjustment: (draft) => hasDraftAdjustment(draft, presets())
5437
+ hasAdjustment: (draft) => draft.adjustment?.preview !== false && hasDraftAdjustment(draft, presets())
5394
5438
  });
5395
5439
  this.draftContext = {
5396
5440
  config: this.config,
@@ -5411,8 +5455,11 @@ var WebReviewKitView = class {
5411
5455
  shadow.replaceChildren();
5412
5456
  shadow.append(createStyleElement());
5413
5457
  shadow.append(hiddenItemsStyle);
5414
- const hasDismissableDraft = Boolean(state.domDraft || state.areaDraft);
5415
- const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && Boolean(this.getShellComposerHost());
5458
+ const hasSelectionOnlyDraft = state.domDraft?.isSelectionOnly === true;
5459
+ const hasDismissableDraft = Boolean(
5460
+ state.domDraft && !hasSelectionOnlyDraft || state.areaDraft
5461
+ );
5462
+ const shouldDockComposer = this.config.options.ui?.panel === false && hasDismissableDraft && !hasSelectionOnlyDraft && Boolean(this.getShellComposerHost());
5416
5463
  let dockedComposer;
5417
5464
  const shell = document.createElement("div");
5418
5465
  shell.className = [
@@ -5534,6 +5581,7 @@ var WebReviewKitView = class {
5534
5581
 
5535
5582
  // src/core/web.review.kit.app.ts
5536
5583
  var ROOT_ID = "df-web-review-kit-root";
5584
+ var VIEWPORT_SCROLL_OPTIONS = { capture: true, passive: true };
5537
5585
  function isEditableEventTarget(event) {
5538
5586
  const path = event.composedPath?.() ?? [];
5539
5587
  const element = path[0] ?? event.target;
@@ -5553,6 +5601,8 @@ function createWebReviewKit(options) {
5553
5601
  toggle: () => app.toggle(),
5554
5602
  setMode: (mode) => app.setMode(mode),
5555
5603
  startElementReview: (element, comment) => app.startElementReview(element, comment),
5604
+ selectElement: (element) => app.selectElement(element),
5605
+ adjustElementSelection: (delta, adjustmentOptions) => app.adjustElementSelection(delta, adjustmentOptions),
5556
5606
  getMode: () => app.getMode(),
5557
5607
  highlightItem: (itemId) => app.highlightItem(itemId),
5558
5608
  setHiddenItemIds: (itemIds) => app.setHiddenItemIds(itemIds),
@@ -5573,6 +5623,12 @@ var WebReviewKitApp = class {
5573
5623
  this.isSelectingArea = false;
5574
5624
  this.handleKeyDown = (event) => {
5575
5625
  if (event.key === "Escape" && this.cancelMode()) {
5626
+ const activeElement = document.activeElement;
5627
+ if (activeElement instanceof HTMLButtonElement && activeElement.matches(
5628
+ ".df-review-mode-button, .df-review-section-outline-link.is-dom-select"
5629
+ )) {
5630
+ activeElement.blur();
5631
+ }
5576
5632
  event.preventDefault();
5577
5633
  event.stopPropagation();
5578
5634
  return;
@@ -5587,6 +5643,7 @@ var WebReviewKitApp = class {
5587
5643
  this.renderFrame = window.requestAnimationFrame(() => {
5588
5644
  this.renderFrame = void 0;
5589
5645
  if (this.isDraftComposerFocused()) return;
5646
+ this.syncDomDraftViewportGeometry();
5590
5647
  this.render();
5591
5648
  });
5592
5649
  };
@@ -5644,16 +5701,48 @@ var WebReviewKitApp = class {
5644
5701
  this.shadow = this.root.attachShadow({ mode: "open" });
5645
5702
  document.body.appendChild(this.root);
5646
5703
  document.addEventListener("keydown", this.handleKeyDown, true);
5647
- window.addEventListener("scroll", this.handleViewportChange, true);
5704
+ window.addEventListener(
5705
+ "scroll",
5706
+ this.handleViewportChange,
5707
+ VIEWPORT_SCROLL_OPTIONS
5708
+ );
5648
5709
  window.addEventListener("resize", this.handleViewportChange);
5710
+ this.targetViewportWindow = this.getEnvironment()?.window;
5711
+ if (this.targetViewportWindow && this.targetViewportWindow !== window) {
5712
+ this.targetViewportWindow.addEventListener(
5713
+ "scroll",
5714
+ this.handleViewportChange,
5715
+ VIEWPORT_SCROLL_OPTIONS
5716
+ );
5717
+ this.targetViewportWindow.addEventListener(
5718
+ "resize",
5719
+ this.handleViewportChange
5720
+ );
5721
+ }
5649
5722
  this.render();
5650
5723
  }
5651
5724
  destroy() {
5652
5725
  this.view.clearDraftPreview();
5653
5726
  this.clearDrafts();
5654
5727
  document.removeEventListener("keydown", this.handleKeyDown, true);
5655
- window.removeEventListener("scroll", this.handleViewportChange, true);
5728
+ window.removeEventListener(
5729
+ "scroll",
5730
+ this.handleViewportChange,
5731
+ VIEWPORT_SCROLL_OPTIONS
5732
+ );
5656
5733
  window.removeEventListener("resize", this.handleViewportChange);
5734
+ if (this.targetViewportWindow && this.targetViewportWindow !== window) {
5735
+ this.targetViewportWindow.removeEventListener(
5736
+ "scroll",
5737
+ this.handleViewportChange,
5738
+ VIEWPORT_SCROLL_OPTIONS
5739
+ );
5740
+ this.targetViewportWindow.removeEventListener(
5741
+ "resize",
5742
+ this.handleViewportChange
5743
+ );
5744
+ }
5745
+ this.targetViewportWindow = void 0;
5657
5746
  if (this.renderFrame) {
5658
5747
  window.cancelAnimationFrame(this.renderFrame);
5659
5748
  this.renderFrame = void 0;
@@ -5698,6 +5787,33 @@ var WebReviewKitApp = class {
5698
5787
  this.isSelectingArea = false;
5699
5788
  await this.bindElementDraftToElement(element, { comment });
5700
5789
  }
5790
+ async selectElement(element) {
5791
+ if (!this.isOpen) {
5792
+ this.isOpen = true;
5793
+ }
5794
+ this.setModeState("element");
5795
+ this.clearDrafts();
5796
+ this.isSelectingArea = false;
5797
+ await this.bindElementDraftToElement(element, {
5798
+ isSelectionOnly: true
5799
+ });
5800
+ }
5801
+ adjustElementSelection(delta, options) {
5802
+ if (!this.domDraft?.selection) return false;
5803
+ const current = this.domDraft.adjustment;
5804
+ this.domDraft = {
5805
+ ...this.domDraft,
5806
+ adjustment: {
5807
+ x: (current?.x ?? 0) + (delta.x ?? 0),
5808
+ y: (current?.y ?? 0) + (delta.y ?? 0),
5809
+ scale: (current?.scale ?? 0) + (delta.scale ?? 0),
5810
+ isActive: true,
5811
+ preview: options?.preview ?? current?.preview
5812
+ }
5813
+ };
5814
+ this.render();
5815
+ return true;
5816
+ }
5701
5817
  getMode() {
5702
5818
  return this.mode;
5703
5819
  }
@@ -5777,6 +5893,33 @@ var WebReviewKitApp = class {
5777
5893
  this.render();
5778
5894
  return true;
5779
5895
  }
5896
+ syncDomDraftViewportGeometry() {
5897
+ const draft = this.domDraft;
5898
+ const element = draft?.previewElement;
5899
+ const environment = this.getEnvironment();
5900
+ if (!draft?.selection || !element?.isConnected || !environment || element.ownerDocument !== environment.document) {
5901
+ return;
5902
+ }
5903
+ const rect = element.getBoundingClientRect();
5904
+ if (rect.width <= 0 || rect.height <= 0) return;
5905
+ const selection = {
5906
+ left: rect.left,
5907
+ top: rect.top,
5908
+ width: rect.width,
5909
+ height: rect.height
5910
+ };
5911
+ this.domDraft = {
5912
+ ...draft,
5913
+ marker: {
5914
+ ...draft.marker,
5915
+ viewport: roundPoint({ x: rect.left, y: rect.top })
5916
+ },
5917
+ selection: {
5918
+ ...draft.selection,
5919
+ viewport: toPublicSelection(selection)
5920
+ }
5921
+ };
5922
+ }
5780
5923
  isDraftComposerFocused() {
5781
5924
  if (!this.domDraft && !this.areaDraft) return false;
5782
5925
  const composerHost = this.getEnvironment()?.composerHost;
@@ -6248,6 +6391,11 @@ function createNoopController() {
6248
6391
  },
6249
6392
  async startElementReview() {
6250
6393
  },
6394
+ async selectElement() {
6395
+ },
6396
+ adjustElementSelection() {
6397
+ return false;
6398
+ },
6251
6399
  getMode() {
6252
6400
  return "idle";
6253
6401
  },