made-refine 0.2.6 → 0.2.8
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/cli.cjs +17 -3
- package/dist/index.d.mts +26 -9
- package/dist/index.d.ts +26 -9
- package/dist/index.js +3715 -1304
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3774 -1367
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/{utils-C7RBdUAE.d.mts → utils-BGCQaaSf.d.mts} +121 -4
- package/dist/{utils-C7RBdUAE.d.ts → utils-BGCQaaSf.d.ts} +121 -4
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +822 -105
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +810 -105
- package/dist/utils.mjs.map +1 -1
- package/package.json +1 -1
package/dist/utils.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/utils/css-value.ts
|
|
2
2
|
function parsePropertyValue(value) {
|
|
3
3
|
const raw = value.trim();
|
|
4
|
-
const match = raw.match(/^(-?\d*\.?\d+)(px|rem|em|%)?$/);
|
|
4
|
+
const match = raw.match(/^(-?\d*\.?\d+)(px|rem|em|vh|vw|%)?$/);
|
|
5
5
|
if (match) {
|
|
6
6
|
return {
|
|
7
7
|
numericValue: parseFloat(match[1]),
|
|
@@ -22,6 +22,24 @@ function formatPropertyValue(value) {
|
|
|
22
22
|
return `${value.numericValue}${value.unit}`;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// src/canvas-store.ts
|
|
26
|
+
import { useSyncExternalStore } from "react";
|
|
27
|
+
var DEFAULT = { active: false, zoom: 1, panX: 0, panY: 0 };
|
|
28
|
+
var snapshot = DEFAULT;
|
|
29
|
+
var bodyOffset = { x: 0, y: 0 };
|
|
30
|
+
function getBodyOffset() {
|
|
31
|
+
return bodyOffset;
|
|
32
|
+
}
|
|
33
|
+
function getCanvasSnapshot() {
|
|
34
|
+
return snapshot;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/utils/measurements.ts
|
|
38
|
+
function getZoomScale() {
|
|
39
|
+
const snap = getCanvasSnapshot();
|
|
40
|
+
return snap.active ? snap.zoom : 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
// src/utils.ts
|
|
26
44
|
function clamp(value, min, max) {
|
|
27
45
|
if (!Number.isFinite(value)) return min;
|
|
@@ -119,6 +137,7 @@ var ORIGINAL_STYLE_PROPS = [
|
|
|
119
137
|
"width",
|
|
120
138
|
"height",
|
|
121
139
|
"background-color",
|
|
140
|
+
"background",
|
|
122
141
|
"color",
|
|
123
142
|
"border-color",
|
|
124
143
|
"outline-color",
|
|
@@ -501,7 +520,7 @@ function hasDirectNonWhitespaceText(element) {
|
|
|
501
520
|
(node) => node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim())
|
|
502
521
|
);
|
|
503
522
|
}
|
|
504
|
-
function
|
|
523
|
+
function isTextElement2(element) {
|
|
505
524
|
const tagName = element.tagName.toLowerCase();
|
|
506
525
|
if (TEXT_ELEMENT_TAGS.has(tagName)) {
|
|
507
526
|
return true;
|
|
@@ -720,6 +739,55 @@ function parseColorValue(cssValue) {
|
|
|
720
739
|
return parseNamedColor(raw);
|
|
721
740
|
}
|
|
722
741
|
var TRANSPARENT_COLOR = { hex: "000000", alpha: 0, raw: "transparent" };
|
|
742
|
+
function isVisibleBorderSide(side) {
|
|
743
|
+
return side.style !== "none" && side.style !== "hidden" && parseFloat(side.width) > 0;
|
|
744
|
+
}
|
|
745
|
+
function hasVisibleOutline(computed) {
|
|
746
|
+
return computed.outlineStyle !== "none" && parseFloat(computed.outlineWidth) > 0;
|
|
747
|
+
}
|
|
748
|
+
function parseVisibleColor(value, fallbackCurrentColor) {
|
|
749
|
+
const raw = value.trim();
|
|
750
|
+
const lowered = raw.toLowerCase();
|
|
751
|
+
if (!raw || lowered === "none" || lowered === "transparent") {
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
const resolved = /^currentcolor$/i.test(raw) ? fallbackCurrentColor ?? raw : raw;
|
|
755
|
+
const parsed = parseColorValue(resolved);
|
|
756
|
+
if (parsed.alpha <= 0) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
return parsed;
|
|
760
|
+
}
|
|
761
|
+
function addUniqueColor(colors, color) {
|
|
762
|
+
if (!color) return;
|
|
763
|
+
colors.set(`${color.hex}:${color.alpha}`, color);
|
|
764
|
+
}
|
|
765
|
+
function isTextRenderingFormControl(element) {
|
|
766
|
+
if (element instanceof HTMLTextAreaElement) return true;
|
|
767
|
+
if (element instanceof HTMLSelectElement) return true;
|
|
768
|
+
if (element instanceof HTMLButtonElement) return true;
|
|
769
|
+
if (element instanceof HTMLInputElement) {
|
|
770
|
+
const textlessInputTypes = /* @__PURE__ */ new Set([
|
|
771
|
+
"hidden",
|
|
772
|
+
"checkbox",
|
|
773
|
+
"radio",
|
|
774
|
+
"range",
|
|
775
|
+
"color",
|
|
776
|
+
"file",
|
|
777
|
+
"image"
|
|
778
|
+
]);
|
|
779
|
+
return !textlessInputTypes.has(element.type.toLowerCase());
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
function hasRenderableTextNode(element) {
|
|
784
|
+
if (element.isContentEditable) return true;
|
|
785
|
+
if (isTextRenderingFormControl(element)) return true;
|
|
786
|
+
if (!element.textContent?.trim()) return false;
|
|
787
|
+
if (hasDirectNonWhitespaceText(element)) return true;
|
|
788
|
+
const tagName = element.tagName.toLowerCase();
|
|
789
|
+
return TEXT_ELEMENT_TAGS.has(tagName) || element.children.length === 0;
|
|
790
|
+
}
|
|
723
791
|
function getComputedBoxShadow(element) {
|
|
724
792
|
const computed = window.getComputedStyle(element);
|
|
725
793
|
const value = computed.boxShadow.trim();
|
|
@@ -733,11 +801,9 @@ function getComputedColorStyles(element) {
|
|
|
733
801
|
{ style: computed.borderBottomStyle, width: computed.borderBottomWidth, color: computed.borderBottomColor },
|
|
734
802
|
{ style: computed.borderLeftStyle, width: computed.borderLeftWidth, color: computed.borderLeftColor }
|
|
735
803
|
];
|
|
736
|
-
const visibleBorderSide = borderSides.find(
|
|
737
|
-
(side) => side.style !== "none" && side.style !== "hidden" && parseFloat(side.width) > 0
|
|
738
|
-
);
|
|
804
|
+
const visibleBorderSide = borderSides.find((side) => isVisibleBorderSide(side));
|
|
739
805
|
const hasBorder = Boolean(visibleBorderSide);
|
|
740
|
-
const hasOutline =
|
|
806
|
+
const hasOutline = hasVisibleOutline(computed);
|
|
741
807
|
return {
|
|
742
808
|
backgroundColor: parseColorValue(computed.backgroundColor),
|
|
743
809
|
color: parseColorValue(computed.color),
|
|
@@ -745,6 +811,48 @@ function getComputedColorStyles(element) {
|
|
|
745
811
|
outlineColor: hasOutline ? parseColorValue(computed.outlineColor) : TRANSPARENT_COLOR
|
|
746
812
|
};
|
|
747
813
|
}
|
|
814
|
+
function getSelectionColors(element) {
|
|
815
|
+
const uniqueColors = /* @__PURE__ */ new Map();
|
|
816
|
+
const queue = [element];
|
|
817
|
+
for (let index = 0; index < queue.length; index++) {
|
|
818
|
+
const node = queue[index];
|
|
819
|
+
const computed = window.getComputedStyle(node);
|
|
820
|
+
if (computed.display === "none") {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const isVisibilityHidden = computed.visibility === "hidden" || computed.visibility === "collapse";
|
|
824
|
+
const currentTextColor = computed.color;
|
|
825
|
+
if (!isVisibilityHidden) {
|
|
826
|
+
addUniqueColor(uniqueColors, parseVisibleColor(computed.backgroundColor));
|
|
827
|
+
if (node instanceof HTMLElement && hasRenderableTextNode(node)) {
|
|
828
|
+
addUniqueColor(uniqueColors, parseVisibleColor(currentTextColor));
|
|
829
|
+
}
|
|
830
|
+
const borderSides = [
|
|
831
|
+
{ style: computed.borderTopStyle, width: computed.borderTopWidth, color: computed.borderTopColor },
|
|
832
|
+
{ style: computed.borderRightStyle, width: computed.borderRightWidth, color: computed.borderRightColor },
|
|
833
|
+
{ style: computed.borderBottomStyle, width: computed.borderBottomWidth, color: computed.borderBottomColor },
|
|
834
|
+
{ style: computed.borderLeftStyle, width: computed.borderLeftWidth, color: computed.borderLeftColor }
|
|
835
|
+
];
|
|
836
|
+
for (const side of borderSides) {
|
|
837
|
+
if (!isVisibleBorderSide(side)) continue;
|
|
838
|
+
addUniqueColor(uniqueColors, parseVisibleColor(side.color, currentTextColor));
|
|
839
|
+
}
|
|
840
|
+
if (hasVisibleOutline(computed)) {
|
|
841
|
+
addUniqueColor(uniqueColors, parseVisibleColor(computed.outlineColor, currentTextColor));
|
|
842
|
+
}
|
|
843
|
+
if (node instanceof SVGElement) {
|
|
844
|
+
const fillColor = parseVisibleColor(computed.getPropertyValue("fill"), currentTextColor) ?? parseVisibleColor(node.getAttribute("fill") ?? "", currentTextColor);
|
|
845
|
+
const strokeColor = parseVisibleColor(computed.getPropertyValue("stroke"), currentTextColor) ?? parseVisibleColor(node.getAttribute("stroke") ?? "", currentTextColor);
|
|
846
|
+
addUniqueColor(uniqueColors, fillColor);
|
|
847
|
+
addUniqueColor(uniqueColors, strokeColor);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
for (const child of node.children) {
|
|
851
|
+
queue.push(child);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return Array.from(uniqueColors.values());
|
|
855
|
+
}
|
|
748
856
|
function getAllComputedStyles(element) {
|
|
749
857
|
const { spacing, borderRadius, flex } = getComputedStyles(element);
|
|
750
858
|
return {
|
|
@@ -792,7 +900,7 @@ function getElementInfo(element) {
|
|
|
792
900
|
classList: Array.from(element.classList),
|
|
793
901
|
isFlexContainer: isFlexContainer2,
|
|
794
902
|
isFlexItem,
|
|
795
|
-
isTextElement:
|
|
903
|
+
isTextElement: isTextElement2(element),
|
|
796
904
|
parentElement,
|
|
797
905
|
hasChildren: element.children.length > 0
|
|
798
906
|
};
|
|
@@ -832,9 +940,8 @@ function isFitSizing(element, dimension) {
|
|
|
832
940
|
return false;
|
|
833
941
|
}
|
|
834
942
|
function getDimensionDisplay(element) {
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const height = Math.round(rect.height);
|
|
943
|
+
const width = Math.round(element.offsetWidth);
|
|
944
|
+
const height = Math.round(element.offsetHeight);
|
|
838
945
|
const widthIsFit = isFitSizing(element, "width");
|
|
839
946
|
const heightIsFit = isFitSizing(element, "height");
|
|
840
947
|
return {
|
|
@@ -867,8 +974,9 @@ function calculateParentMeasurements(element, container) {
|
|
|
867
974
|
parentInnerRight = paddingBoxRight - (parseFloat(parentStyles.paddingRight) || 0);
|
|
868
975
|
parentInnerBottom = paddingBoxBottom - (parseFloat(parentStyles.paddingBottom) || 0);
|
|
869
976
|
}
|
|
977
|
+
const zoom = getZoomScale();
|
|
870
978
|
const measurements = [];
|
|
871
|
-
const topDistance = Math.round(elementRect.top - parentInnerTop);
|
|
979
|
+
const topDistance = Math.round((elementRect.top - parentInnerTop) / zoom);
|
|
872
980
|
if (topDistance > 0) {
|
|
873
981
|
const midX = elementRect.left + elementRect.width / 2;
|
|
874
982
|
measurements.push({
|
|
@@ -881,7 +989,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
881
989
|
labelPosition: { x: midX, y: (parentInnerTop + elementRect.top) / 2 }
|
|
882
990
|
});
|
|
883
991
|
}
|
|
884
|
-
const bottomDistance = Math.round(parentInnerBottom - elementRect.bottom);
|
|
992
|
+
const bottomDistance = Math.round((parentInnerBottom - elementRect.bottom) / zoom);
|
|
885
993
|
if (bottomDistance > 0) {
|
|
886
994
|
const midX = elementRect.left + elementRect.width / 2;
|
|
887
995
|
measurements.push({
|
|
@@ -894,7 +1002,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
894
1002
|
labelPosition: { x: midX, y: (elementRect.bottom + parentInnerBottom) / 2 }
|
|
895
1003
|
});
|
|
896
1004
|
}
|
|
897
|
-
const leftDistance = Math.round(elementRect.left - parentInnerLeft);
|
|
1005
|
+
const leftDistance = Math.round((elementRect.left - parentInnerLeft) / zoom);
|
|
898
1006
|
if (leftDistance > 0) {
|
|
899
1007
|
const midY = elementRect.top + elementRect.height / 2;
|
|
900
1008
|
measurements.push({
|
|
@@ -907,7 +1015,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
907
1015
|
labelPosition: { x: (parentInnerLeft + elementRect.left) / 2, y: midY }
|
|
908
1016
|
});
|
|
909
1017
|
}
|
|
910
|
-
const rightDistance = Math.round(parentInnerRight - elementRect.right);
|
|
1018
|
+
const rightDistance = Math.round((parentInnerRight - elementRect.right) / zoom);
|
|
911
1019
|
if (rightDistance > 0) {
|
|
912
1020
|
const midY = elementRect.top + elementRect.height / 2;
|
|
913
1021
|
measurements.push({
|
|
@@ -925,6 +1033,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
925
1033
|
function calculateElementMeasurements(from, to) {
|
|
926
1034
|
const fromRect = from.getBoundingClientRect();
|
|
927
1035
|
const toRect = to.getBoundingClientRect();
|
|
1036
|
+
const zoom = getZoomScale();
|
|
928
1037
|
const measurements = [];
|
|
929
1038
|
const horizontalOverlap = fromRect.left < toRect.right && fromRect.right > toRect.left;
|
|
930
1039
|
const verticalOverlap = fromRect.top < toRect.bottom && fromRect.bottom > toRect.top;
|
|
@@ -933,7 +1042,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
933
1042
|
const overlapBottom = Math.min(fromRect.bottom, toRect.bottom);
|
|
934
1043
|
const midY = (overlapTop + overlapBottom) / 2;
|
|
935
1044
|
if (fromRect.right <= toRect.left) {
|
|
936
|
-
const distance = Math.round(toRect.left - fromRect.right);
|
|
1045
|
+
const distance = Math.round((toRect.left - fromRect.right) / zoom);
|
|
937
1046
|
measurements.push({
|
|
938
1047
|
direction: "horizontal",
|
|
939
1048
|
x1: fromRect.right,
|
|
@@ -944,7 +1053,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
944
1053
|
labelPosition: { x: (fromRect.right + toRect.left) / 2, y: midY }
|
|
945
1054
|
});
|
|
946
1055
|
} else if (fromRect.left >= toRect.right) {
|
|
947
|
-
const distance = Math.round(fromRect.left - toRect.right);
|
|
1056
|
+
const distance = Math.round((fromRect.left - toRect.right) / zoom);
|
|
948
1057
|
measurements.push({
|
|
949
1058
|
direction: "horizontal",
|
|
950
1059
|
x1: toRect.right,
|
|
@@ -961,7 +1070,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
961
1070
|
const overlapRight = Math.min(fromRect.right, toRect.right);
|
|
962
1071
|
const midX = (overlapLeft + overlapRight) / 2;
|
|
963
1072
|
if (fromRect.bottom <= toRect.top) {
|
|
964
|
-
const distance = Math.round(toRect.top - fromRect.bottom);
|
|
1073
|
+
const distance = Math.round((toRect.top - fromRect.bottom) / zoom);
|
|
965
1074
|
measurements.push({
|
|
966
1075
|
direction: "vertical",
|
|
967
1076
|
x1: midX,
|
|
@@ -972,7 +1081,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
972
1081
|
labelPosition: { x: midX, y: (fromRect.bottom + toRect.top) / 2 }
|
|
973
1082
|
});
|
|
974
1083
|
} else if (fromRect.top >= toRect.bottom) {
|
|
975
|
-
const distance = Math.round(fromRect.top - toRect.bottom);
|
|
1084
|
+
const distance = Math.round((fromRect.top - toRect.bottom) / zoom);
|
|
976
1085
|
measurements.push({
|
|
977
1086
|
direction: "vertical",
|
|
978
1087
|
x1: midX,
|
|
@@ -989,7 +1098,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
989
1098
|
const fromCenterY = fromRect.top + fromRect.height / 2;
|
|
990
1099
|
const toCenterX = toRect.left + toRect.width / 2;
|
|
991
1100
|
const toCenterY = toRect.top + toRect.height / 2;
|
|
992
|
-
const hDistance = toCenterX > fromCenterX ? Math.round(toRect.left - fromRect.right) : Math.round(fromRect.left - toRect.right);
|
|
1101
|
+
const hDistance = toCenterX > fromCenterX ? Math.round((toRect.left - fromRect.right) / zoom) : Math.round((fromRect.left - toRect.right) / zoom);
|
|
993
1102
|
if (hDistance > 0) {
|
|
994
1103
|
const startX = toCenterX > fromCenterX ? fromRect.right : fromRect.left;
|
|
995
1104
|
const endX = toCenterX > fromCenterX ? toRect.left : toRect.right;
|
|
@@ -1004,7 +1113,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
1004
1113
|
labelPosition: { x: (startX + endX) / 2, y }
|
|
1005
1114
|
});
|
|
1006
1115
|
}
|
|
1007
|
-
const vDistance = toCenterY > fromCenterY ? Math.round(toRect.top - fromRect.bottom) : Math.round(fromRect.top - toRect.bottom);
|
|
1116
|
+
const vDistance = toCenterY > fromCenterY ? Math.round((toRect.top - fromRect.bottom) / zoom) : Math.round((fromRect.top - toRect.bottom) / zoom);
|
|
1008
1117
|
if (vDistance > 0) {
|
|
1009
1118
|
const x = (fromCenterX + toCenterX) / 2;
|
|
1010
1119
|
const startY = toCenterY > fromCenterY ? fromRect.bottom : fromRect.top;
|
|
@@ -1025,70 +1134,77 @@ function calculateElementMeasurements(from, to) {
|
|
|
1025
1134
|
var GUIDELINE_PROXIMITY = 80;
|
|
1026
1135
|
function calculateGuidelineMeasurements(element, guidelines, mousePosition) {
|
|
1027
1136
|
if (guidelines.length === 0) return [];
|
|
1137
|
+
const snap = getCanvasSnapshot();
|
|
1138
|
+
const zoom = snap.active ? snap.zoom : 1;
|
|
1028
1139
|
const rect = element.getBoundingClientRect();
|
|
1029
|
-
const scrollX = window.scrollX;
|
|
1030
|
-
const scrollY = window.scrollY;
|
|
1031
1140
|
const measurements = [];
|
|
1032
1141
|
for (const g of guidelines) {
|
|
1142
|
+
let viewportPos;
|
|
1143
|
+
if (snap.active) {
|
|
1144
|
+
const pan = g.orientation === "horizontal" ? snap.panY : snap.panX;
|
|
1145
|
+
const bo = g.orientation === "horizontal" ? getBodyOffset().y : getBodyOffset().x;
|
|
1146
|
+
viewportPos = bo + (g.position - bo + pan) * zoom;
|
|
1147
|
+
} else {
|
|
1148
|
+
const scroll = g.orientation === "horizontal" ? window.scrollY : window.scrollX;
|
|
1149
|
+
viewportPos = g.position - scroll;
|
|
1150
|
+
}
|
|
1033
1151
|
if (g.orientation === "horizontal") {
|
|
1034
|
-
const gy = g.position - scrollY;
|
|
1035
1152
|
const midX = rect.left + rect.width / 2;
|
|
1036
|
-
if (mousePosition && Math.abs(mousePosition.y -
|
|
1037
|
-
if (
|
|
1038
|
-
const distance = Math.round(rect.top -
|
|
1153
|
+
if (mousePosition && Math.abs(mousePosition.y - viewportPos) > GUIDELINE_PROXIMITY) continue;
|
|
1154
|
+
if (viewportPos < rect.top) {
|
|
1155
|
+
const distance = Math.round((rect.top - viewportPos) / zoom);
|
|
1039
1156
|
if (distance > 0) {
|
|
1040
1157
|
measurements.push({
|
|
1041
1158
|
direction: "vertical",
|
|
1042
1159
|
x1: midX,
|
|
1043
|
-
y1:
|
|
1160
|
+
y1: viewportPos,
|
|
1044
1161
|
x2: midX,
|
|
1045
1162
|
y2: rect.top,
|
|
1046
1163
|
distance,
|
|
1047
|
-
labelPosition: { x: midX, y: (
|
|
1164
|
+
labelPosition: { x: midX, y: (viewportPos + rect.top) / 2 }
|
|
1048
1165
|
});
|
|
1049
1166
|
}
|
|
1050
|
-
} else if (
|
|
1051
|
-
const distance = Math.round(
|
|
1167
|
+
} else if (viewportPos > rect.bottom) {
|
|
1168
|
+
const distance = Math.round((viewportPos - rect.bottom) / zoom);
|
|
1052
1169
|
if (distance > 0) {
|
|
1053
1170
|
measurements.push({
|
|
1054
1171
|
direction: "vertical",
|
|
1055
1172
|
x1: midX,
|
|
1056
1173
|
y1: rect.bottom,
|
|
1057
1174
|
x2: midX,
|
|
1058
|
-
y2:
|
|
1175
|
+
y2: viewportPos,
|
|
1059
1176
|
distance,
|
|
1060
|
-
labelPosition: { x: midX, y: (rect.bottom +
|
|
1177
|
+
labelPosition: { x: midX, y: (rect.bottom + viewportPos) / 2 }
|
|
1061
1178
|
});
|
|
1062
1179
|
}
|
|
1063
1180
|
}
|
|
1064
1181
|
} else {
|
|
1065
|
-
const gx = g.position - scrollX;
|
|
1066
1182
|
const midY = rect.top + rect.height / 2;
|
|
1067
|
-
if (mousePosition && Math.abs(mousePosition.x -
|
|
1068
|
-
if (
|
|
1069
|
-
const distance = Math.round(rect.left -
|
|
1183
|
+
if (mousePosition && Math.abs(mousePosition.x - viewportPos) > GUIDELINE_PROXIMITY) continue;
|
|
1184
|
+
if (viewportPos < rect.left) {
|
|
1185
|
+
const distance = Math.round((rect.left - viewportPos) / zoom);
|
|
1070
1186
|
if (distance > 0) {
|
|
1071
1187
|
measurements.push({
|
|
1072
1188
|
direction: "horizontal",
|
|
1073
|
-
x1:
|
|
1189
|
+
x1: viewportPos,
|
|
1074
1190
|
y1: midY,
|
|
1075
1191
|
x2: rect.left,
|
|
1076
1192
|
y2: midY,
|
|
1077
1193
|
distance,
|
|
1078
|
-
labelPosition: { x: (
|
|
1194
|
+
labelPosition: { x: (viewportPos + rect.left) / 2, y: midY }
|
|
1079
1195
|
});
|
|
1080
1196
|
}
|
|
1081
|
-
} else if (
|
|
1082
|
-
const distance = Math.round(
|
|
1197
|
+
} else if (viewportPos > rect.right) {
|
|
1198
|
+
const distance = Math.round((viewportPos - rect.right) / zoom);
|
|
1083
1199
|
if (distance > 0) {
|
|
1084
1200
|
measurements.push({
|
|
1085
1201
|
direction: "horizontal",
|
|
1086
1202
|
x1: rect.right,
|
|
1087
1203
|
y1: midY,
|
|
1088
|
-
x2:
|
|
1204
|
+
x2: viewportPos,
|
|
1089
1205
|
y2: midY,
|
|
1090
1206
|
distance,
|
|
1091
|
-
labelPosition: { x: (rect.right +
|
|
1207
|
+
labelPosition: { x: (rect.right + viewportPos) / 2, y: midY }
|
|
1092
1208
|
});
|
|
1093
1209
|
}
|
|
1094
1210
|
}
|
|
@@ -1104,6 +1220,10 @@ function getFlexDirection(element) {
|
|
|
1104
1220
|
const computed = window.getComputedStyle(element);
|
|
1105
1221
|
return computed.flexDirection;
|
|
1106
1222
|
}
|
|
1223
|
+
function isInFlowChild(el) {
|
|
1224
|
+
const cs = window.getComputedStyle(el);
|
|
1225
|
+
return cs.display !== "none" && cs.position !== "absolute" && cs.position !== "fixed";
|
|
1226
|
+
}
|
|
1107
1227
|
function detectChildrenDirection(container, exclude) {
|
|
1108
1228
|
const computed = window.getComputedStyle(container);
|
|
1109
1229
|
if (computed.display === "flex" || computed.display === "inline-flex") {
|
|
@@ -1116,8 +1236,7 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1116
1236
|
const visible = [];
|
|
1117
1237
|
for (const c of container.children) {
|
|
1118
1238
|
if (!(c instanceof HTMLElement) || c === exclude) continue;
|
|
1119
|
-
|
|
1120
|
-
if (cs.display === "none" || cs.position === "absolute" || cs.position === "fixed") continue;
|
|
1239
|
+
if (!isInFlowChild(c)) continue;
|
|
1121
1240
|
visible.push(c);
|
|
1122
1241
|
if (visible.length === 2) break;
|
|
1123
1242
|
}
|
|
@@ -1130,6 +1249,29 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1130
1249
|
}
|
|
1131
1250
|
return { axis: "vertical", reversed: second.bottom < first.top };
|
|
1132
1251
|
}
|
|
1252
|
+
function computeIntendedIndex(parent, draggedElement) {
|
|
1253
|
+
const { axis } = detectChildrenDirection(parent, draggedElement);
|
|
1254
|
+
const isHorizontal = axis === "horizontal";
|
|
1255
|
+
const draggedRect = draggedElement.getBoundingClientRect();
|
|
1256
|
+
const intendedCenter = isHorizontal ? draggedRect.left + draggedRect.width / 2 : draggedRect.top + draggedRect.height / 2;
|
|
1257
|
+
const siblings = [];
|
|
1258
|
+
for (const c of parent.children) {
|
|
1259
|
+
if (!(c instanceof HTMLElement) || c === draggedElement) continue;
|
|
1260
|
+
if (!isInFlowChild(c)) continue;
|
|
1261
|
+
siblings.push(c);
|
|
1262
|
+
}
|
|
1263
|
+
if (siblings.length === 0) {
|
|
1264
|
+
return { index: 0, siblingBefore: null, siblingAfter: null };
|
|
1265
|
+
}
|
|
1266
|
+
for (let i = 0; i < siblings.length; i++) {
|
|
1267
|
+
const rect = siblings[i].getBoundingClientRect();
|
|
1268
|
+
const midpoint = isHorizontal ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
|
|
1269
|
+
if (intendedCenter < midpoint) {
|
|
1270
|
+
return { index: i, siblingBefore: i > 0 ? siblings[i - 1] : null, siblingAfter: siblings[i] };
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return { index: siblings.length, siblingBefore: siblings[siblings.length - 1], siblingAfter: null };
|
|
1274
|
+
}
|
|
1133
1275
|
function htmlChildren(el) {
|
|
1134
1276
|
return Array.from(el.children).filter(
|
|
1135
1277
|
(child) => child instanceof HTMLElement
|
|
@@ -1288,6 +1430,22 @@ function findContainerAtPoint(x, y, exclude, preferredParent) {
|
|
|
1288
1430
|
}
|
|
1289
1431
|
return findContainerViaTraversal(x, y, exclude);
|
|
1290
1432
|
}
|
|
1433
|
+
function findLayoutContainerAtPoint(x, y, exclude, preferredParent) {
|
|
1434
|
+
const host = document.querySelector("[data-direct-edit-host]");
|
|
1435
|
+
if (host) host.style.display = "none";
|
|
1436
|
+
const elements = document.elementsFromPoint(x, y);
|
|
1437
|
+
if (host) host.style.display = "";
|
|
1438
|
+
for (const el of elements) {
|
|
1439
|
+
if (skipElement(el, exclude)) continue;
|
|
1440
|
+
if (isLayoutContainer(el)) return el;
|
|
1441
|
+
}
|
|
1442
|
+
if (preferredParent && isLayoutContainer(preferredParent)) {
|
|
1443
|
+
for (const el of elements) {
|
|
1444
|
+
if (el === preferredParent) return preferredParent;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1291
1449
|
function calculateDropPosition(container, pointerX, pointerY, draggedElement) {
|
|
1292
1450
|
const { axis, reversed: isReversed } = detectChildrenDirection(container, draggedElement);
|
|
1293
1451
|
const isHorizontal = axis === "horizontal";
|
|
@@ -1682,7 +1840,18 @@ function getReactComponentStack(element) {
|
|
|
1682
1840
|
return getRenderStack(fiber);
|
|
1683
1841
|
}
|
|
1684
1842
|
function getElementDisplayName(element) {
|
|
1685
|
-
|
|
1843
|
+
const tag = element.tagName.toLowerCase();
|
|
1844
|
+
if (element.id) return `${tag}#${element.id}`;
|
|
1845
|
+
const firstClass = Array.from(element.classList).find((c) => c && !c.startsWith("direct-edit"));
|
|
1846
|
+
if (firstClass) return `${tag}.${firstClass}`;
|
|
1847
|
+
return tag;
|
|
1848
|
+
}
|
|
1849
|
+
function getChildBriefInfo(element) {
|
|
1850
|
+
const name = getElementDisplayName(element);
|
|
1851
|
+
const raw = ((element.innerText || element.textContent) ?? "").replace(/\s+/g, " ").trim();
|
|
1852
|
+
const textPreview = raw.length > 40 ? `${raw.slice(0, 37)}...` : raw;
|
|
1853
|
+
const source = getElementSource(element);
|
|
1854
|
+
return { name, textPreview, source };
|
|
1686
1855
|
}
|
|
1687
1856
|
var STABLE_ATTRIBUTES = ["data-testid", "data-qa", "data-cy", "aria-label", "role"];
|
|
1688
1857
|
var MAX_SELECTOR_DEPTH = 24;
|
|
@@ -1970,26 +2139,28 @@ function parseDomSource(element) {
|
|
|
1970
2139
|
}
|
|
1971
2140
|
return { file, line, column };
|
|
1972
2141
|
}
|
|
1973
|
-
function
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
};
|
|
1988
|
-
break;
|
|
1989
|
-
}
|
|
1990
|
-
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
2142
|
+
function getElementSource(element) {
|
|
2143
|
+
const domSource = parseDomSource(element);
|
|
2144
|
+
if (domSource) return domSource;
|
|
2145
|
+
const seenFibers = /* @__PURE__ */ new Set();
|
|
2146
|
+
let fiber = getFiberForElement(element);
|
|
2147
|
+
while (fiber && !seenFibers.has(fiber)) {
|
|
2148
|
+
seenFibers.add(fiber);
|
|
2149
|
+
const fiberSource = getSourceFromFiber(fiber);
|
|
2150
|
+
if (fiberSource?.fileName) {
|
|
2151
|
+
return {
|
|
2152
|
+
file: fiberSource.fileName,
|
|
2153
|
+
line: fiberSource.lineNumber,
|
|
2154
|
+
column: fiberSource.columnNumber
|
|
2155
|
+
};
|
|
1991
2156
|
}
|
|
2157
|
+
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
1992
2158
|
}
|
|
2159
|
+
return null;
|
|
2160
|
+
}
|
|
2161
|
+
function getElementLocator(element) {
|
|
2162
|
+
const elementInfo = getElementInfo(element);
|
|
2163
|
+
const domSource = getElementSource(element);
|
|
1993
2164
|
return {
|
|
1994
2165
|
reactStack: getReactComponentStack(element),
|
|
1995
2166
|
domSelector: buildDomSelector(element),
|
|
@@ -2008,7 +2179,7 @@ function getLocatorHeader(locator) {
|
|
|
2008
2179
|
const formattedSource = locator.domSource?.file ? formatSourceLocation(locator.domSource.file, locator.domSource.line, locator.domSource.column) : primaryFrame?.file ? formatSourceLocation(primaryFrame.file, primaryFrame.line, primaryFrame.column) : null;
|
|
2009
2180
|
return { componentLabel, formattedSource };
|
|
2010
2181
|
}
|
|
2011
|
-
function buildLocatorContextLines(locator) {
|
|
2182
|
+
function buildLocatorContextLines(locator, options) {
|
|
2012
2183
|
const lines = [];
|
|
2013
2184
|
const { componentLabel, formattedSource } = getLocatorHeader(locator);
|
|
2014
2185
|
const target = (locator.targetHtml || locator.domContextHtml || "").trim();
|
|
@@ -2021,7 +2192,7 @@ function buildLocatorContextLines(locator) {
|
|
|
2021
2192
|
lines.push("target:");
|
|
2022
2193
|
lines.push(target);
|
|
2023
2194
|
}
|
|
2024
|
-
if (context && context !== target) {
|
|
2195
|
+
if (!options?.skipContext && context && context !== target) {
|
|
2025
2196
|
lines.push("context:");
|
|
2026
2197
|
lines.push(context);
|
|
2027
2198
|
}
|
|
@@ -2175,6 +2346,29 @@ function buildEditExport(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
|
|
|
2175
2346
|
}
|
|
2176
2347
|
return lines.join("\n");
|
|
2177
2348
|
}
|
|
2349
|
+
function buildEditExportWithOptions(locator, pendingStyles, textEdit, options) {
|
|
2350
|
+
const changes = [];
|
|
2351
|
+
const collapsedStyles = collapseExportShorthands(pendingStyles);
|
|
2352
|
+
for (const [property, value] of Object.entries(collapsedStyles)) {
|
|
2353
|
+
const tailwindClass = stylesToTailwind({ [property]: value });
|
|
2354
|
+
changes.push({ property, value, tailwind: tailwindClass });
|
|
2355
|
+
}
|
|
2356
|
+
const lines = buildLocatorContextLines(locator, options);
|
|
2357
|
+
lines.push("");
|
|
2358
|
+
if (changes.length > 0) {
|
|
2359
|
+
lines.push("edits:");
|
|
2360
|
+
for (const change of changes) {
|
|
2361
|
+
const tailwind = change.tailwind ? ` (${change.tailwind})` : "";
|
|
2362
|
+
lines.push(`${change.property}: ${change.value}${tailwind}`);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
if (textEdit) {
|
|
2366
|
+
lines.push("text content changed:");
|
|
2367
|
+
lines.push(`from: "${textEdit.originalText}"`);
|
|
2368
|
+
lines.push(`to: "${textEdit.newText}"`);
|
|
2369
|
+
}
|
|
2370
|
+
return lines.join("\n");
|
|
2371
|
+
}
|
|
2178
2372
|
function buildCommentExport(locator, commentText, replies) {
|
|
2179
2373
|
const lines = buildLocatorContextLines(locator);
|
|
2180
2374
|
lines.push("");
|
|
@@ -2186,55 +2380,554 @@ function buildCommentExport(locator, commentText, replies) {
|
|
|
2186
2380
|
}
|
|
2187
2381
|
return lines.join("\n");
|
|
2188
2382
|
}
|
|
2189
|
-
function
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2383
|
+
function normalizeSelector(selector) {
|
|
2384
|
+
const value = selector?.trim();
|
|
2385
|
+
return value && value.length > 0 ? value : null;
|
|
2386
|
+
}
|
|
2387
|
+
function normalizeName(name) {
|
|
2388
|
+
return name?.trim().toLowerCase() || "element";
|
|
2194
2389
|
}
|
|
2195
|
-
function
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2390
|
+
function buildAnchorRef(name, selector, source) {
|
|
2391
|
+
return {
|
|
2392
|
+
name: name?.trim() || "element",
|
|
2393
|
+
selector: normalizeSelector(selector),
|
|
2394
|
+
source: source?.file ? source : null
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
function anchorKey(anchor) {
|
|
2398
|
+
if (!anchor) return "none";
|
|
2399
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2400
|
+
if (selector) return `selector:${selector}`;
|
|
2401
|
+
if (anchor.source?.file) {
|
|
2402
|
+
return `source:${anchor.source.file}:${anchor.source.line ?? 0}:${anchor.source.column ?? 0}`;
|
|
2403
|
+
}
|
|
2404
|
+
return `name:${normalizeName(anchor.name)}`;
|
|
2405
|
+
}
|
|
2406
|
+
function anchorsEqual(a, b) {
|
|
2407
|
+
if (!a && !b) return true;
|
|
2408
|
+
if (!a || !b) return false;
|
|
2409
|
+
const aSelector = normalizeSelector(a.selector);
|
|
2410
|
+
const bSelector = normalizeSelector(b.selector);
|
|
2411
|
+
if (aSelector && bSelector) return aSelector === bSelector;
|
|
2412
|
+
if (a.source?.file && b.source?.file) {
|
|
2413
|
+
return a.source.file === b.source.file && (a.source.line ?? null) === (b.source.line ?? null) && (a.source.column ?? null) === (b.source.column ?? null);
|
|
2414
|
+
}
|
|
2415
|
+
return normalizeName(a.name) === normalizeName(b.name);
|
|
2416
|
+
}
|
|
2417
|
+
function formatAnchorRef(anchor, fallback = "(none)") {
|
|
2418
|
+
if (!anchor) return fallback;
|
|
2419
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2420
|
+
if (selector) return selector;
|
|
2421
|
+
if (anchor.source?.file) return `<${anchor.name}> @ ${formatSourceLocation(anchor.source.file, anchor.source.line, anchor.source.column)}`;
|
|
2422
|
+
return `<${anchor.name}>`;
|
|
2423
|
+
}
|
|
2424
|
+
function buildPlacementRef(before, after) {
|
|
2425
|
+
if (before && after) {
|
|
2426
|
+
return {
|
|
2427
|
+
before,
|
|
2428
|
+
after,
|
|
2429
|
+
description: `between after ${formatAnchorRef(before)} and before ${formatAnchorRef(after)}`
|
|
2430
|
+
};
|
|
2200
2431
|
}
|
|
2201
|
-
|
|
2432
|
+
if (before) {
|
|
2433
|
+
return {
|
|
2434
|
+
before,
|
|
2435
|
+
after: null,
|
|
2436
|
+
description: `after ${formatAnchorRef(before)}`
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
if (after) {
|
|
2440
|
+
return {
|
|
2441
|
+
before: null,
|
|
2442
|
+
after,
|
|
2443
|
+
description: `before ${formatAnchorRef(after)}`
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
return {
|
|
2447
|
+
before: null,
|
|
2448
|
+
after: null,
|
|
2449
|
+
description: "as the only child"
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
function buildPlacementFromMove(beforeName, beforeSelector, beforeSource, afterName, afterSelector, afterSource) {
|
|
2453
|
+
const before = beforeName || beforeSelector || beforeSource?.file ? buildAnchorRef(beforeName, beforeSelector, beforeSource) : null;
|
|
2454
|
+
const after = afterName || afterSelector || afterSource?.file ? buildAnchorRef(afterName, afterSelector, afterSource) : null;
|
|
2455
|
+
return buildPlacementRef(before, after);
|
|
2456
|
+
}
|
|
2457
|
+
function toRoundedVisualDelta(move) {
|
|
2458
|
+
const delta = move.visualDelta ?? move.positionDelta;
|
|
2459
|
+
if (!delta) return void 0;
|
|
2460
|
+
const rounded = { x: Math.round(delta.x), y: Math.round(delta.y) };
|
|
2461
|
+
return rounded.x === 0 && rounded.y === 0 ? void 0 : rounded;
|
|
2462
|
+
}
|
|
2463
|
+
function hasVisualIntent(move) {
|
|
2464
|
+
return Boolean(toRoundedVisualDelta(move));
|
|
2465
|
+
}
|
|
2466
|
+
function hasStructuralChange(move) {
|
|
2467
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2468
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2469
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2470
|
+
move.fromSiblingBefore,
|
|
2471
|
+
move.fromSiblingBeforeSelector,
|
|
2472
|
+
move.fromSiblingBeforeSource,
|
|
2473
|
+
move.fromSiblingAfter,
|
|
2474
|
+
move.fromSiblingAfterSelector,
|
|
2475
|
+
move.fromSiblingAfterSource
|
|
2476
|
+
);
|
|
2477
|
+
const toPlacement = buildPlacementFromMove(
|
|
2478
|
+
move.toSiblingBefore,
|
|
2479
|
+
move.toSiblingBeforeSelector,
|
|
2480
|
+
move.toSiblingBeforeSource,
|
|
2481
|
+
move.toSiblingAfter,
|
|
2482
|
+
move.toSiblingAfterSelector,
|
|
2483
|
+
move.toSiblingAfterSource
|
|
2484
|
+
);
|
|
2485
|
+
if (!anchorsEqual(fromParent, toParent)) return true;
|
|
2486
|
+
if (!anchorsEqual(fromPlacement.before, toPlacement.before)) return true;
|
|
2487
|
+
if (!anchorsEqual(fromPlacement.after, toPlacement.after)) return true;
|
|
2488
|
+
if (typeof move.fromIndex === "number" && typeof move.toIndex === "number" && move.fromIndex !== move.toIndex) return true;
|
|
2489
|
+
return false;
|
|
2202
2490
|
}
|
|
2203
|
-
function
|
|
2204
|
-
|
|
2205
|
-
|
|
2491
|
+
function isStructuredLayoutContainer(layout) {
|
|
2492
|
+
return layout === "flex" || layout === "grid";
|
|
2493
|
+
}
|
|
2494
|
+
function isExistingFlexWorkflow(move) {
|
|
2495
|
+
const structuralChange = hasStructuralChange(move);
|
|
2496
|
+
if (!structuralChange) return false;
|
|
2497
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2498
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2499
|
+
const sameParent = anchorsEqual(fromParent, toParent);
|
|
2500
|
+
const fromLayout = move.fromParentLayout;
|
|
2501
|
+
const toLayout = move.toParentLayout;
|
|
2502
|
+
if (sameParent) {
|
|
2503
|
+
return Boolean(move.mode === "reorder" && (isStructuredLayoutContainer(toLayout) || isStructuredLayoutContainer(fromLayout)));
|
|
2504
|
+
}
|
|
2505
|
+
return Boolean(isStructuredLayoutContainer(fromLayout) && isStructuredLayoutContainer(toLayout));
|
|
2506
|
+
}
|
|
2507
|
+
function classifyMove(move) {
|
|
2508
|
+
const structuralChange = hasStructuralChange(move);
|
|
2509
|
+
const visualIntent = hasVisualIntent(move);
|
|
2510
|
+
if (!structuralChange && !visualIntent) return "noop";
|
|
2511
|
+
if (isExistingFlexWorkflow(move)) return "existing_layout_move";
|
|
2512
|
+
if (move.mode === "free" || move.mode === "position") return "layout_refactor";
|
|
2513
|
+
if (!structuralChange && visualIntent) return "layout_refactor";
|
|
2514
|
+
return "layout_refactor";
|
|
2515
|
+
}
|
|
2516
|
+
function buildNumericClusters(values, tolerance) {
|
|
2517
|
+
if (values.length === 0) return [];
|
|
2518
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2519
|
+
const clusters = [{ center: sorted[0], values: [sorted[0]] }];
|
|
2520
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
2521
|
+
const value = sorted[i];
|
|
2522
|
+
const current = clusters[clusters.length - 1];
|
|
2523
|
+
if (Math.abs(value - current.center) <= tolerance) {
|
|
2524
|
+
current.values.push(value);
|
|
2525
|
+
current.center = current.values.reduce((sum, n) => sum + n, 0) / current.values.length;
|
|
2526
|
+
} else {
|
|
2527
|
+
clusters.push({ center: value, values: [value] });
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
return clusters;
|
|
2206
2531
|
}
|
|
2207
|
-
function
|
|
2208
|
-
if (
|
|
2209
|
-
|
|
2532
|
+
function inferFlexDirection(sameRowCount, sameColumnCount, visualDelta) {
|
|
2533
|
+
if (sameRowCount > sameColumnCount) {
|
|
2534
|
+
return { direction: "row", reason: "Subject aligns with neighboring anchors on the same row." };
|
|
2535
|
+
}
|
|
2536
|
+
if (sameColumnCount > sameRowCount) {
|
|
2537
|
+
return { direction: "column", reason: "Subject aligns with neighboring anchors on the same column." };
|
|
2538
|
+
}
|
|
2539
|
+
if (sameRowCount > 0) {
|
|
2540
|
+
return { direction: "row", reason: "Detected row alignment in final geometry." };
|
|
2541
|
+
}
|
|
2542
|
+
if (sameColumnCount > 0) {
|
|
2543
|
+
return { direction: "column", reason: "Detected column alignment in final geometry." };
|
|
2544
|
+
}
|
|
2545
|
+
const horizontalDominant = Math.abs(visualDelta?.x ?? 0) >= Math.abs(visualDelta?.y ?? 0);
|
|
2546
|
+
return {
|
|
2547
|
+
direction: horizontalDominant ? "row" : "column",
|
|
2548
|
+
reason: "Fell back to movement axis because anchor alignment was ambiguous."
|
|
2549
|
+
};
|
|
2210
2550
|
}
|
|
2211
|
-
function
|
|
2212
|
-
|
|
2551
|
+
function inferLayoutPrescription(edit, operation, reasons) {
|
|
2552
|
+
const parent = edit.element.parentElement;
|
|
2553
|
+
if (!parent || !edit.element.isConnected) {
|
|
2554
|
+
return {
|
|
2555
|
+
recommendedSystem: "flex",
|
|
2556
|
+
intentPatterns: ["no_geometry_context"],
|
|
2557
|
+
refactorSteps: [
|
|
2558
|
+
`Reparent ${formatAnchorRef(operation.subject)} under ${formatAnchorRef(operation.to.parent)} at ${operation.to.placement.description}.`
|
|
2559
|
+
],
|
|
2560
|
+
styleSteps: [
|
|
2561
|
+
`Convert ${formatAnchorRef(operation.to.parent)} to flex and set a clear primary axis for this relationship.`,
|
|
2562
|
+
"Use `gap` for spacing and keep positioning static."
|
|
2563
|
+
],
|
|
2564
|
+
itemSteps: [
|
|
2565
|
+
"Remove any inline `left/top/transform` move artifacts from moved elements."
|
|
2566
|
+
]
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
const children = Array.from(parent.children).filter(
|
|
2570
|
+
(node) => node instanceof HTMLElement && isInFlowChild(node) && !node.hasAttribute("data-direct-edit")
|
|
2571
|
+
);
|
|
2572
|
+
const childSnapshots = children.map((child) => {
|
|
2573
|
+
const rect = child.getBoundingClientRect();
|
|
2574
|
+
const locator = getElementLocator(child);
|
|
2575
|
+
const anchor = buildAnchorRef(getElementDisplayName(child), locator.domSelector, locator.domSource);
|
|
2576
|
+
return {
|
|
2577
|
+
child,
|
|
2578
|
+
rect,
|
|
2579
|
+
centerX: rect.left + rect.width / 2,
|
|
2580
|
+
centerY: rect.top + rect.height / 2,
|
|
2581
|
+
anchor,
|
|
2582
|
+
anchorLabel: formatAnchorRef(anchor)
|
|
2583
|
+
};
|
|
2584
|
+
});
|
|
2585
|
+
const subjectSnapshot = childSnapshots.find((snapshot2) => snapshot2.child === edit.element);
|
|
2586
|
+
const subjectRect = edit.element.getBoundingClientRect();
|
|
2587
|
+
const subjectCenterX = subjectRect.left + subjectRect.width / 2;
|
|
2588
|
+
const subjectCenterY = subjectRect.top + subjectRect.height / 2;
|
|
2589
|
+
const rowTolerance = Math.max(8, subjectRect.height * 0.35);
|
|
2590
|
+
const colTolerance = Math.max(8, subjectRect.width * 0.35);
|
|
2591
|
+
const sameRowWith = [];
|
|
2592
|
+
const sameColumnWith = [];
|
|
2593
|
+
const sameRowNodes = [];
|
|
2594
|
+
let aboveAnchor = null;
|
|
2595
|
+
let belowAnchor = null;
|
|
2596
|
+
let bestAboveDistance = Number.POSITIVE_INFINITY;
|
|
2597
|
+
let bestBelowDistance = Number.POSITIVE_INFINITY;
|
|
2598
|
+
for (const node of childSnapshots) {
|
|
2599
|
+
if (node.child === edit.element) continue;
|
|
2600
|
+
if (Math.abs(node.centerY - subjectCenterY) <= rowTolerance) {
|
|
2601
|
+
sameRowWith.push(node.anchorLabel);
|
|
2602
|
+
sameRowNodes.push(node);
|
|
2603
|
+
}
|
|
2604
|
+
if (Math.abs(node.centerX - subjectCenterX) <= colTolerance) {
|
|
2605
|
+
sameColumnWith.push(node.anchorLabel);
|
|
2606
|
+
}
|
|
2607
|
+
const yDelta = node.centerY - subjectCenterY;
|
|
2608
|
+
if (yDelta < 0 && Math.abs(yDelta) < bestAboveDistance) {
|
|
2609
|
+
bestAboveDistance = Math.abs(yDelta);
|
|
2610
|
+
aboveAnchor = node.anchorLabel;
|
|
2611
|
+
}
|
|
2612
|
+
if (yDelta > 0 && yDelta < bestBelowDistance) {
|
|
2613
|
+
bestBelowDistance = yDelta;
|
|
2614
|
+
belowAnchor = node.anchorLabel;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
const rowCenters = childSnapshots.map(({ centerY }) => centerY);
|
|
2618
|
+
const colCenters = childSnapshots.map(({ centerX }) => centerX);
|
|
2619
|
+
const rowClusters = buildNumericClusters(rowCenters, rowTolerance);
|
|
2620
|
+
const colClusters = buildNumericClusters(colCenters, colTolerance);
|
|
2621
|
+
const denseRowClusters = rowClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2622
|
+
const denseColClusters = colClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2623
|
+
const isTwoDimensional = childSnapshots.length >= 4 && denseRowClusters >= 2 && denseColClusters >= 2;
|
|
2624
|
+
const recommendedSystem = isTwoDimensional ? "grid" : "flex";
|
|
2625
|
+
const intentPatterns = [];
|
|
2626
|
+
if (sameRowWith.length > 0) intentPatterns.push(`same_row_with:${sameRowWith.slice(0, 3).join(", ")}`);
|
|
2627
|
+
if (sameColumnWith.length > 0) intentPatterns.push(`same_column_with:${sameColumnWith.slice(0, 3).join(", ")}`);
|
|
2628
|
+
if (aboveAnchor) intentPatterns.push(`below:${aboveAnchor}`);
|
|
2629
|
+
if (belowAnchor) intentPatterns.push(`above:${belowAnchor}`);
|
|
2630
|
+
if (sameRowWith.length === 0 && sameColumnWith.length === 0) intentPatterns.push("separate_cluster");
|
|
2631
|
+
const visualDelta = operation.visualDelta;
|
|
2632
|
+
const flexDirectionInfo = inferFlexDirection(sameRowWith.length, sameColumnWith.length, visualDelta);
|
|
2633
|
+
const flexDirection = flexDirectionInfo.direction;
|
|
2634
|
+
if (recommendedSystem === "grid") {
|
|
2635
|
+
reasons.push("Detected multiple dense row and column clusters; a 2D layout system is likely intentional.");
|
|
2636
|
+
return {
|
|
2637
|
+
recommendedSystem: "grid",
|
|
2638
|
+
intentPatterns,
|
|
2639
|
+
refactorSteps: [
|
|
2640
|
+
`Create/ensure a shared container around ${formatAnchorRef(operation.subject)} and related anchors under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2641
|
+
`Reorder/reparent elements to satisfy placement ${operation.to.placement.description}.`
|
|
2642
|
+
],
|
|
2643
|
+
styleSteps: [
|
|
2644
|
+
`Set ${formatAnchorRef(operation.to.parent)} to grid with explicit template rows/columns for the final layout.`,
|
|
2645
|
+
"Use `gap` for consistent spacing and keep placement structural."
|
|
2646
|
+
],
|
|
2647
|
+
itemSteps: [
|
|
2648
|
+
`Set item alignment on ${formatAnchorRef(operation.subject)} with grid self-alignment (\`justify-self\`/\`align-self\`).`
|
|
2649
|
+
]
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
reasons.push(`${flexDirectionInfo.reason} Use a 1D flex layout instead of literal drag replay.`);
|
|
2653
|
+
let hasStackedCluster = false;
|
|
2654
|
+
const stackedAnchorLabels = /* @__PURE__ */ new Set();
|
|
2655
|
+
if (flexDirection === "row" && subjectSnapshot) {
|
|
2656
|
+
for (const rowPeer of sameRowNodes) {
|
|
2657
|
+
for (const node of childSnapshots) {
|
|
2658
|
+
if (node.child === edit.element || node.child === rowPeer.child) continue;
|
|
2659
|
+
const sameColumnAsPeer = Math.abs(node.centerX - rowPeer.centerX) <= colTolerance;
|
|
2660
|
+
const verticallySeparated = Math.abs(node.centerY - rowPeer.centerY) > rowTolerance;
|
|
2661
|
+
if (sameColumnAsPeer && verticallySeparated) {
|
|
2662
|
+
hasStackedCluster = true;
|
|
2663
|
+
stackedAnchorLabels.add(rowPeer.anchorLabel);
|
|
2664
|
+
stackedAnchorLabels.add(node.anchorLabel);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
const hasBelowCluster = childSnapshots.some((node) => node.child !== edit.element && node.centerY - subjectCenterY > rowTolerance * 1.5 && Math.abs(node.centerY - subjectCenterY) > Math.abs(node.centerX - subjectCenterX));
|
|
2670
|
+
const refactorSteps = [
|
|
2671
|
+
`Ensure ${formatAnchorRef(operation.subject)} and referenced neighbors share a common container under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2672
|
+
`Reparent/reorder nodes so ${formatAnchorRef(operation.subject)} lands ${operation.to.placement.description}.`
|
|
2673
|
+
];
|
|
2674
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2675
|
+
const clusterSample = Array.from(stackedAnchorLabels).slice(0, 3).join(", ");
|
|
2676
|
+
refactorSteps.push(`Create a left-side content wrapper for vertically stacked items (${clusterSample}), and keep ${formatAnchorRef(operation.subject)} as the opposite-side sibling.`);
|
|
2677
|
+
}
|
|
2678
|
+
if (hasBelowCluster) {
|
|
2679
|
+
refactorSteps.push("Keep lower content sections in a separate block below the horizontal header row; do not force them into the same row.");
|
|
2680
|
+
}
|
|
2681
|
+
const styleSteps = [
|
|
2682
|
+
`Set ${formatAnchorRef(operation.to.parent)} to flex with direction ${flexDirection}.`,
|
|
2683
|
+
flexDirection === "row" ? "Use `justify-content: space-between` and `align-items: flex-start` when the moved element should sit on the opposite edge." : "Use `justify-content` / `align-items` to establish top-bottom alignment.",
|
|
2684
|
+
"Use `gap` for spacing between siblings."
|
|
2685
|
+
];
|
|
2686
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2687
|
+
styleSteps.push("Set the content wrapper to `display: flex` with `flex-direction: column` and an appropriate vertical gap.");
|
|
2688
|
+
}
|
|
2689
|
+
return {
|
|
2690
|
+
recommendedSystem: "flex",
|
|
2691
|
+
intentPatterns,
|
|
2692
|
+
refactorSteps,
|
|
2693
|
+
styleSteps,
|
|
2694
|
+
itemSteps: [
|
|
2695
|
+
`Apply item-level alignment (\`align-self\` / flex-basis) only when needed for ${formatAnchorRef(operation.subject)}.`,
|
|
2696
|
+
"Do not use absolute positioning, top/left offsets, transforms, or margin hacks to simulate movement."
|
|
2697
|
+
]
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
function buildMoveEntries(edits) {
|
|
2701
|
+
const entries = [];
|
|
2702
|
+
let noopMoveCount = 0;
|
|
2703
|
+
for (const edit of edits) {
|
|
2704
|
+
const move = edit.move;
|
|
2705
|
+
if (!move) continue;
|
|
2706
|
+
const subject = buildAnchorRef(
|
|
2707
|
+
getElementDisplayName(edit.element) || edit.locator.tagName,
|
|
2708
|
+
edit.locator.domSelector,
|
|
2709
|
+
edit.locator.domSource
|
|
2710
|
+
);
|
|
2711
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2712
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2713
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2714
|
+
move.fromSiblingBefore,
|
|
2715
|
+
move.fromSiblingBeforeSelector,
|
|
2716
|
+
move.fromSiblingBeforeSource,
|
|
2717
|
+
move.fromSiblingAfter,
|
|
2718
|
+
move.fromSiblingAfterSelector,
|
|
2719
|
+
move.fromSiblingAfterSource
|
|
2720
|
+
);
|
|
2721
|
+
const toPlacement = buildPlacementFromMove(
|
|
2722
|
+
move.toSiblingBefore,
|
|
2723
|
+
move.toSiblingBeforeSelector,
|
|
2724
|
+
move.toSiblingBeforeSource,
|
|
2725
|
+
move.toSiblingAfter,
|
|
2726
|
+
move.toSiblingAfterSelector,
|
|
2727
|
+
move.toSiblingAfterSource
|
|
2728
|
+
);
|
|
2729
|
+
const reasons = [];
|
|
2730
|
+
const classification = classifyMove(move);
|
|
2731
|
+
if (classification === "noop") {
|
|
2732
|
+
noopMoveCount++;
|
|
2733
|
+
continue;
|
|
2734
|
+
}
|
|
2735
|
+
const interactionMode = move.mode ?? "free";
|
|
2736
|
+
const visualDelta = toRoundedVisualDelta(move);
|
|
2737
|
+
if (visualDelta) {
|
|
2738
|
+
reasons.push(`Non-zero visual delta detected (${visualDelta.x}px, ${visualDelta.y}px).`);
|
|
2739
|
+
}
|
|
2740
|
+
const structuralChange = hasStructuralChange(move);
|
|
2741
|
+
if (structuralChange) reasons.push("Anchor placement changed between source and target.");
|
|
2742
|
+
else reasons.push("No anchor placement change; treating movement as layout intent translation.");
|
|
2743
|
+
const operationBase = {
|
|
2744
|
+
classification,
|
|
2745
|
+
interactionMode,
|
|
2746
|
+
subject,
|
|
2747
|
+
from: { parent: fromParent, placement: fromPlacement },
|
|
2748
|
+
to: { parent: toParent, placement: toPlacement },
|
|
2749
|
+
...visualDelta ? { visualDelta } : {},
|
|
2750
|
+
confidence: classification === "existing_layout_move" ? "high" : structuralChange ? "medium" : "high",
|
|
2751
|
+
reasons
|
|
2752
|
+
};
|
|
2753
|
+
if (classification === "layout_refactor") {
|
|
2754
|
+
operationBase.layoutPrescription = inferLayoutPrescription(edit, operationBase, reasons);
|
|
2755
|
+
}
|
|
2756
|
+
const sortSource = subject.source?.file ? `${subject.source.file}:${subject.source.line ?? 0}:${subject.source.column ?? 0}` : "";
|
|
2757
|
+
const sortKey = [
|
|
2758
|
+
sortSource,
|
|
2759
|
+
anchorKey(subject),
|
|
2760
|
+
anchorKey(toParent),
|
|
2761
|
+
toPlacement.description
|
|
2762
|
+
].join("|");
|
|
2763
|
+
entries.push({ edit, operation: operationBase, sortKey });
|
|
2764
|
+
}
|
|
2765
|
+
entries.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
2766
|
+
return { entries, noopMoveCount };
|
|
2767
|
+
}
|
|
2768
|
+
function buildMovePlanContext(edits, _domContext) {
|
|
2769
|
+
const { entries, noopMoveCount } = buildMoveEntries(edits);
|
|
2770
|
+
if (entries.length === 0) {
|
|
2771
|
+
return {
|
|
2772
|
+
movePlan: null,
|
|
2773
|
+
intentsByEdit: /* @__PURE__ */ new Map(),
|
|
2774
|
+
noopMoveCount
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
const operations = [];
|
|
2778
|
+
const intentsByEdit = /* @__PURE__ */ new Map();
|
|
2779
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2780
|
+
const operationId = `op-${i + 1}`;
|
|
2781
|
+
const operation = { operationId, ...entries[i].operation };
|
|
2782
|
+
operations.push(operation);
|
|
2783
|
+
intentsByEdit.set(entries[i].edit, operation);
|
|
2784
|
+
}
|
|
2785
|
+
const affectedContainerMap = /* @__PURE__ */ new Map();
|
|
2786
|
+
for (const operation of operations) {
|
|
2787
|
+
affectedContainerMap.set(anchorKey(operation.from.parent), operation.from.parent);
|
|
2788
|
+
affectedContainerMap.set(anchorKey(operation.to.parent), operation.to.parent);
|
|
2789
|
+
}
|
|
2790
|
+
const orderingConstraints = operations.filter((op) => op.classification === "existing_layout_move").map((op) => `${op.operationId}: place ${formatAnchorRef(op.subject)} ${op.to.placement.description} in ${formatAnchorRef(op.to.parent)}.`);
|
|
2791
|
+
const notes = [];
|
|
2792
|
+
if (noopMoveCount > 0) notes.push(`Excluded ${noopMoveCount} no-op move(s).`);
|
|
2793
|
+
if (operations.some((op) => op.classification === "layout_refactor")) {
|
|
2794
|
+
notes.push("Layout refactor operations include best-practice flex/grid prescriptions.");
|
|
2795
|
+
}
|
|
2796
|
+
return {
|
|
2797
|
+
movePlan: {
|
|
2798
|
+
operations,
|
|
2799
|
+
affectedContainers: Array.from(affectedContainerMap.values()),
|
|
2800
|
+
orderingConstraints,
|
|
2801
|
+
notes
|
|
2802
|
+
},
|
|
2803
|
+
intentsByEdit,
|
|
2804
|
+
noopMoveCount
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
function buildMovePlan(edits, domContext) {
|
|
2808
|
+
const context = buildMovePlanContext(edits, domContext);
|
|
2809
|
+
return context.movePlan ?? {
|
|
2810
|
+
operations: [],
|
|
2811
|
+
affectedContainers: [],
|
|
2812
|
+
orderingConstraints: [],
|
|
2813
|
+
notes: context.noopMoveCount > 0 ? [`Excluded ${context.noopMoveCount} no-op move(s).`] : []
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function getMoveIntentForEdit(edit, context) {
|
|
2817
|
+
if (!edit.move) return null;
|
|
2818
|
+
if (context?.intentsByEdit.has(edit)) return context.intentsByEdit.get(edit) ?? null;
|
|
2819
|
+
const singleContext = buildMovePlanContext([edit]);
|
|
2820
|
+
return singleContext.intentsByEdit.get(edit) ?? null;
|
|
2821
|
+
}
|
|
2822
|
+
function buildMoveInstructionFromIntent(intent) {
|
|
2823
|
+
if (intent.classification === "existing_layout_move") {
|
|
2824
|
+
return `Apply as a structural move in code: place ${formatAnchorRef(intent.subject)} ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`;
|
|
2825
|
+
}
|
|
2826
|
+
const system = intent.layoutPrescription?.recommendedSystem ?? "flex";
|
|
2827
|
+
return `Treat this as a ${system} layout refactor. Implement the listed structure/style steps in source code instead of drag replay.`;
|
|
2828
|
+
}
|
|
2829
|
+
function formatMoveType(classification) {
|
|
2830
|
+
return classification === "existing_layout_move" ? "structural_move" : "layout_refactor";
|
|
2831
|
+
}
|
|
2832
|
+
function buildMoveExportLines(intent) {
|
|
2833
|
+
const moveType = formatMoveType(intent.classification);
|
|
2834
|
+
const implementationSteps = [];
|
|
2835
|
+
if (intent.classification === "existing_layout_move") {
|
|
2836
|
+
implementationSteps.push(`Reorder/reparent ${formatAnchorRef(intent.subject)} to ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`);
|
|
2837
|
+
} else {
|
|
2838
|
+
const prescription = intent.layoutPrescription;
|
|
2839
|
+
if (prescription) {
|
|
2840
|
+
implementationSteps.push(...prescription.refactorSteps);
|
|
2841
|
+
implementationSteps.push(...prescription.styleSteps);
|
|
2842
|
+
implementationSteps.push(...prescription.itemSteps);
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
const lines = [
|
|
2213
2846
|
"moved:",
|
|
2214
|
-
`
|
|
2215
|
-
`
|
|
2216
|
-
`
|
|
2217
|
-
`
|
|
2218
|
-
`
|
|
2219
|
-
`
|
|
2220
|
-
`
|
|
2221
|
-
`to_parent_selector: ${formatMoveSelector(move.toParentSelector, "(unknown)")}`,
|
|
2222
|
-
`to_before_selector: ${formatMoveSelector(move.toSiblingBeforeSelector, "(none)")}`,
|
|
2223
|
-
`to_after_selector: ${formatMoveSelector(move.toSiblingAfterSelector, "(none)")}`,
|
|
2224
|
-
`to_parent_source: ${formatMoveSource(move.toParentSource, "(unknown)")}`,
|
|
2225
|
-
`to_before_source: ${formatMoveSource(move.toSiblingBeforeSource, "(none)")}`,
|
|
2226
|
-
`to_after_source: ${formatMoveSource(move.toSiblingAfterSource, "(none)")}`
|
|
2847
|
+
`id: ${intent.operationId}`,
|
|
2848
|
+
`type: ${moveType}`,
|
|
2849
|
+
`subject: ${formatAnchorRef(intent.subject, "(unknown)")}`,
|
|
2850
|
+
`parent: ${formatAnchorRef(intent.to.parent, "(unknown)")}`,
|
|
2851
|
+
`current_anchor: ${intent.from.placement.description}`,
|
|
2852
|
+
`target_anchor: ${intent.to.placement.description}`,
|
|
2853
|
+
...intent.visualDelta ? [`visual_hint: ${intent.visualDelta.x}px horizontal, ${intent.visualDelta.y}px vertical`] : []
|
|
2227
2854
|
];
|
|
2855
|
+
if (intent.layoutPrescription) {
|
|
2856
|
+
lines.push(`recommended_layout: ${intent.layoutPrescription.recommendedSystem}`);
|
|
2857
|
+
}
|
|
2858
|
+
lines.push("implementation_steps:");
|
|
2859
|
+
for (const step of implementationSteps) {
|
|
2860
|
+
lines.push(` - ${step}`);
|
|
2861
|
+
}
|
|
2862
|
+
lines.push("guardrails:");
|
|
2863
|
+
lines.push(" - Do not simulate movement with absolute positioning, left/top offsets, transform, or margin hacks.");
|
|
2864
|
+
lines.push(`instruction: ${buildMoveInstructionFromIntent(intent)}`);
|
|
2865
|
+
return lines;
|
|
2866
|
+
}
|
|
2867
|
+
function getExportContentProfile(edits, comments, movePlanOrContext) {
|
|
2868
|
+
const moveOpCount = movePlanOrContext ? "operations" in movePlanOrContext ? movePlanOrContext.operations.length : movePlanOrContext.movePlan?.operations.length ?? 0 : buildMovePlanContext(edits).movePlan?.operations.length ?? 0;
|
|
2869
|
+
return {
|
|
2870
|
+
hasCssEdits: edits.some((e) => Object.keys(e.pendingStyles).length > 0),
|
|
2871
|
+
hasTextEdits: edits.some((e) => e.textEdit != null),
|
|
2872
|
+
hasMoves: moveOpCount > 0,
|
|
2873
|
+
hasComments: comments.length > 0
|
|
2874
|
+
};
|
|
2228
2875
|
}
|
|
2229
|
-
function
|
|
2876
|
+
function buildExportInstruction(profile) {
|
|
2877
|
+
const { hasCssEdits, hasTextEdits, hasMoves, hasComments } = profile;
|
|
2878
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves && !hasComments) return "";
|
|
2879
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves) {
|
|
2880
|
+
return hasComments ? "Address this feedback on the UI. Use the provided source location and selector to find each element in the codebase." : "";
|
|
2881
|
+
}
|
|
2882
|
+
const parts = [];
|
|
2883
|
+
if (hasCssEdits) parts.push("Apply the CSS changes to the targeted elements using the project's existing styling approach (Tailwind, CSS modules, etc.).");
|
|
2884
|
+
if (hasTextEdits) parts.push("Update the text content as specified.");
|
|
2885
|
+
if (hasMoves) parts.push("Implement the move plan below directly in source code. For `structural_move`, reorder/reparent elements using the target anchors. For `layout_refactor`, apply the listed flex/grid refactor steps. Do NOT simulate movement with absolute positioning, left/top offsets, transform, or margin hacks.");
|
|
2886
|
+
if (hasComments) parts.push("Address the comments on the relevant elements.");
|
|
2887
|
+
return `${parts.join(" ")} Use the provided source locations, selectors, and context HTML to locate each element in the codebase.`;
|
|
2888
|
+
}
|
|
2889
|
+
function buildSessionExport(edits, comments = [], options) {
|
|
2230
2890
|
const blocks = [];
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2891
|
+
const planContext = options?.movePlanContext ?? buildMovePlanContext(edits);
|
|
2892
|
+
const movePlan = planContext.movePlan;
|
|
2893
|
+
const includeMovePlanHeader = options?.includeMovePlanHeader !== false;
|
|
2894
|
+
if (includeMovePlanHeader && movePlan && movePlan.operations.length > 0) {
|
|
2895
|
+
const planLines = [
|
|
2896
|
+
"=== LAYOUT MOVE PLAN ===",
|
|
2897
|
+
`operations: ${movePlan.operations.length}`
|
|
2898
|
+
];
|
|
2899
|
+
if (movePlan.affectedContainers.length > 0) {
|
|
2900
|
+
planLines.push("containers:");
|
|
2901
|
+
for (const container of movePlan.affectedContainers) {
|
|
2902
|
+
planLines.push(` - ${formatAnchorRef(container, "(unknown)")}`);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
if (movePlan.orderingConstraints.length > 0) {
|
|
2906
|
+
planLines.push("structural_constraints:");
|
|
2907
|
+
for (const constraint of movePlan.orderingConstraints) {
|
|
2908
|
+
planLines.push(` - ${constraint}`);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
if (movePlan.notes.length > 0) {
|
|
2912
|
+
planLines.push("plan_notes:");
|
|
2913
|
+
for (const note of movePlan.notes) {
|
|
2914
|
+
planLines.push(` - ${note}`);
|
|
2915
|
+
}
|
|
2236
2916
|
}
|
|
2237
|
-
blocks.push(
|
|
2917
|
+
blocks.push(planLines.join("\n"));
|
|
2918
|
+
}
|
|
2919
|
+
for (const edit of edits) {
|
|
2920
|
+
const moveIntent = getMoveIntentForEdit(edit, planContext);
|
|
2921
|
+
const hasMove = Boolean(moveIntent);
|
|
2922
|
+
const hasStyleOrText = Object.keys(edit.pendingStyles).length > 0 || edit.textEdit != null;
|
|
2923
|
+
if (!hasMove && !hasStyleOrText) continue;
|
|
2924
|
+
const block = hasMove ? buildEditExportWithOptions(edit.locator, edit.pendingStyles, edit.textEdit, { skipContext: true }) : buildEditExport(edit.locator, edit.pendingStyles, edit.textEdit);
|
|
2925
|
+
let moveBlock = "";
|
|
2926
|
+
if (moveIntent) {
|
|
2927
|
+
moveBlock = `
|
|
2928
|
+
${buildMoveExportLines(moveIntent).join("\n")}`;
|
|
2929
|
+
}
|
|
2930
|
+
blocks.push(block + moveBlock);
|
|
2238
2931
|
}
|
|
2239
2932
|
for (const comment of comments) {
|
|
2240
2933
|
blocks.push(buildCommentExport(comment.locator, comment.text, comment.replies));
|
|
@@ -2248,6 +2941,9 @@ export {
|
|
|
2248
2941
|
buildCommentExport,
|
|
2249
2942
|
buildEditExport,
|
|
2250
2943
|
buildElementContext,
|
|
2944
|
+
buildExportInstruction,
|
|
2945
|
+
buildMovePlan,
|
|
2946
|
+
buildMovePlanContext,
|
|
2251
2947
|
buildSessionExport,
|
|
2252
2948
|
calculateDropPosition,
|
|
2253
2949
|
calculateElementMeasurements,
|
|
@@ -2259,17 +2955,20 @@ export {
|
|
|
2259
2955
|
colorPropertyToCSSMap,
|
|
2260
2956
|
colorToTailwind,
|
|
2261
2957
|
computeHoverHighlight,
|
|
2958
|
+
computeIntendedIndex,
|
|
2262
2959
|
detectChildrenDirection,
|
|
2263
2960
|
detectSizingMode,
|
|
2264
2961
|
elementFromPointWithoutOverlays,
|
|
2265
2962
|
ensureDirectTextSpanAtPoint,
|
|
2266
2963
|
findChildAtPoint,
|
|
2267
2964
|
findContainerAtPoint,
|
|
2965
|
+
findLayoutContainerAtPoint,
|
|
2268
2966
|
findTextOwnerAtPoint,
|
|
2269
2967
|
findTextOwnerByRangeScan,
|
|
2270
2968
|
flexPropertyToCSSMap,
|
|
2271
2969
|
formatPropertyValue,
|
|
2272
2970
|
getAllComputedStyles,
|
|
2971
|
+
getChildBriefInfo,
|
|
2273
2972
|
getComputedBorderStyles,
|
|
2274
2973
|
getComputedBoxShadow,
|
|
2275
2974
|
getComputedColorStyles,
|
|
@@ -2280,12 +2979,18 @@ export {
|
|
|
2280
2979
|
getElementDisplayName,
|
|
2281
2980
|
getElementInfo,
|
|
2282
2981
|
getElementLocator,
|
|
2982
|
+
getElementSource,
|
|
2983
|
+
getExportContentProfile,
|
|
2283
2984
|
getFlexDirection,
|
|
2985
|
+
getMoveIntentForEdit,
|
|
2284
2986
|
getOriginalInlineStyles,
|
|
2987
|
+
getSelectionColors,
|
|
2285
2988
|
getSizingValue,
|
|
2286
2989
|
isFlexContainer,
|
|
2990
|
+
isInFlowChild,
|
|
2287
2991
|
isInputFocused,
|
|
2288
|
-
|
|
2992
|
+
isLayoutContainer,
|
|
2993
|
+
isTextElement2 as isTextElement,
|
|
2289
2994
|
parseColorValue,
|
|
2290
2995
|
parsePropertyValue,
|
|
2291
2996
|
propertyToCSSMap,
|