@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.
package/dist/index.cjs CHANGED
@@ -558,8 +558,8 @@ 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 getElementViewportSelection(anchor, environment, preferredSelection) {
562
+ const element = getAnchorElement(anchor, environment, preferredSelection);
563
563
  if (!element) return void 0;
564
564
  const rect = element.getBoundingClientRect();
565
565
  if (rect.width <= 0 || rect.height <= 0) return void 0;
@@ -598,22 +598,35 @@ function getAnchorCandidates(anchor) {
598
598
  ...anchor.candidates ?? []
599
599
  ]);
600
600
  }
601
- function resolveAnchorElement(anchor, environment) {
601
+ function resolveAnchorElement(anchor, environment, preferredSelection) {
602
602
  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
- }];
603
+ const textFingerprint = candidate.textFingerprint ?? anchor.textFingerprint;
604
+ if (!preferredSelection) {
605
+ const match = queryBestAnchorCandidate(
606
+ candidate,
607
+ textFingerprint,
608
+ environment
609
+ );
610
+ if (!match) return [];
611
+ const confidence = roundRatio(
612
+ (candidate.confidence ?? 0.5) * match.score
613
+ );
614
+ return [{
615
+ element: match.element,
616
+ candidate,
617
+ confidence
618
+ }];
619
+ }
620
+ return queryAnchorElements(candidate.selector, environment).map((element) => {
621
+ const confidence = roundRatio(
622
+ (candidate.confidence ?? 0.5) * getTextFingerprintScore(textFingerprint, getTextFingerprint(element)) * getSelectionMatchScore(element, preferredSelection)
623
+ );
624
+ return {
625
+ element,
626
+ candidate,
627
+ confidence
628
+ };
629
+ });
617
630
  });
618
631
  return matches.sort((a, b) => b.confidence - a.confidence)[0];
619
632
  }
@@ -623,8 +636,8 @@ function cssEscape(value) {
623
636
  }
624
637
  return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
625
638
  }
626
- function getAnchorElement(anchor, environment) {
627
- return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment)?.element;
639
+ function getAnchorElement(anchor, environment, preferredSelection) {
640
+ return typeof anchor === "string" ? queryAnchorElement(anchor, environment) : resolveAnchorElement(anchor, environment, preferredSelection)?.element;
628
641
  }
