@designfever/web-review-kit 0.4.0 → 0.5.0

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
@@ -558,8 +558,22 @@ function getDomAnchorFromPoint(point, configuredAttribute = "data-qa-id", enviro
558
558
  source: getDomSourceHint(target)
559
559
  };
560
560
  }
561
- function getElementViewportSelection(anchor, environment) {
562
- const element = getAnchorElement(anchor, environment);
561
+ function getDomAnchorFromElement(target, configuredAttribute = "data-qa-id", environment) {
562
+ if (target.ownerDocument !== environment.document) return void 0;
563
+ const candidates = createAnchorCandidates(target, configuredAttribute);
564
+ const primary = candidates[0];
565
+ if (!primary) return void 0;
566
+ return {
567
+ ...primary,
568
+ candidates,
569
+ htmlSnippet: getElementHtmlSnippet(
570
+ getAnchorSourceElement(target, primary, configuredAttribute) ?? target
571
+ ),
572
+ source: getDomSourceHint(target)
573
+ };
574
+ }
575
+ function getElementViewportSelection(anchor, environment, preferredSelection) {
576
+ const element = getAnchorElement(anchor, environment, preferredSelection);
563
577
  if (!element) return void 0;
564
578
  const rect = element.getBoundingClientRect();
565
579
  if (rect.width <= 0 || rect.height <= 0) return void 0;
@@ -598,22 +612,35 @@ function getAnchorCandidates(anchor) {
598
612
  ...anchor.candidates ?? []
599
613
  ]);
600
614
  }
601
- function resolveAnchorElement(anchor, environment) {
615
+ function resolveAnchorElement(anchor, environment, preferredSelection) {
602
616
  const matches = getAnchorCandidates(anchor).flatMap((candidate) => {
603
- const match = queryBestAnchorCandidate(
604
- candidate,
605
- candidate.textFingerprint ?? anchor.textFingerprint,
606
- environment
607
- );
608
- if (!match) return [];
609
- const confidence = roundRatio(
610
- (candidate.confidence ?? 0.5) * match.score
611
- );
612
- return [{
613
- element: match.element,
614
- candidate,
615
- confidence
616
- }];
617
+ const textFingerprint = candidate.textFingerprint ?? anchor.textFingerprint;
618
+ if (!preferredSelection) {
619
+ const match = queryBestAnchorCandidate(
620
+ candidate,
621
+ textFingerprint,
622
+ environment
623
+ );
624
+ if (!match) return [];
625
+ const confidence = roundRatio(
626
+ (candidate.confidence ?? 0.5) * match.score
627
+ );
628
+ return [{
629
+ element: match.element,
630
+ candidate,
631
+ confidence
632
+ }];
633
+ }
634
+ return queryAnchorElements(candidate.selector, environment).map((element) => {
635
+ const confidence = roundRatio(
636
+ (candidate.confidence ?? 0.5) * getTextFingerprintScore(textFingerprint, getTextFingerprint(element)) * getSelectionMatchScore(element, preferredSelection)
637
+ );
638
+ return {
639
+ element,
640
+ candidate,
641
+ confidence
642
+ };
643
+ });
617
644
  });
618
645
  return matches.sort((a, b) => b.confidence - a.confidence)[0];
619
646
  }
@@ -623,8 +650,8 @@ function cssEscape(value) {
623
650
  }
624
651
  return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
625
652
  }
626
- function getAnchorElement(anchor, environment) {
627
- return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment)?.element;
653
+ function getAnchorElement(anchor, environment, preferredSelection) {
654
+ return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment, preferredSelection)?.element;
628
655
  }