629
642
  function createAnchorCandidates(target, configuredAttribute) {
630
643
  const targetCandidates = [];
@@ -988,6 +1001,38 @@ function getTextFingerprintScore(expected, actual) {
988
1001
  const matches = expectedTokens.filter((token) => actualTokens.has(token));
989
1002
  return clamp(matches.length / expectedTokens.length, 0.25, 0.76);
990
1003
  }
1004
+ function getSelectionMatchScore(element, selection) {
1005
+ const rect = element.getBoundingClientRect();
1006
+ if (rect.width <= 0 || rect.height <= 0) return 0.05;
1007
+ const overlapLeft = Math.max(rect.left, selection.left);
1008
+ const overlapTop = Math.max(rect.top, selection.top);
1009
+ const overlapRight = Math.min(rect.right, selection.left + selection.width);
1010
+ const overlapBottom = Math.min(rect.bottom, selection.top + selection.height);
1011
+ const overlapWidth = Math.max(0, overlapRight - overlapLeft);
1012
+ const overlapHeight = Math.max(0, overlapBottom - overlapTop);
1013
+ const overlapArea = overlapWidth * overlapHeight;
1014
+ if (overlapArea > 0) {
1015
+ const selectionArea = Math.max(1, selection.width * selection.height);
1016
+ const rectArea = Math.max(1, rect.width * rect.height);
1017
+ return 1 + overlapArea / Math.min(selectionArea, rectArea);
1018
+ }
1019
+ const rectCenterX = rect.left + rect.width / 2;
1020
+ const rectCenterY = rect.top + rect.height / 2;
1021
+ const selectionCenterX = selection.left + selection.width / 2;
1022
+ const selectionCenterY = selection.top + selection.height / 2;
1023
+ const distance = Math.hypot(
1024
+ rectCenterX - selectionCenterX,
1025
+ rectCenterY - selectionCenterY
1026
+ );
1027
+ const basis = Math.max(
1028
+ 1,
1029
+ rect.width,
1030
+ rect.height,
1031
+ selection.width,
1032
+ selection.height
1033
+ );
1034
+ return clamp(1 / (1 + distance / basis), 0.05, 0.95);
1035
+ }
991
1036
  function getFingerprintTokens(value) {
992
1037
  return value.toLowerCase().split(/[\s/|,.:;()[\]{}"'`~!?<>]+/).map((token) => token.trim()).filter((token) => token.length > 1);
993
1038
  }
@@ -2118,32 +2163,6 @@ function createStyleElement() {
2118
2163
  pointer-events: none;
2119
2164
  }
2120
2165
 
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
2166
  .dfwr-empty,
2148
2167
  .dfwr-error {
2149
2168
  margin: 0;
@@ -2506,7 +2525,6 @@ var WebReviewKitView = class {
2506
2525
  if (event.button !== 0) return;
2507
2526
  cancel(event);
2508
2527
  });
2509
- layer.addEventListener("click", cancel);
2510
2528
  return layer;
2511
2529
  }
2512
2530
  getDraftAdjustmentMetrics(draft) {
@@ -2686,14 +2704,6 @@ var WebReviewKitView = class {
2686
2704
  if (!draft.selection) return void 0;
2687
2705
  return toViewportSelection(draft.selection.viewport);
2688
2706
  }
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
2707
  getDraftAdjustmentMetricLines(draft) {
2698
2708
  const metrics = this.getDraftAdjustmentMetrics(draft);
2699
2709
  return [
@@ -2705,6 +2715,7 @@ var WebReviewKitView = class {
2705
2715
  }
2706
2716
  withDraftAdjustmentComment(comment, draft) {
2707
2717
  if (!this.hasDraftAdjustment(draft)) return comment;
2718
+ const trimmedComment = comment.trim();
2708
2719
  const metrics = this.getDraftAdjustmentMetrics(draft);
2709
2720
  const adjustment = [
2710
2721
  `${this.getAdjustmentLabel()}: x ${this.formatSignedPx(
@@ -2716,12 +2727,20 @@ var WebReviewKitView = class {
2716
2727
  metrics.viewportWidth
2717
2728
  )}/design ${Math.round(metrics.designWidth)})`
2718
2729
  ].join(" ");
2719
- return `${comment.trim()}
2720
- ${adjustment}`;
2730
+ return trimmedComment ? `${trimmedComment}
2731
+ ${adjustment}` : adjustment;
2721
2732
  }
2722
2733
  getStyleableDraftElement(draft, environment) {
2734
+ if (draft.previewElement && draft.previewElement.ownerDocument === environment.document && "style" in draft.previewElement) {
2735
+ return draft.previewElement;
2736
+ }
2723
2737
  if (!draft.anchor) return void 0;
2724
- const element = resolveAnchorElement(draft.anchor, environment)?.element;
2738
+ const preferredSelection = draft.selection ? toViewportSelection(draft.selection.viewport) : void 0;
2739
+ const element = resolveAnchorElement(
2740
+ draft.anchor,
2741
+ environment,
2742
+ preferredSelection
2743
+ )?.element;
2725
2744
  if (!element) return void 0;
2726
2745
  if ("style" in element) return element;
2727
2746
  return void 0;
@@ -2741,39 +2760,77 @@ ${adjustment}`;
2741
2760
  this.restoreDraftPreview();
2742
2761
  }
2743
2762
  if (!this.draftPreview) {
2744
- const computedTransform = environment.window.getComputedStyle(element).transform;
2763
+ const computedStyle = environment.window.getComputedStyle(element);
2764
+ const clone = element.cloneNode(true);
2765
+ this.removeDuplicateIds(clone);
2766
+ this.copyComputedStyle(element, clone, environment);
2767
+ this.positionDraftPreviewClone(clone, element, computedStyle);
2768
+ environment.document.body?.appendChild(clone);
2745
2769
  this.draftPreview = {
2746
2770
  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 : "")
2771
+ clone,
2772
+ visibility: element.style.visibility
2752
2773
  };
2774
+ element.style.visibility = "hidden";
2753
2775
  }
2754
2776
  const metrics = this.getDraftAdjustmentMetrics(draft);
2755
2777
  const translate = `translate(${this.toCssNumber(metrics.cssX)}px, ${this.toCssNumber(
2756
2778
  metrics.cssY
2757
2779
  )}px)`;
2758
2780
  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(" ");
2781
+ this.draftPreview.clone.style.transform = [translate, scale].filter(Boolean).join(" ");
2767
2782
  }
2768
2783
  restoreDraftPreview() {
2769
2784
  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;
2785
+ const { element, clone, visibility } = this.draftPreview;
2786
+ clone.remove();
2787
+ element.style.visibility = visibility;
2775
2788
  this.draftPreview = void 0;
2776
2789
  }
2790
+ positionDraftPreviewClone(clone, element, computedStyle) {
2791
+ const rect = element.getBoundingClientRect();
2792
+ clone.setAttribute("data-dfwr-adjust-preview", "true");
2793
+ clone.setAttribute("aria-hidden", "true");
2794
+ clone.style.position = "fixed";
2795
+ clone.style.left = `${this.toCssNumber(rect.left)}px`;
2796
+ clone.style.top = `${this.toCssNumber(rect.top)}px`;
2797
+ clone.style.right = "auto";
2798
+ clone.style.bottom = "auto";
2799
+ clone.style.width = `${this.toCssNumber(rect.width)}px`;
2800
+ clone.style.height = `${this.toCssNumber(rect.height)}px`;
2801
+ clone.style.maxWidth = "none";
2802
+ clone.style.maxHeight = "none";
2803
+ clone.style.margin = "0";
2804
+ clone.style.boxSizing = "border-box";
2805
+ clone.style.display = this.getDraftPreviewDisplay(computedStyle.display);
2806
+ clone.style.zIndex = "2147483646";
2807
+ clone.style.pointerEvents = "none";
2808
+ clone.style.transition = "none";
2809
+ clone.style.willChange = "transform";
2810
+ clone.style.transformOrigin = "top left";
2811
+ clone.style.transform = "none";
2812
+ }
2813
+ getDraftPreviewDisplay(display) {
2814
+ if (display === "inline" || display === "contents") return "inline-block";
2815
+ return display || "block";
2816
+ }
2817
+ copyComputedStyle(element, clone, environment) {
2818
+ const computedStyle = environment.window.getComputedStyle(element);
2819
+ for (let index = 0; index < computedStyle.length; index += 1) {
2820
+ const property = computedStyle.item(index);
2821
+ clone.style.setProperty(
2822
+ property,
2823
+ computedStyle.getPropertyValue(property),
2824
+ computedStyle.getPropertyPriority(property)
2825
+ );
2826
+ }
2827
+ }
2828
+ removeDuplicateIds(element) {
2829
+ element.removeAttribute("id");
2830
+ element.querySelectorAll("[id]").forEach((child) => {
2831
+ child.removeAttribute("id");
2832
+ });
2833
+ }
2777
2834
  toCssNumber(value) {
2778
2835
  return Math.round(value * 1e3) / 1e3;
2779
2836
  }
@@ -2935,8 +2992,8 @@ ${adjustment}`;
2935
2992
  });
2936
2993
  const saveDraft = () => {
2937
2994
  const comment = textarea.value.trim();
2938
- if (!comment) return;
2939
2995
  const currentDraft = this.state.noteDraft ?? draft;
2996
+ if (!comment && !this.hasDraftAdjustment(currentDraft)) return;
2940
2997
  void this.config.actions.createItem({
2941
2998
  kind: "note",
2942
2999
  comment: this.withDraftAdjustmentComment(comment, currentDraft),
@@ -2946,13 +3003,8 @@ ${adjustment}`;
2946
3003
  selection: currentDraft.selection
2947
3004
  });
2948
3005
  };
2949
- const adjustmentHud = isElementDraft ? this.createAdjustmentHud(draft, environment) : void 0;
2950
- if (adjustmentHud) {
2951
- group.append(adjustmentHud);
2952
- }
2953
3006
  const adjustmentControls = isElementDraft ? this.createAdjustmentControls({
2954
3007
  draft,
2955
- hud: adjustmentHud,
2956
3008
  pin,
2957
3009
  popover,
2958
3010
  selectionHighlight,
@@ -3080,7 +3132,6 @@ ${adjustment}`;
3080
3132
  }
3081
3133
  createAdjustmentControls({
3082
3134
  draft,
3083
- hud,
3084
3135
  pin,
3085
3136
  popover,
3086
3137
  selectionHighlight,
@@ -3118,7 +3169,6 @@ ${adjustment}`;
3118
3169
  scaleStatus.textContent = scaleLine;
3119
3170
  this.syncDraftAdjustmentUi({
3120
3171
  draft: nextDraft,
3121
- hud,
3122
3172
  pin,
3123
3173
  selectionHighlight
3124
3174
  });
@@ -3179,16 +3229,8 @@ ${adjustment}`;
3179
3229
  if (event.key.toLowerCase() === "s") return { x: 0, y: 0, scale: -step };
3180
3230
  return void 0;
3181
3231
  }
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
3232
  syncDraftAdjustmentUi({
3190
3233
  draft,
3191
- hud,
3192
3234
  pin,
3193
3235
  selectionHighlight
3194
3236
  }) {
@@ -3213,26 +3255,8 @@ ${adjustment}`;
3213
3255
  selectionHighlight.style.width = `${rect.width}px`;
3214
3256
  selectionHighlight.style.height = `${rect.height}px`;
3215
3257
  }
3216
- if (hud) {
3217
- this.syncAdjustmentHud(hud, draft, environment);
3218
- }
3219
3258
  this.syncDraftPreview(draft);
3220
3259
  }
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
3260
  createAreaForm() {
3237
3261
  const form = document.createElement("form");
3238
3262
  form.className = "dfwr-form";
@@ -3356,12 +3380,18 @@ ${adjustment}`;
3356
3380
  save.className = "dfwr-button is-primary";
3357
3381
  save.type = "button";
3358
3382
  save.textContent = saveLabel;
3359
- save.addEventListener("click", onSave);
3383
+ save.addEventListener("click", (event) => {
3384
+ event.preventDefault();
3385
+ event.stopPropagation();
3386
+ onSave();
3387
+ });
3360
3388
  const cancel = document.createElement("button");
3361
3389
  cancel.className = "dfwr-button";
3362
3390
  cancel.type = "button";
3363
3391
  cancel.textContent = "Cancel";
3364
- cancel.addEventListener("click", () => {
3392
+ cancel.addEventListener("click", (event) => {
3393
+ event.preventDefault();
3394
+ event.stopPropagation();
3365
3395
  this.config.actions.setModeState("idle");
3366
3396
  this.config.actions.clearDrafts();
3367
3397
  this.config.actions.render();
@@ -4059,13 +4089,26 @@ var WebReviewKitApp = class {
4059
4089
  const viewport = getViewportSize(environment);
4060
4090
  const nextPoint = clampPoint(point, environment);
4061
4091
  const draft = await this.withOverlayHidden(() => {
4092
+ const pointSelection = getPointSelection(nextPoint);
4093
+ const targetElement = environment.document.elementFromPoint(
4094
+ nextPoint.x,
4095
+ nextPoint.y
4096
+ );
4097
+ const previewElement = targetElement && "style" in targetElement ? targetElement : void 0;
4098
+ const targetRect = targetElement?.getBoundingClientRect();
4099
+ const clickedSelection = targetRect && targetRect.width > 0 && targetRect.height > 0 ? {
4100
+ left: targetRect.left,
4101
+ top: targetRect.top,
4102
+ width: targetRect.width,
4103
+ height: targetRect.height
4104
+ } : void 0;
4062
4105
  const anchor = getDomAnchorFromPoint(
4063
4106
  nextPoint,
4064
4107
  this.options.anchors?.attribute,
4065
4108
  environment
4066
4109
  );
4067
- const elementSelection = anchor ? getElementViewportSelection(anchor, environment) : void 0;
4068
- const selection = elementSelection ?? getPointSelection(nextPoint);
4110
+ const elementSelection = anchor ? clickedSelection ?? getElementViewportSelection(anchor, environment, pointSelection) : void 0;
4111
+ const selection = elementSelection ?? pointSelection;
4069
4112
  const markerPoint = elementSelection ? { x: selection.left, y: selection.top } : getSelectionCenter(selection);
4070
4113
  const reviewSelection = elementSelection ? {
4071
4114
  viewport: toPublicSelection(elementSelection),
@@ -4084,7 +4127,8 @@ var WebReviewKitApp = class {
4084
4127
  anchor,
4085
4128
  marker,
4086
4129
  selection: reviewSelection,
4087
- comment
4130
+ comment,
4131
+ previewElement
4088
4132
  };
4089
4133
  });
4090
4134
  this.noteDraft = draft;