629
656
  function createAnchorCandidates(target, configuredAttribute) {
630
657
  const targetCandidates = [];
@@ -988,6 +1015,38 @@ function getTextFingerprintScore(expected, actual) {
988
1015
  const matches = expectedTokens.filter((token) => actualTokens.has(token));
989
1016
  return clamp(matches.length / expectedTokens.length, 0.25, 0.76);
990
1017
  }
1018
+ function getSelectionMatchScore(element, selection) {
1019
+ const rect = element.getBoundingClientRect();
1020
+ if (rect.width <= 0 || rect.height <= 0) return 0.05;
1021
+ const overlapLeft = Math.max(rect.left, selection.left);
1022
+ const overlapTop = Math.max(rect.top, selection.top);
1023
+ const overlapRight = Math.min(rect.right, selection.left + selection.width);
1024
+ const overlapBottom = Math.min(rect.bottom, selection.top + selection.height);
1025
+ const overlapWidth = Math.max(0, overlapRight - overlapLeft);
1026
+ const overlapHeight = Math.max(0, overlapBottom - overlapTop);
1027
+ const overlapArea = overlapWidth * overlapHeight;
1028
+ if (overlapArea > 0) {
1029
+ const selectionArea = Math.max(1, selection.width * selection.height);
1030
+ const rectArea = Math.max(1, rect.width * rect.height);
1031
+ return 1 + overlapArea / Math.min(selectionArea, rectArea);
1032
+ }
1033
+ const rectCenterX = rect.left + rect.width / 2;
1034
+ const rectCenterY = rect.top + rect.height / 2;
1035
+ const selectionCenterX = selection.left + selection.width / 2;
1036
+ const selectionCenterY = selection.top + selection.height / 2;
1037
+ const distance = Math.hypot(
1038
+ rectCenterX - selectionCenterX,
1039
+ rectCenterY - selectionCenterY
1040
+ );
1041
+ const basis = Math.max(
1042
+ 1,
1043
+ rect.width,
1044
+ rect.height,
1045
+ selection.width,
1046
+ selection.height
1047
+ );
1048
+ return clamp(1 / (1 + distance / basis), 0.05, 0.95);
1049
+ }
991
1050
  function getFingerprintTokens(value) {
992
1051
  return value.toLowerCase().split(/[\s/|,.:;()[\]{}"'`~!?<>]+/).map((token) => token.trim()).filter((token) => token.length > 1);
993
1052
  }
@@ -2045,6 +2104,12 @@ function createStyleElement() {
2045
2104
  outline-offset: 1px;
2046
2105
  }
2047
2106
 
2107
+ @media (hover: none) and (pointer: coarse) {
2108
+ .dfwr-textarea {
2109
+ font-size: 16px;
2110
+ }
2111
+ }
2112
+
2048
2113
  .dfwr-adjust-panel {
2049
2114
  display: grid;
2050
2115
  gap: 4px;
@@ -2118,32 +2183,6 @@ function createStyleElement() {
2118
2183
  pointer-events: none;
2119
2184
  }
2120
2185
 
2121
- .dfwr-adjust-hud {
2122
- position: fixed;
2123
- z-index: 5;
2124
- display: inline-flex;
2125
- align-items: center;
2126
- min-height: 22px;
2127
- padding: 0 8px;
2128
- border: 1px solid rgba(99, 215, 199, 0.72);
2129
- border-radius: var(--df-review-radius-sm);
2130
- background: rgba(21, 25, 29, 0.92);
2131
- box-shadow:
2132
- 0 0 0 3px rgba(99, 215, 199, 0.14),
2133
- 0 8px 18px rgba(0, 0, 0, 0.26);
2134
- color: #63d7c7;
2135
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
2136
- font-size: var(--df-review-font-size-2xs);
2137
- font-weight: 800;
2138
- line-height: 1;
2139
- pointer-events: none;
2140
- white-space: nowrap;
2141
- }
2142
-
2143
- .dfwr-adjust-hud[hidden] {
2144
- display: none;
2145
- }
2146
-
2147
2186
  .dfwr-empty,
2148
2187
  .dfwr-error {
2149
2188
  margin: 0;
@@ -2506,7 +2545,6 @@ var WebReviewKitView = class {
2506
2545
  if (event.button !== 0) return;
2507
2546
  cancel(event);
2508
2547
  });
2509
- layer.addEventListener("click", cancel);
2510
2548
  return layer;
2511
2549
  }
2512
2550
  getDraftAdjustmentMetrics(draft) {
@@ -2686,14 +2724,6 @@ var WebReviewKitView = class {
2686
2724
  if (!draft.selection) return void 0;
2687
2725
  return toViewportSelection(draft.selection.viewport);
2688
2726
  }
2689
- formatDraftAdjustmentStatus(draft) {
2690
- const metrics = this.getDraftAdjustmentMetrics(draft);
2691
- return [
2692
- `x ${this.formatSignedPx(metrics.x)}`,
2693
- `y ${this.formatSignedPx(metrics.y)}`,
2694
- `scale ${this.formatSignedPx(metrics.scale)}`
2695
- ].join(" / ");
2696
- }
2697
2727
  getDraftAdjustmentMetricLines(draft) {
2698
2728
  const metrics = this.getDraftAdjustmentMetrics(draft);
2699
2729
  return [
@@ -2705,6 +2735,7 @@ var WebReviewKitView = class {
2705
2735
  }
2706
2736
  withDraftAdjustmentComment(comment, draft) {
2707
2737
  if (!this.hasDraftAdjustment(draft)) return comment;
2738
+ const trimmedComment = comment.trim();
2708
2739
  const metrics = this.getDraftAdjustmentMetrics(draft);
2709
2740
  const adjustment = [
2710
2741
  `${this.getAdjustmentLabel()}: x ${this.formatSignedPx(
@@ -2716,12 +2747,20 @@ var WebReviewKitView = class {
2716
2747
  metrics.viewportWidth
2717
2748
  )}/design ${Math.round(metrics.designWidth)})`
2718
2749
  ].join(" ");
2719
- return `${comment.trim()}
2720
- ${adjustment}`;
2750
+ return trimmedComment ? `${trimmedComment}
2751
+ ${adjustment}` : adjustment;
2721
2752
  }
2722
2753
  getStyleableDraftElement(draft, environment) {
2754
+ if (draft.previewElement && draft.previewElement.ownerDocument === environment.document && "style" in draft.previewElement) {
2755
+ return draft.previewElement;
2756
+ }
2723
2757
  if (!draft.anchor) return void 0;
2724
- const element = resolveAnchorElement(draft.anchor, environment)?.element;
2758
+ const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
2759
+ const element = resolveAnchorElement(
2760
+ draft.anchor,
2761
+ environment,
2762
+ preferredSelection
2763
+ )?.element;
2725
2764
  if (!element) return void 0;
2726
2765
  if ("style" in element) return element;
2727
2766
  return void 0;
@@ -2741,39 +2780,77 @@ ${adjustment}`;
2741
2780
  this.restoreDraftPreview();
2742
2781
  }
2743
2782
  if (!this.draftPreview) {
2744
- const computedTransform = environment.window.getComputedStyle(element).transform;
2783
+ const computedStyle = environment.window.getComputedStyle(element);
2784
+ const clone = element.cloneNode(true);
2785
+ this.removeDuplicateIds(clone);
2786
+ this.copyComputedStyle(element, clone, environment);
2787
+ this.positionDraftPreviewClone(clone, element, computedStyle);
2788
+ environment.document.body?.appendChild(clone);
2745
2789
  this.draftPreview = {
2746
2790
  element,
2747
- transform: element.style.transform,
2748
- transformOrigin: element.style.transformOrigin,
2749
- transition: element.style.transition,
2750
- willChange: element.style.willChange,
2751
- baseTransform: element.style.transform || (computedTransform && computedTransform !== "none" ? computedTransform : "")
2791
+ clone,
2792
+ visibility: element.style.visibility
2752
2793
  };
2794
+ element.style.visibility = "hidden";
2753
2795
  }
2754
2796
  const metrics = this.getDraftAdjustmentMetrics(draft);
2755
2797
  const translate = `translate(${this.toCssNumber(metrics.cssX)}px, ${this.toCssNumber(
2756
2798
  metrics.cssY
2757
2799
  )}px)`;
2758
2800
  const scale = metrics.scaleFactor === 1 ? "" : `scale(${this.toCssNumber(metrics.scaleFactor)})`;
2759
- element.style.transition = "none";
2760
- element.style.willChange = "transform";
2761
- element.style.transformOrigin = "top left";
2762
- element.style.transform = [
2763
- this.draftPreview.baseTransform,
2764
- translate,
2765
- scale
2766
- ].filter(Boolean).join(" ");
2801
+ this.draftPreview.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
2767
2802
  }
2768
2803
  restoreDraftPreview() {
2769
2804
  if (!this.draftPreview) return;
2770
- const { element, transform, transformOrigin, transition, willChange } = this.draftPreview;
2771
- element.style.transform = transform;
2772
- element.style.transformOrigin = transformOrigin;
2773
- element.style.transition = transition;
2774
- element.style.willChange = willChange;
2805
+ const { element, clone, visibility } = this.draftPreview;
2806
+ clone.remove();
2807
+ element.style.visibility = visibility;
2775
2808
  this.draftPreview = void 0;
2776
2809
  }
2810
+ positionDraftPreviewClone(clone, element, computedStyle) {
2811
+ const rect = element.getBoundingClientRect();
2812
+ clone.setAttribute("data-dfwr-adjust-preview", "true");
2813
+ clone.setAttribute("aria-hidden", "true");
2814
+ clone.style.position = "fixed";
2815
+ clone.style.left = `${this.toCssNumber(rect.left)}px`;
2816
+ clone.style.top = `${this.toCssNumber(rect.top)}px`;
2817
+ clone.style.right = "auto";
2818
+ clone.style.bottom = "auto";
2819
+ clone.style.width = `${this.toCssNumber(rect.width)}px`;
2820
+ clone.style.height = `${this.toCssNumber(rect.height)}px`;
2821
+ clone.style.maxWidth = "none";
2822
+ clone.style.maxHeight = "none";
2823
+ clone.style.margin = "0";
2824
+ clone.style.boxSizing = "border-box";
2825
+ clone.style.display = this.getDraftPreviewDisplay(computedStyle.display);
2826
+ clone.style.zIndex = "2147483646";
2827
+ clone.style.pointerEvents = "none";
2828
+ clone.style.transition = "none";
2829
+ clone.style.willChange = "transform";
2830
+ clone.style.transformOrigin = "top left";
2831
+ clone.style.transform = "none";
2832
+ }
2833
+ getDraftPreviewDisplay(display) {
2834
+ if (display === "inline" || display === "contents") return "inline-block";
2835
+ return display || "block";
2836
+ }
2837
+ copyComputedStyle(element, clone, environment) {
2838
+ const computedStyle = environment.window.getComputedStyle(element);
2839
+ for (let index = 0; index < computedStyle.length; index += 1) {
2840
+ const property = computedStyle.item(index);
2841
+ clone.style.setProperty(
2842
+ property,
2843
+ computedStyle.getPropertyValue(property),
2844
+ computedStyle.getPropertyPriority(property)
2845
+ );
2846
+ }
2847
+ }
2848
+ removeDuplicateIds(element) {
2849
+ element.removeAttribute("id");
2850
+ element.querySelectorAll("[id]").forEach((child) => {
2851
+ child.removeAttribute("id");
2852
+ });
2853
+ }
2777
2854
  toCssNumber(value) {
2778
2855
  return Math.round(value * 1e3) / 1e3;
2779
2856
  }
@@ -2935,8 +3012,8 @@ ${adjustment}`;
2935
3012
  });
2936
3013
  const saveDraft = () => {
2937
3014
  const comment = textarea.value.trim();
2938
- if (!comment) return;
2939
3015
  const currentDraft = this.state.noteDraft ?? draft;
3016
+ if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
2940
3017
  void this.config.actions.createItem({
2941
3018
  kind: "note",
2942
3019
  comment: this.withDraftAdjustmentComment(comment, currentDraft),
@@ -2946,13 +3023,8 @@ ${adjustment}`;
2946
3023
  selection: currentDraft.selection
2947
3024
  });
2948
3025
  };
2949
- const adjustmentHud = isElementDraft ? this.createAdjustmentHud(draft, environment) : void 0;
2950
- if (adjustmentHud) {
2951
- group.append(adjustmentHud);
2952
- }
2953
3026
  const adjustmentControls = isElementDraft ? this.createAdjustmentControls({
2954
3027
  draft,
2955
- hud: adjustmentHud,
2956
3028
  pin,
2957
3029
  popover,
2958
3030
  selectionHighlight,
@@ -3080,7 +3152,6 @@ ${adjustment}`;
3080
3152
  }
3081
3153
  createAdjustmentControls({
3082
3154
  draft,
3083
- hud,
3084
3155
  pin,
3085
3156
  popover,
3086
3157
  selectionHighlight,
@@ -3118,7 +3189,6 @@ ${adjustment}`;
3118
3189
  scaleStatus.textContent = scaleLine;
3119
3190
  this.syncDraftAdjustmentUi({
3120
3191
  draft: nextDraft,
3121
- hud,
3122
3192
  pin,
3123
3193
  selectionHighlight
3124
3194
  });
@@ -3179,16 +3249,8 @@ ${adjustment}`;
3179
3249
  if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
3180
3250
  return void 0;
3181
3251
  }
3182
- createAdjustmentHud(draft, environment) {
3183
- const hud = document.createElement("div");
3184
- hud.className = "dfwr-adjust-hud";
3185
- hud.setAttribute("aria-hidden", "true");
3186
- this.syncAdjustmentHud(hud, draft, environment);
3187
- return hud;
3188
- }
3189
3252
  syncDraftAdjustmentUi({
3190
3253
  draft,
3191
- hud,
3192
3254
  pin,
3193
3255
  selectionHighlight
3194
3256
  }) {
@@ -3213,26 +3275,8 @@ ${adjustment}`;
3213
3275
  selectionHighlight.style.width = `${rect.width}px`;
3214
3276
  selectionHighlight.style.height = `${rect.height}px`;
3215
3277
  }
3216
- if (hud) {
3217
- this.syncAdjustmentHud(hud, draft, environment);
3218
- }
3219
3278
  this.syncDraftPreview(draft);
3220
3279
  }
3221
- syncAdjustmentHud(hud, draft, environment) {
3222
- if (!draft.selection) return;
3223
- const rect = toHostSelection(
3224
- this.getAdjustedDraftSelection(
3225
- toViewportSelection(draft.selection.viewport),
3226
- draft
3227
- ),
3228
- environment
3229
- );
3230
- const isVisible = draft.adjustment?.isActive === true || this.hasDraftAdjustment(draft);
3231
- hud.hidden = !isVisible;
3232
- hud.textContent = this.formatDraftAdjustmentStatus(draft);
3233
- hud.style.left = `${Math.max(4, rect.left)}px`;
3234
- hud.style.top = `${Math.max(4, rect.top - 28)}px`;
3235
- }
3236
3280
  createAreaForm() {
3237
3281
  const form = document.createElement("form");
3238
3282
  form.className = "dfwr-form";
@@ -3356,12 +3400,18 @@ ${adjustment}`;
3356
3400
  save.className = "dfwr-button is-primary";
3357
3401
  save.type = "button";
3358
3402
  save.textContent = saveLabel;
3359
- save.addEventListener("click", onSave);
3403
+ save.addEventListener("click", (event) => {
3404
+ event.preventDefault();
3405
+ event.stopPropagation();
3406
+ onSave();
3407
+ });
3360
3408
  const cancel = document.createElement("button");
3361
3409
  cancel.className = "dfwr-button";
3362
3410
  cancel.type = "button";
3363
3411
  cancel.textContent = "Cancel";
3364
- cancel.addEventListener("click", () => {
3412
+ cancel.addEventListener("click", (event) => {
3413
+ event.preventDefault();
3414
+ event.stopPropagation();
3365
3415
  this.config.actions.setModeState("idle");
3366
3416
  this.config.actions.clearDrafts();
3367
3417
  this.config.actions.render();
@@ -3766,6 +3816,7 @@ function createWebReviewKit(options) {
3766
3816
  close: () => app.close(),
3767
3817
  toggle: () => app.toggle(),
3768
3818
  setMode: (mode) => app.setMode(mode),
3819
+ startElementReview: (element, comment) => app.startElementReview(element, comment),
3769
3820
  getMode: () => app.getMode(),
3770
3821
  highlightItem: (itemId) => app.highlightItem(itemId),
3771
3822
  setHiddenItemIds: (itemIds) => app.setHiddenItemIds(itemIds),
@@ -3896,6 +3947,16 @@ var WebReviewKitApp = class {
3896
3947
  this.areaDraft = void 0;
3897
3948
  this.render();
3898
3949
  }
3950
+ async startElementReview(element, comment) {
3951
+ if (!this.isOpen) {
3952
+ this.isOpen = true;
3953
+ }
3954
+ this.setModeState("element");
3955
+ this.noteDraft = void 0;
3956
+ this.areaDraft = void 0;
3957
+ this.isSelectingArea = false;
3958
+ await this.bindElementDraftToElement(element, comment);
3959
+ }
3899
3960
  getMode() {
3900
3961
  return this.mode;
3901
3962
  }
@@ -4059,13 +4120,26 @@ var WebReviewKitApp = class {
4059
4120
  const viewport = getViewportSize(environment);
4060
4121
  const nextPoint = clampPoint(point, environment);
4061
4122
  const draft = await this.withOverlayHidden(() => {
4123
+ const pointSelection = getPointSelection(nextPoint);
4124
+ const targetElement = environment.document.elementFromPoint(
4125
+ nextPoint.x,
4126
+ nextPoint.y
4127
+ );
4128
+ const previewElement = targetElement && "style" in targetElement ? targetElement : void 0;
4129
+ const targetRect = targetElement?.getBoundingClientRect();
4130
+ const clickedSelection = targetRect && targetRect.width > 0 && targetRect.height > 0 ? {
4131
+ left: targetRect.left,
4132
+ top: targetRect.top,
4133
+ width: targetRect.width,
4134
+ height: targetRect.height
4135
+ } : void 0;
4062
4136
  const anchor = getDomAnchorFromPoint(
4063
4137
  nextPoint,
4064
4138
  this.options.anchors?.attribute,
4065
4139
  environment
4066
4140
  );
4067
- const elementSelection = anchor ? getElementViewportSelection(anchor, environment) : void 0;
4068
- const selection = elementSelection ?? getPointSelection(nextPoint);
4141
+ const elementSelection = anchor ? clickedSelection ?? getElementViewportSelection(anchor, environment, pointSelection) : void 0;
4142
+ const selection = elementSelection ?? pointSelection;
4069
4143
  const markerPoint = elementSelection ? { x: selection.left, y: selection.top } : getSelectionCenter(selection);
4070
4144
  const reviewSelection = elementSelection ? {
4071
4145
  viewport: toPublicSelection(elementSelection),
@@ -4084,9 +4158,51 @@ var WebReviewKitApp = class {
4084
4158
  anchor,
4085
4159
  marker,
4086
4160
  selection: reviewSelection,
4087
- comment
4161
+ comment,
4162
+ previewElement
4163
+ };
4164
+ });
4165
+ this.noteDraft = draft;
4166
+ this.render();
4167
+ }
4168
+ async bindElementDraftToElement(element, comment) {
4169
+ const environment = this.getEnvironment();
4170
+ if (!environment || element.ownerDocument !== environment.document) return;
4171
+ const viewport = getViewportSize(environment);
4172
+ const draft = await this.withOverlayHidden(() => {
4173
+ const rect = element.getBoundingClientRect();
4174
+ if (rect.width <= 0 || rect.height <= 0) return void 0;
4175
+ const selection = {
4176
+ left: rect.left,
4177
+ top: rect.top,
4178
+ width: rect.width,
4179
+ height: rect.height
4180
+ };
4181
+ const anchor = getDomAnchorFromElement(
4182
+ element,
4183
+ this.options.anchors?.attribute,
4184
+ environment
4185
+ );
4186
+ const markerPoint = { x: selection.left, y: selection.top };
4187
+ const marker = {
4188
+ viewport: roundPoint(markerPoint),
4189
+ relative: anchor ? getRelativePoint(markerPoint, anchor, environment) : void 0
4190
+ };
4191
+ const reviewSelection = {
4192
+ viewport: toPublicSelection(selection),
4193
+ relative: anchor ? getRelativeSelection(selection, anchor, environment) : void 0
4194
+ };
4195
+ const previewElement = "style" in element ? element : void 0;
4196
+ return {
4197
+ viewport,
4198
+ anchor,
4199
+ marker,
4200
+ selection: reviewSelection,
4201
+ comment,
4202
+ previewElement
4088
4203
  };
4089
4204
  });
4205
+ if (!draft) return;
4090
4206
  this.noteDraft = draft;
4091
4207
  this.render();
4092
4208
  }
@@ -4194,6 +4310,8 @@ function createNoopController() {
4194
4310
  },
4195
4311
  setMode() {
4196
4312
  },
4313
+ async startElementReview() {
4314
+ },
4197
4315
  getMode() {
4198
4316
  return "idle";
4199
4317
  },