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.js
CHANGED
|
@@ -26,6 +26,9 @@ __export(utils_exports, {
|
|
|
26
26
|
buildCommentExport: () => buildCommentExport,
|
|
27
27
|
buildEditExport: () => buildEditExport,
|
|
28
28
|
buildElementContext: () => buildElementContext,
|
|
29
|
+
buildExportInstruction: () => buildExportInstruction,
|
|
30
|
+
buildMovePlan: () => buildMovePlan,
|
|
31
|
+
buildMovePlanContext: () => buildMovePlanContext,
|
|
29
32
|
buildSessionExport: () => buildSessionExport,
|
|
30
33
|
calculateDropPosition: () => calculateDropPosition,
|
|
31
34
|
calculateElementMeasurements: () => calculateElementMeasurements,
|
|
@@ -37,17 +40,20 @@ __export(utils_exports, {
|
|
|
37
40
|
colorPropertyToCSSMap: () => colorPropertyToCSSMap,
|
|
38
41
|
colorToTailwind: () => colorToTailwind,
|
|
39
42
|
computeHoverHighlight: () => computeHoverHighlight,
|
|
43
|
+
computeIntendedIndex: () => computeIntendedIndex,
|
|
40
44
|
detectChildrenDirection: () => detectChildrenDirection,
|
|
41
45
|
detectSizingMode: () => detectSizingMode,
|
|
42
46
|
elementFromPointWithoutOverlays: () => elementFromPointWithoutOverlays,
|
|
43
47
|
ensureDirectTextSpanAtPoint: () => ensureDirectTextSpanAtPoint,
|
|
44
48
|
findChildAtPoint: () => findChildAtPoint,
|
|
45
49
|
findContainerAtPoint: () => findContainerAtPoint,
|
|
50
|
+
findLayoutContainerAtPoint: () => findLayoutContainerAtPoint,
|
|
46
51
|
findTextOwnerAtPoint: () => findTextOwnerAtPoint,
|
|
47
52
|
findTextOwnerByRangeScan: () => findTextOwnerByRangeScan,
|
|
48
53
|
flexPropertyToCSSMap: () => flexPropertyToCSSMap,
|
|
49
54
|
formatPropertyValue: () => formatPropertyValue,
|
|
50
55
|
getAllComputedStyles: () => getAllComputedStyles,
|
|
56
|
+
getChildBriefInfo: () => getChildBriefInfo,
|
|
51
57
|
getComputedBorderStyles: () => getComputedBorderStyles,
|
|
52
58
|
getComputedBoxShadow: () => getComputedBoxShadow,
|
|
53
59
|
getComputedColorStyles: () => getComputedColorStyles,
|
|
@@ -58,12 +64,18 @@ __export(utils_exports, {
|
|
|
58
64
|
getElementDisplayName: () => getElementDisplayName,
|
|
59
65
|
getElementInfo: () => getElementInfo,
|
|
60
66
|
getElementLocator: () => getElementLocator,
|
|
67
|
+
getElementSource: () => getElementSource,
|
|
68
|
+
getExportContentProfile: () => getExportContentProfile,
|
|
61
69
|
getFlexDirection: () => getFlexDirection,
|
|
70
|
+
getMoveIntentForEdit: () => getMoveIntentForEdit,
|
|
62
71
|
getOriginalInlineStyles: () => getOriginalInlineStyles,
|
|
72
|
+
getSelectionColors: () => getSelectionColors,
|
|
63
73
|
getSizingValue: () => getSizingValue,
|
|
64
74
|
isFlexContainer: () => isFlexContainer,
|
|
75
|
+
isInFlowChild: () => isInFlowChild,
|
|
65
76
|
isInputFocused: () => isInputFocused,
|
|
66
|
-
|
|
77
|
+
isLayoutContainer: () => isLayoutContainer,
|
|
78
|
+
isTextElement: () => isTextElement2,
|
|
67
79
|
parseColorValue: () => parseColorValue,
|
|
68
80
|
parsePropertyValue: () => parsePropertyValue,
|
|
69
81
|
propertyToCSSMap: () => propertyToCSSMap,
|
|
@@ -79,7 +91,7 @@ module.exports = __toCommonJS(utils_exports);
|
|
|
79
91
|
// src/utils/css-value.ts
|
|
80
92
|
function parsePropertyValue(value) {
|
|
81
93
|
const raw = value.trim();
|
|
82
|
-
const match = raw.match(/^(-?\d*\.?\d+)(px|rem|em|%)?$/);
|
|
94
|
+
const match = raw.match(/^(-?\d*\.?\d+)(px|rem|em|vh|vw|%)?$/);
|
|
83
95
|
if (match) {
|
|
84
96
|
return {
|
|
85
97
|
numericValue: parseFloat(match[1]),
|
|
@@ -100,6 +112,24 @@ function formatPropertyValue(value) {
|
|
|
100
112
|
return `${value.numericValue}${value.unit}`;
|
|
101
113
|
}
|
|
102
114
|
|
|
115
|
+
// src/canvas-store.ts
|
|
116
|
+
var import_react = require("react");
|
|
117
|
+
var DEFAULT = { active: false, zoom: 1, panX: 0, panY: 0 };
|
|
118
|
+
var snapshot = DEFAULT;
|
|
119
|
+
var bodyOffset = { x: 0, y: 0 };
|
|
120
|
+
function getBodyOffset() {
|
|
121
|
+
return bodyOffset;
|
|
122
|
+
}
|
|
123
|
+
function getCanvasSnapshot() {
|
|
124
|
+
return snapshot;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/utils/measurements.ts
|
|
128
|
+
function getZoomScale() {
|
|
129
|
+
const snap = getCanvasSnapshot();
|
|
130
|
+
return snap.active ? snap.zoom : 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
103
133
|
// src/utils.ts
|
|
104
134
|
function clamp(value, min, max) {
|
|
105
135
|
if (!Number.isFinite(value)) return min;
|
|
@@ -197,6 +227,7 @@ var ORIGINAL_STYLE_PROPS = [
|
|
|
197
227
|
"width",
|
|
198
228
|
"height",
|
|
199
229
|
"background-color",
|
|
230
|
+
"background",
|
|
200
231
|
"color",
|
|
201
232
|
"border-color",
|
|
202
233
|
"outline-color",
|
|
@@ -579,7 +610,7 @@ function hasDirectNonWhitespaceText(element) {
|
|
|
579
610
|
(node) => node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim())
|
|
580
611
|
);
|
|
581
612
|
}
|
|
582
|
-
function
|
|
613
|
+
function isTextElement2(element) {
|
|
583
614
|
const tagName = element.tagName.toLowerCase();
|
|
584
615
|
if (TEXT_ELEMENT_TAGS.has(tagName)) {
|
|
585
616
|
return true;
|
|
@@ -798,6 +829,55 @@ function parseColorValue(cssValue) {
|
|
|
798
829
|
return parseNamedColor(raw);
|
|
799
830
|
}
|
|
800
831
|
var TRANSPARENT_COLOR = { hex: "000000", alpha: 0, raw: "transparent" };
|
|
832
|
+
function isVisibleBorderSide(side) {
|
|
833
|
+
return side.style !== "none" && side.style !== "hidden" && parseFloat(side.width) > 0;
|
|
834
|
+
}
|
|
835
|
+
function hasVisibleOutline(computed) {
|
|
836
|
+
return computed.outlineStyle !== "none" && parseFloat(computed.outlineWidth) > 0;
|
|
837
|
+
}
|
|
838
|
+
function parseVisibleColor(value, fallbackCurrentColor) {
|
|
839
|
+
const raw = value.trim();
|
|
840
|
+
const lowered = raw.toLowerCase();
|
|
841
|
+
if (!raw || lowered === "none" || lowered === "transparent") {
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
const resolved = /^currentcolor$/i.test(raw) ? fallbackCurrentColor ?? raw : raw;
|
|
845
|
+
const parsed = parseColorValue(resolved);
|
|
846
|
+
if (parsed.alpha <= 0) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
return parsed;
|
|
850
|
+
}
|
|
851
|
+
function addUniqueColor(colors, color) {
|
|
852
|
+
if (!color) return;
|
|
853
|
+
colors.set(`${color.hex}:${color.alpha}`, color);
|
|
854
|
+
}
|
|
855
|
+
function isTextRenderingFormControl(element) {
|
|
856
|
+
if (element instanceof HTMLTextAreaElement) return true;
|
|
857
|
+
if (element instanceof HTMLSelectElement) return true;
|
|
858
|
+
if (element instanceof HTMLButtonElement) return true;
|
|
859
|
+
if (element instanceof HTMLInputElement) {
|
|
860
|
+
const textlessInputTypes = /* @__PURE__ */ new Set([
|
|
861
|
+
"hidden",
|
|
862
|
+
"checkbox",
|
|
863
|
+
"radio",
|
|
864
|
+
"range",
|
|
865
|
+
"color",
|
|
866
|
+
"file",
|
|
867
|
+
"image"
|
|
868
|
+
]);
|
|
869
|
+
return !textlessInputTypes.has(element.type.toLowerCase());
|
|
870
|
+
}
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
function hasRenderableTextNode(element) {
|
|
874
|
+
if (element.isContentEditable) return true;
|
|
875
|
+
if (isTextRenderingFormControl(element)) return true;
|
|
876
|
+
if (!element.textContent?.trim()) return false;
|
|
877
|
+
if (hasDirectNonWhitespaceText(element)) return true;
|
|
878
|
+
const tagName = element.tagName.toLowerCase();
|
|
879
|
+
return TEXT_ELEMENT_TAGS.has(tagName) || element.children.length === 0;
|
|
880
|
+
}
|
|
801
881
|
function getComputedBoxShadow(element) {
|
|
802
882
|
const computed = window.getComputedStyle(element);
|
|
803
883
|
const value = computed.boxShadow.trim();
|
|
@@ -811,11 +891,9 @@ function getComputedColorStyles(element) {
|
|
|
811
891
|
{ style: computed.borderBottomStyle, width: computed.borderBottomWidth, color: computed.borderBottomColor },
|
|
812
892
|
{ style: computed.borderLeftStyle, width: computed.borderLeftWidth, color: computed.borderLeftColor }
|
|
813
893
|
];
|
|
814
|
-
const visibleBorderSide = borderSides.find(
|
|
815
|
-
(side) => side.style !== "none" && side.style !== "hidden" && parseFloat(side.width) > 0
|
|
816
|
-
);
|
|
894
|
+
const visibleBorderSide = borderSides.find((side) => isVisibleBorderSide(side));
|
|
817
895
|
const hasBorder = Boolean(visibleBorderSide);
|
|
818
|
-
const hasOutline =
|
|
896
|
+
const hasOutline = hasVisibleOutline(computed);
|
|
819
897
|
return {
|
|
820
898
|
backgroundColor: parseColorValue(computed.backgroundColor),
|
|
821
899
|
color: parseColorValue(computed.color),
|
|
@@ -823,6 +901,48 @@ function getComputedColorStyles(element) {
|
|
|
823
901
|
outlineColor: hasOutline ? parseColorValue(computed.outlineColor) : TRANSPARENT_COLOR
|
|
824
902
|
};
|
|
825
903
|
}
|
|
904
|
+
function getSelectionColors(element) {
|
|
905
|
+
const uniqueColors = /* @__PURE__ */ new Map();
|
|
906
|
+
const queue = [element];
|
|
907
|
+
for (let index = 0; index < queue.length; index++) {
|
|
908
|
+
const node = queue[index];
|
|
909
|
+
const computed = window.getComputedStyle(node);
|
|
910
|
+
if (computed.display === "none") {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const isVisibilityHidden = computed.visibility === "hidden" || computed.visibility === "collapse";
|
|
914
|
+
const currentTextColor = computed.color;
|
|
915
|
+
if (!isVisibilityHidden) {
|
|
916
|
+
addUniqueColor(uniqueColors, parseVisibleColor(computed.backgroundColor));
|
|
917
|
+
if (node instanceof HTMLElement && hasRenderableTextNode(node)) {
|
|
918
|
+
addUniqueColor(uniqueColors, parseVisibleColor(currentTextColor));
|
|
919
|
+
}
|
|
920
|
+
const borderSides = [
|
|
921
|
+
{ style: computed.borderTopStyle, width: computed.borderTopWidth, color: computed.borderTopColor },
|
|
922
|
+
{ style: computed.borderRightStyle, width: computed.borderRightWidth, color: computed.borderRightColor },
|
|
923
|
+
{ style: computed.borderBottomStyle, width: computed.borderBottomWidth, color: computed.borderBottomColor },
|
|
924
|
+
{ style: computed.borderLeftStyle, width: computed.borderLeftWidth, color: computed.borderLeftColor }
|
|
925
|
+
];
|
|
926
|
+
for (const side of borderSides) {
|
|
927
|
+
if (!isVisibleBorderSide(side)) continue;
|
|
928
|
+
addUniqueColor(uniqueColors, parseVisibleColor(side.color, currentTextColor));
|
|
929
|
+
}
|
|
930
|
+
if (hasVisibleOutline(computed)) {
|
|
931
|
+
addUniqueColor(uniqueColors, parseVisibleColor(computed.outlineColor, currentTextColor));
|
|
932
|
+
}
|
|
933
|
+
if (node instanceof SVGElement) {
|
|
934
|
+
const fillColor = parseVisibleColor(computed.getPropertyValue("fill"), currentTextColor) ?? parseVisibleColor(node.getAttribute("fill") ?? "", currentTextColor);
|
|
935
|
+
const strokeColor = parseVisibleColor(computed.getPropertyValue("stroke"), currentTextColor) ?? parseVisibleColor(node.getAttribute("stroke") ?? "", currentTextColor);
|
|
936
|
+
addUniqueColor(uniqueColors, fillColor);
|
|
937
|
+
addUniqueColor(uniqueColors, strokeColor);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
for (const child of node.children) {
|
|
941
|
+
queue.push(child);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return Array.from(uniqueColors.values());
|
|
945
|
+
}
|
|
826
946
|
function getAllComputedStyles(element) {
|
|
827
947
|
const { spacing, borderRadius, flex } = getComputedStyles(element);
|
|
828
948
|
return {
|
|
@@ -870,7 +990,7 @@ function getElementInfo(element) {
|
|
|
870
990
|
classList: Array.from(element.classList),
|
|
871
991
|
isFlexContainer: isFlexContainer2,
|
|
872
992
|
isFlexItem,
|
|
873
|
-
isTextElement:
|
|
993
|
+
isTextElement: isTextElement2(element),
|
|
874
994
|
parentElement,
|
|
875
995
|
hasChildren: element.children.length > 0
|
|
876
996
|
};
|
|
@@ -910,9 +1030,8 @@ function isFitSizing(element, dimension) {
|
|
|
910
1030
|
return false;
|
|
911
1031
|
}
|
|
912
1032
|
function getDimensionDisplay(element) {
|
|
913
|
-
const
|
|
914
|
-
const
|
|
915
|
-
const height = Math.round(rect.height);
|
|
1033
|
+
const width = Math.round(element.offsetWidth);
|
|
1034
|
+
const height = Math.round(element.offsetHeight);
|
|
916
1035
|
const widthIsFit = isFitSizing(element, "width");
|
|
917
1036
|
const heightIsFit = isFitSizing(element, "height");
|
|
918
1037
|
return {
|
|
@@ -945,8 +1064,9 @@ function calculateParentMeasurements(element, container) {
|
|
|
945
1064
|
parentInnerRight = paddingBoxRight - (parseFloat(parentStyles.paddingRight) || 0);
|
|
946
1065
|
parentInnerBottom = paddingBoxBottom - (parseFloat(parentStyles.paddingBottom) || 0);
|
|
947
1066
|
}
|
|
1067
|
+
const zoom = getZoomScale();
|
|
948
1068
|
const measurements = [];
|
|
949
|
-
const topDistance = Math.round(elementRect.top - parentInnerTop);
|
|
1069
|
+
const topDistance = Math.round((elementRect.top - parentInnerTop) / zoom);
|
|
950
1070
|
if (topDistance > 0) {
|
|
951
1071
|
const midX = elementRect.left + elementRect.width / 2;
|
|
952
1072
|
measurements.push({
|
|
@@ -959,7 +1079,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
959
1079
|
labelPosition: { x: midX, y: (parentInnerTop + elementRect.top) / 2 }
|
|
960
1080
|
});
|
|
961
1081
|
}
|
|
962
|
-
const bottomDistance = Math.round(parentInnerBottom - elementRect.bottom);
|
|
1082
|
+
const bottomDistance = Math.round((parentInnerBottom - elementRect.bottom) / zoom);
|
|
963
1083
|
if (bottomDistance > 0) {
|
|
964
1084
|
const midX = elementRect.left + elementRect.width / 2;
|
|
965
1085
|
measurements.push({
|
|
@@ -972,7 +1092,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
972
1092
|
labelPosition: { x: midX, y: (elementRect.bottom + parentInnerBottom) / 2 }
|
|
973
1093
|
});
|
|
974
1094
|
}
|
|
975
|
-
const leftDistance = Math.round(elementRect.left - parentInnerLeft);
|
|
1095
|
+
const leftDistance = Math.round((elementRect.left - parentInnerLeft) / zoom);
|
|
976
1096
|
if (leftDistance > 0) {
|
|
977
1097
|
const midY = elementRect.top + elementRect.height / 2;
|
|
978
1098
|
measurements.push({
|
|
@@ -985,7 +1105,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
985
1105
|
labelPosition: { x: (parentInnerLeft + elementRect.left) / 2, y: midY }
|
|
986
1106
|
});
|
|
987
1107
|
}
|
|
988
|
-
const rightDistance = Math.round(parentInnerRight - elementRect.right);
|
|
1108
|
+
const rightDistance = Math.round((parentInnerRight - elementRect.right) / zoom);
|
|
989
1109
|
if (rightDistance > 0) {
|
|
990
1110
|
const midY = elementRect.top + elementRect.height / 2;
|
|
991
1111
|
measurements.push({
|
|
@@ -1003,6 +1123,7 @@ function calculateParentMeasurements(element, container) {
|
|
|
1003
1123
|
function calculateElementMeasurements(from, to) {
|
|
1004
1124
|
const fromRect = from.getBoundingClientRect();
|
|
1005
1125
|
const toRect = to.getBoundingClientRect();
|
|
1126
|
+
const zoom = getZoomScale();
|
|
1006
1127
|
const measurements = [];
|
|
1007
1128
|
const horizontalOverlap = fromRect.left < toRect.right && fromRect.right > toRect.left;
|
|
1008
1129
|
const verticalOverlap = fromRect.top < toRect.bottom && fromRect.bottom > toRect.top;
|
|
@@ -1011,7 +1132,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
1011
1132
|
const overlapBottom = Math.min(fromRect.bottom, toRect.bottom);
|
|
1012
1133
|
const midY = (overlapTop + overlapBottom) / 2;
|
|
1013
1134
|
if (fromRect.right <= toRect.left) {
|
|
1014
|
-
const distance = Math.round(toRect.left - fromRect.right);
|
|
1135
|
+
const distance = Math.round((toRect.left - fromRect.right) / zoom);
|
|
1015
1136
|
measurements.push({
|
|
1016
1137
|
direction: "horizontal",
|
|
1017
1138
|
x1: fromRect.right,
|
|
@@ -1022,7 +1143,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
1022
1143
|
labelPosition: { x: (fromRect.right + toRect.left) / 2, y: midY }
|
|
1023
1144
|
});
|
|
1024
1145
|
} else if (fromRect.left >= toRect.right) {
|
|
1025
|
-
const distance = Math.round(fromRect.left - toRect.right);
|
|
1146
|
+
const distance = Math.round((fromRect.left - toRect.right) / zoom);
|
|
1026
1147
|
measurements.push({
|
|
1027
1148
|
direction: "horizontal",
|
|
1028
1149
|
x1: toRect.right,
|
|
@@ -1039,7 +1160,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
1039
1160
|
const overlapRight = Math.min(fromRect.right, toRect.right);
|
|
1040
1161
|
const midX = (overlapLeft + overlapRight) / 2;
|
|
1041
1162
|
if (fromRect.bottom <= toRect.top) {
|
|
1042
|
-
const distance = Math.round(toRect.top - fromRect.bottom);
|
|
1163
|
+
const distance = Math.round((toRect.top - fromRect.bottom) / zoom);
|
|
1043
1164
|
measurements.push({
|
|
1044
1165
|
direction: "vertical",
|
|
1045
1166
|
x1: midX,
|
|
@@ -1050,7 +1171,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
1050
1171
|
labelPosition: { x: midX, y: (fromRect.bottom + toRect.top) / 2 }
|
|
1051
1172
|
});
|
|
1052
1173
|
} else if (fromRect.top >= toRect.bottom) {
|
|
1053
|
-
const distance = Math.round(fromRect.top - toRect.bottom);
|
|
1174
|
+
const distance = Math.round((fromRect.top - toRect.bottom) / zoom);
|
|
1054
1175
|
measurements.push({
|
|
1055
1176
|
direction: "vertical",
|
|
1056
1177
|
x1: midX,
|
|
@@ -1067,7 +1188,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
1067
1188
|
const fromCenterY = fromRect.top + fromRect.height / 2;
|
|
1068
1189
|
const toCenterX = toRect.left + toRect.width / 2;
|
|
1069
1190
|
const toCenterY = toRect.top + toRect.height / 2;
|
|
1070
|
-
const hDistance = toCenterX > fromCenterX ? Math.round(toRect.left - fromRect.right) : Math.round(fromRect.left - toRect.right);
|
|
1191
|
+
const hDistance = toCenterX > fromCenterX ? Math.round((toRect.left - fromRect.right) / zoom) : Math.round((fromRect.left - toRect.right) / zoom);
|
|
1071
1192
|
if (hDistance > 0) {
|
|
1072
1193
|
const startX = toCenterX > fromCenterX ? fromRect.right : fromRect.left;
|
|
1073
1194
|
const endX = toCenterX > fromCenterX ? toRect.left : toRect.right;
|
|
@@ -1082,7 +1203,7 @@ function calculateElementMeasurements(from, to) {
|
|
|
1082
1203
|
labelPosition: { x: (startX + endX) / 2, y }
|
|
1083
1204
|
});
|
|
1084
1205
|
}
|
|
1085
|
-
const vDistance = toCenterY > fromCenterY ? Math.round(toRect.top - fromRect.bottom) : Math.round(fromRect.top - toRect.bottom);
|
|
1206
|
+
const vDistance = toCenterY > fromCenterY ? Math.round((toRect.top - fromRect.bottom) / zoom) : Math.round((fromRect.top - toRect.bottom) / zoom);
|
|
1086
1207
|
if (vDistance > 0) {
|
|
1087
1208
|
const x = (fromCenterX + toCenterX) / 2;
|
|
1088
1209
|
const startY = toCenterY > fromCenterY ? fromRect.bottom : fromRect.top;
|
|
@@ -1103,70 +1224,77 @@ function calculateElementMeasurements(from, to) {
|
|
|
1103
1224
|
var GUIDELINE_PROXIMITY = 80;
|
|
1104
1225
|
function calculateGuidelineMeasurements(element, guidelines, mousePosition) {
|
|
1105
1226
|
if (guidelines.length === 0) return [];
|
|
1227
|
+
const snap = getCanvasSnapshot();
|
|
1228
|
+
const zoom = snap.active ? snap.zoom : 1;
|
|
1106
1229
|
const rect = element.getBoundingClientRect();
|
|
1107
|
-
const scrollX = window.scrollX;
|
|
1108
|
-
const scrollY = window.scrollY;
|
|
1109
1230
|
const measurements = [];
|
|
1110
1231
|
for (const g of guidelines) {
|
|
1232
|
+
let viewportPos;
|
|
1233
|
+
if (snap.active) {
|
|
1234
|
+
const pan = g.orientation === "horizontal" ? snap.panY : snap.panX;
|
|
1235
|
+
const bo = g.orientation === "horizontal" ? getBodyOffset().y : getBodyOffset().x;
|
|
1236
|
+
viewportPos = bo + (g.position - bo + pan) * zoom;
|
|
1237
|
+
} else {
|
|
1238
|
+
const scroll = g.orientation === "horizontal" ? window.scrollY : window.scrollX;
|
|
1239
|
+
viewportPos = g.position - scroll;
|
|
1240
|
+
}
|
|
1111
1241
|
if (g.orientation === "horizontal") {
|
|
1112
|
-
const gy = g.position - scrollY;
|
|
1113
1242
|
const midX = rect.left + rect.width / 2;
|
|
1114
|
-
if (mousePosition && Math.abs(mousePosition.y -
|
|
1115
|
-
if (
|
|
1116
|
-
const distance = Math.round(rect.top -
|
|
1243
|
+
if (mousePosition && Math.abs(mousePosition.y - viewportPos) > GUIDELINE_PROXIMITY) continue;
|
|
1244
|
+
if (viewportPos < rect.top) {
|
|
1245
|
+
const distance = Math.round((rect.top - viewportPos) / zoom);
|
|
1117
1246
|
if (distance > 0) {
|
|
1118
1247
|
measurements.push({
|
|
1119
1248
|
direction: "vertical",
|
|
1120
1249
|
x1: midX,
|
|
1121
|
-
y1:
|
|
1250
|
+
y1: viewportPos,
|
|
1122
1251
|
x2: midX,
|
|
1123
1252
|
y2: rect.top,
|
|
1124
1253
|
distance,
|
|
1125
|
-
labelPosition: { x: midX, y: (
|
|
1254
|
+
labelPosition: { x: midX, y: (viewportPos + rect.top) / 2 }
|
|
1126
1255
|
});
|
|
1127
1256
|
}
|
|
1128
|
-
} else if (
|
|
1129
|
-
const distance = Math.round(
|
|
1257
|
+
} else if (viewportPos > rect.bottom) {
|
|
1258
|
+
const distance = Math.round((viewportPos - rect.bottom) / zoom);
|
|
1130
1259
|
if (distance > 0) {
|
|
1131
1260
|
measurements.push({
|
|
1132
1261
|
direction: "vertical",
|
|
1133
1262
|
x1: midX,
|
|
1134
1263
|
y1: rect.bottom,
|
|
1135
1264
|
x2: midX,
|
|
1136
|
-
y2:
|
|
1265
|
+
y2: viewportPos,
|
|
1137
1266
|
distance,
|
|
1138
|
-
labelPosition: { x: midX, y: (rect.bottom +
|
|
1267
|
+
labelPosition: { x: midX, y: (rect.bottom + viewportPos) / 2 }
|
|
1139
1268
|
});
|
|
1140
1269
|
}
|
|
1141
1270
|
}
|
|
1142
1271
|
} else {
|
|
1143
|
-
const gx = g.position - scrollX;
|
|
1144
1272
|
const midY = rect.top + rect.height / 2;
|
|
1145
|
-
if (mousePosition && Math.abs(mousePosition.x -
|
|
1146
|
-
if (
|
|
1147
|
-
const distance = Math.round(rect.left -
|
|
1273
|
+
if (mousePosition && Math.abs(mousePosition.x - viewportPos) > GUIDELINE_PROXIMITY) continue;
|
|
1274
|
+
if (viewportPos < rect.left) {
|
|
1275
|
+
const distance = Math.round((rect.left - viewportPos) / zoom);
|
|
1148
1276
|
if (distance > 0) {
|
|
1149
1277
|
measurements.push({
|
|
1150
1278
|
direction: "horizontal",
|
|
1151
|
-
x1:
|
|
1279
|
+
x1: viewportPos,
|
|
1152
1280
|
y1: midY,
|
|
1153
1281
|
x2: rect.left,
|
|
1154
1282
|
y2: midY,
|
|
1155
1283
|
distance,
|
|
1156
|
-
labelPosition: { x: (
|
|
1284
|
+
labelPosition: { x: (viewportPos + rect.left) / 2, y: midY }
|
|
1157
1285
|
});
|
|
1158
1286
|
}
|
|
1159
|
-
} else if (
|
|
1160
|
-
const distance = Math.round(
|
|
1287
|
+
} else if (viewportPos > rect.right) {
|
|
1288
|
+
const distance = Math.round((viewportPos - rect.right) / zoom);
|
|
1161
1289
|
if (distance > 0) {
|
|
1162
1290
|
measurements.push({
|
|
1163
1291
|
direction: "horizontal",
|
|
1164
1292
|
x1: rect.right,
|
|
1165
1293
|
y1: midY,
|
|
1166
|
-
x2:
|
|
1294
|
+
x2: viewportPos,
|
|
1167
1295
|
y2: midY,
|
|
1168
1296
|
distance,
|
|
1169
|
-
labelPosition: { x: (rect.right +
|
|
1297
|
+
labelPosition: { x: (rect.right + viewportPos) / 2, y: midY }
|
|
1170
1298
|
});
|
|
1171
1299
|
}
|
|
1172
1300
|
}
|
|
@@ -1182,6 +1310,10 @@ function getFlexDirection(element) {
|
|
|
1182
1310
|
const computed = window.getComputedStyle(element);
|
|
1183
1311
|
return computed.flexDirection;
|
|
1184
1312
|
}
|
|
1313
|
+
function isInFlowChild(el) {
|
|
1314
|
+
const cs = window.getComputedStyle(el);
|
|
1315
|
+
return cs.display !== "none" && cs.position !== "absolute" && cs.position !== "fixed";
|
|
1316
|
+
}
|
|
1185
1317
|
function detectChildrenDirection(container, exclude) {
|
|
1186
1318
|
const computed = window.getComputedStyle(container);
|
|
1187
1319
|
if (computed.display === "flex" || computed.display === "inline-flex") {
|
|
@@ -1194,8 +1326,7 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1194
1326
|
const visible = [];
|
|
1195
1327
|
for (const c of container.children) {
|
|
1196
1328
|
if (!(c instanceof HTMLElement) || c === exclude) continue;
|
|
1197
|
-
|
|
1198
|
-
if (cs.display === "none" || cs.position === "absolute" || cs.position === "fixed") continue;
|
|
1329
|
+
if (!isInFlowChild(c)) continue;
|
|
1199
1330
|
visible.push(c);
|
|
1200
1331
|
if (visible.length === 2) break;
|
|
1201
1332
|
}
|
|
@@ -1208,6 +1339,29 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1208
1339
|
}
|
|
1209
1340
|
return { axis: "vertical", reversed: second.bottom < first.top };
|
|
1210
1341
|
}
|
|
1342
|
+
function computeIntendedIndex(parent, draggedElement) {
|
|
1343
|
+
const { axis } = detectChildrenDirection(parent, draggedElement);
|
|
1344
|
+
const isHorizontal = axis === "horizontal";
|
|
1345
|
+
const draggedRect = draggedElement.getBoundingClientRect();
|
|
1346
|
+
const intendedCenter = isHorizontal ? draggedRect.left + draggedRect.width / 2 : draggedRect.top + draggedRect.height / 2;
|
|
1347
|
+
const siblings = [];
|
|
1348
|
+
for (const c of parent.children) {
|
|
1349
|
+
if (!(c instanceof HTMLElement) || c === draggedElement) continue;
|
|
1350
|
+
if (!isInFlowChild(c)) continue;
|
|
1351
|
+
siblings.push(c);
|
|
1352
|
+
}
|
|
1353
|
+
if (siblings.length === 0) {
|
|
1354
|
+
return { index: 0, siblingBefore: null, siblingAfter: null };
|
|
1355
|
+
}
|
|
1356
|
+
for (let i = 0; i < siblings.length; i++) {
|
|
1357
|
+
const rect = siblings[i].getBoundingClientRect();
|
|
1358
|
+
const midpoint = isHorizontal ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
|
|
1359
|
+
if (intendedCenter < midpoint) {
|
|
1360
|
+
return { index: i, siblingBefore: i > 0 ? siblings[i - 1] : null, siblingAfter: siblings[i] };
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return { index: siblings.length, siblingBefore: siblings[siblings.length - 1], siblingAfter: null };
|
|
1364
|
+
}
|
|
1211
1365
|
function htmlChildren(el) {
|
|
1212
1366
|
return Array.from(el.children).filter(
|
|
1213
1367
|
(child) => child instanceof HTMLElement
|
|
@@ -1366,6 +1520,22 @@ function findContainerAtPoint(x, y, exclude, preferredParent) {
|
|
|
1366
1520
|
}
|
|
1367
1521
|
return findContainerViaTraversal(x, y, exclude);
|
|
1368
1522
|
}
|
|
1523
|
+
function findLayoutContainerAtPoint(x, y, exclude, preferredParent) {
|
|
1524
|
+
const host = document.querySelector("[data-direct-edit-host]");
|
|
1525
|
+
if (host) host.style.display = "none";
|
|
1526
|
+
const elements = document.elementsFromPoint(x, y);
|
|
1527
|
+
if (host) host.style.display = "";
|
|
1528
|
+
for (const el of elements) {
|
|
1529
|
+
if (skipElement(el, exclude)) continue;
|
|
1530
|
+
if (isLayoutContainer(el)) return el;
|
|
1531
|
+
}
|
|
1532
|
+
if (preferredParent && isLayoutContainer(preferredParent)) {
|
|
1533
|
+
for (const el of elements) {
|
|
1534
|
+
if (el === preferredParent) return preferredParent;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
return null;
|
|
1538
|
+
}
|
|
1369
1539
|
function calculateDropPosition(container, pointerX, pointerY, draggedElement) {
|
|
1370
1540
|
const { axis, reversed: isReversed } = detectChildrenDirection(container, draggedElement);
|
|
1371
1541
|
const isHorizontal = axis === "horizontal";
|
|
@@ -1760,7 +1930,18 @@ function getReactComponentStack(element) {
|
|
|
1760
1930
|
return getRenderStack(fiber);
|
|
1761
1931
|
}
|
|
1762
1932
|
function getElementDisplayName(element) {
|
|
1763
|
-
|
|
1933
|
+
const tag = element.tagName.toLowerCase();
|
|
1934
|
+
if (element.id) return `${tag}#${element.id}`;
|
|
1935
|
+
const firstClass = Array.from(element.classList).find((c) => c && !c.startsWith("direct-edit"));
|
|
1936
|
+
if (firstClass) return `${tag}.${firstClass}`;
|
|
1937
|
+
return tag;
|
|
1938
|
+
}
|
|
1939
|
+
function getChildBriefInfo(element) {
|
|
1940
|
+
const name = getElementDisplayName(element);
|
|
1941
|
+
const raw = ((element.innerText || element.textContent) ?? "").replace(/\s+/g, " ").trim();
|
|
1942
|
+
const textPreview = raw.length > 40 ? `${raw.slice(0, 37)}...` : raw;
|
|
1943
|
+
const source = getElementSource(element);
|
|
1944
|
+
return { name, textPreview, source };
|
|
1764
1945
|
}
|
|
1765
1946
|
var STABLE_ATTRIBUTES = ["data-testid", "data-qa", "data-cy", "aria-label", "role"];
|
|
1766
1947
|
var MAX_SELECTOR_DEPTH = 24;
|
|
@@ -2048,26 +2229,28 @@ function parseDomSource(element) {
|
|
|
2048
2229
|
}
|
|
2049
2230
|
return { file, line, column };
|
|
2050
2231
|
}
|
|
2051
|
-
function
|
|
2052
|
-
const
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
};
|
|
2066
|
-
break;
|
|
2067
|
-
}
|
|
2068
|
-
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
2232
|
+
function getElementSource(element) {
|
|
2233
|
+
const domSource = parseDomSource(element);
|
|
2234
|
+
if (domSource) return domSource;
|
|
2235
|
+
const seenFibers = /* @__PURE__ */ new Set();
|
|
2236
|
+
let fiber = getFiberForElement(element);
|
|
2237
|
+
while (fiber && !seenFibers.has(fiber)) {
|
|
2238
|
+
seenFibers.add(fiber);
|
|
2239
|
+
const fiberSource = getSourceFromFiber(fiber);
|
|
2240
|
+
if (fiberSource?.fileName) {
|
|
2241
|
+
return {
|
|
2242
|
+
file: fiberSource.fileName,
|
|
2243
|
+
line: fiberSource.lineNumber,
|
|
2244
|
+
column: fiberSource.columnNumber
|
|
2245
|
+
};
|
|
2069
2246
|
}
|
|
2247
|
+
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
2070
2248
|
}
|
|
2249
|
+
return null;
|
|
2250
|
+
}
|
|
2251
|
+
function getElementLocator(element) {
|
|
2252
|
+
const elementInfo = getElementInfo(element);
|
|
2253
|
+
const domSource = getElementSource(element);
|
|
2071
2254
|
return {
|
|
2072
2255
|
reactStack: getReactComponentStack(element),
|
|
2073
2256
|
domSelector: buildDomSelector(element),
|
|
@@ -2086,7 +2269,7 @@ function getLocatorHeader(locator) {
|
|
|
2086
2269
|
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;
|
|
2087
2270
|
return { componentLabel, formattedSource };
|
|
2088
2271
|
}
|
|
2089
|
-
function buildLocatorContextLines(locator) {
|
|
2272
|
+
function buildLocatorContextLines(locator, options) {
|
|
2090
2273
|
const lines = [];
|
|
2091
2274
|
const { componentLabel, formattedSource } = getLocatorHeader(locator);
|
|
2092
2275
|
const target = (locator.targetHtml || locator.domContextHtml || "").trim();
|
|
@@ -2099,7 +2282,7 @@ function buildLocatorContextLines(locator) {
|
|
|
2099
2282
|
lines.push("target:");
|
|
2100
2283
|
lines.push(target);
|
|
2101
2284
|
}
|
|
2102
|
-
if (context && context !== target) {
|
|
2285
|
+
if (!options?.skipContext && context && context !== target) {
|
|
2103
2286
|
lines.push("context:");
|
|
2104
2287
|
lines.push(context);
|
|
2105
2288
|
}
|
|
@@ -2253,6 +2436,29 @@ function buildEditExport(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
|
|
|
2253
2436
|
}
|
|
2254
2437
|
return lines.join("\n");
|
|
2255
2438
|
}
|
|
2439
|
+
function buildEditExportWithOptions(locator, pendingStyles, textEdit, options) {
|
|
2440
|
+
const changes = [];
|
|
2441
|
+
const collapsedStyles = collapseExportShorthands(pendingStyles);
|
|
2442
|
+
for (const [property, value] of Object.entries(collapsedStyles)) {
|
|
2443
|
+
const tailwindClass = stylesToTailwind({ [property]: value });
|
|
2444
|
+
changes.push({ property, value, tailwind: tailwindClass });
|
|
2445
|
+
}
|
|
2446
|
+
const lines = buildLocatorContextLines(locator, options);
|
|
2447
|
+
lines.push("");
|
|
2448
|
+
if (changes.length > 0) {
|
|
2449
|
+
lines.push("edits:");
|
|
2450
|
+
for (const change of changes) {
|
|
2451
|
+
const tailwind = change.tailwind ? ` (${change.tailwind})` : "";
|
|
2452
|
+
lines.push(`${change.property}: ${change.value}${tailwind}`);
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
if (textEdit) {
|
|
2456
|
+
lines.push("text content changed:");
|
|
2457
|
+
lines.push(`from: "${textEdit.originalText}"`);
|
|
2458
|
+
lines.push(`to: "${textEdit.newText}"`);
|
|
2459
|
+
}
|
|
2460
|
+
return lines.join("\n");
|
|
2461
|
+
}
|
|
2256
2462
|
function buildCommentExport(locator, commentText, replies) {
|
|
2257
2463
|
const lines = buildLocatorContextLines(locator);
|
|
2258
2464
|
lines.push("");
|
|
@@ -2264,55 +2470,554 @@ function buildCommentExport(locator, commentText, replies) {
|
|
|
2264
2470
|
}
|
|
2265
2471
|
return lines.join("\n");
|
|
2266
2472
|
}
|
|
2267
|
-
function
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2473
|
+
function normalizeSelector(selector) {
|
|
2474
|
+
const value = selector?.trim();
|
|
2475
|
+
return value && value.length > 0 ? value : null;
|
|
2476
|
+
}
|
|
2477
|
+
function normalizeName(name) {
|
|
2478
|
+
return name?.trim().toLowerCase() || "element";
|
|
2272
2479
|
}
|
|
2273
|
-
function
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2480
|
+
function buildAnchorRef(name, selector, source) {
|
|
2481
|
+
return {
|
|
2482
|
+
name: name?.trim() || "element",
|
|
2483
|
+
selector: normalizeSelector(selector),
|
|
2484
|
+
source: source?.file ? source : null
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
function anchorKey(anchor) {
|
|
2488
|
+
if (!anchor) return "none";
|
|
2489
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2490
|
+
if (selector) return `selector:${selector}`;
|
|
2491
|
+
if (anchor.source?.file) {
|
|
2492
|
+
return `source:${anchor.source.file}:${anchor.source.line ?? 0}:${anchor.source.column ?? 0}`;
|
|
2493
|
+
}
|
|
2494
|
+
return `name:${normalizeName(anchor.name)}`;
|
|
2495
|
+
}
|
|
2496
|
+
function anchorsEqual(a, b) {
|
|
2497
|
+
if (!a && !b) return true;
|
|
2498
|
+
if (!a || !b) return false;
|
|
2499
|
+
const aSelector = normalizeSelector(a.selector);
|
|
2500
|
+
const bSelector = normalizeSelector(b.selector);
|
|
2501
|
+
if (aSelector && bSelector) return aSelector === bSelector;
|
|
2502
|
+
if (a.source?.file && b.source?.file) {
|
|
2503
|
+
return a.source.file === b.source.file && (a.source.line ?? null) === (b.source.line ?? null) && (a.source.column ?? null) === (b.source.column ?? null);
|
|
2504
|
+
}
|
|
2505
|
+
return normalizeName(a.name) === normalizeName(b.name);
|
|
2506
|
+
}
|
|
2507
|
+
function formatAnchorRef(anchor, fallback = "(none)") {
|
|
2508
|
+
if (!anchor) return fallback;
|
|
2509
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2510
|
+
if (selector) return selector;
|
|
2511
|
+
if (anchor.source?.file) return `<${anchor.name}> @ ${formatSourceLocation(anchor.source.file, anchor.source.line, anchor.source.column)}`;
|
|
2512
|
+
return `<${anchor.name}>`;
|
|
2513
|
+
}
|
|
2514
|
+
function buildPlacementRef(before, after) {
|
|
2515
|
+
if (before && after) {
|
|
2516
|
+
return {
|
|
2517
|
+
before,
|
|
2518
|
+
after,
|
|
2519
|
+
description: `between after ${formatAnchorRef(before)} and before ${formatAnchorRef(after)}`
|
|
2520
|
+
};
|
|
2278
2521
|
}
|
|
2279
|
-
|
|
2522
|
+
if (before) {
|
|
2523
|
+
return {
|
|
2524
|
+
before,
|
|
2525
|
+
after: null,
|
|
2526
|
+
description: `after ${formatAnchorRef(before)}`
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
if (after) {
|
|
2530
|
+
return {
|
|
2531
|
+
before: null,
|
|
2532
|
+
after,
|
|
2533
|
+
description: `before ${formatAnchorRef(after)}`
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
return {
|
|
2537
|
+
before: null,
|
|
2538
|
+
after: null,
|
|
2539
|
+
description: "as the only child"
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
function buildPlacementFromMove(beforeName, beforeSelector, beforeSource, afterName, afterSelector, afterSource) {
|
|
2543
|
+
const before = beforeName || beforeSelector || beforeSource?.file ? buildAnchorRef(beforeName, beforeSelector, beforeSource) : null;
|
|
2544
|
+
const after = afterName || afterSelector || afterSource?.file ? buildAnchorRef(afterName, afterSelector, afterSource) : null;
|
|
2545
|
+
return buildPlacementRef(before, after);
|
|
2546
|
+
}
|
|
2547
|
+
function toRoundedVisualDelta(move) {
|
|
2548
|
+
const delta = move.visualDelta ?? move.positionDelta;
|
|
2549
|
+
if (!delta) return void 0;
|
|
2550
|
+
const rounded = { x: Math.round(delta.x), y: Math.round(delta.y) };
|
|
2551
|
+
return rounded.x === 0 && rounded.y === 0 ? void 0 : rounded;
|
|
2552
|
+
}
|
|
2553
|
+
function hasVisualIntent(move) {
|
|
2554
|
+
return Boolean(toRoundedVisualDelta(move));
|
|
2555
|
+
}
|
|
2556
|
+
function hasStructuralChange(move) {
|
|
2557
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2558
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2559
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2560
|
+
move.fromSiblingBefore,
|
|
2561
|
+
move.fromSiblingBeforeSelector,
|
|
2562
|
+
move.fromSiblingBeforeSource,
|
|
2563
|
+
move.fromSiblingAfter,
|
|
2564
|
+
move.fromSiblingAfterSelector,
|
|
2565
|
+
move.fromSiblingAfterSource
|
|
2566
|
+
);
|
|
2567
|
+
const toPlacement = buildPlacementFromMove(
|
|
2568
|
+
move.toSiblingBefore,
|
|
2569
|
+
move.toSiblingBeforeSelector,
|
|
2570
|
+
move.toSiblingBeforeSource,
|
|
2571
|
+
move.toSiblingAfter,
|
|
2572
|
+
move.toSiblingAfterSelector,
|
|
2573
|
+
move.toSiblingAfterSource
|
|
2574
|
+
);
|
|
2575
|
+
if (!anchorsEqual(fromParent, toParent)) return true;
|
|
2576
|
+
if (!anchorsEqual(fromPlacement.before, toPlacement.before)) return true;
|
|
2577
|
+
if (!anchorsEqual(fromPlacement.after, toPlacement.after)) return true;
|
|
2578
|
+
if (typeof move.fromIndex === "number" && typeof move.toIndex === "number" && move.fromIndex !== move.toIndex) return true;
|
|
2579
|
+
return false;
|
|
2280
2580
|
}
|
|
2281
|
-
function
|
|
2282
|
-
|
|
2283
|
-
|
|
2581
|
+
function isStructuredLayoutContainer(layout) {
|
|
2582
|
+
return layout === "flex" || layout === "grid";
|
|
2583
|
+
}
|
|
2584
|
+
function isExistingFlexWorkflow(move) {
|
|
2585
|
+
const structuralChange = hasStructuralChange(move);
|
|
2586
|
+
if (!structuralChange) return false;
|
|
2587
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2588
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2589
|
+
const sameParent = anchorsEqual(fromParent, toParent);
|
|
2590
|
+
const fromLayout = move.fromParentLayout;
|
|
2591
|
+
const toLayout = move.toParentLayout;
|
|
2592
|
+
if (sameParent) {
|
|
2593
|
+
return Boolean(move.mode === "reorder" && (isStructuredLayoutContainer(toLayout) || isStructuredLayoutContainer(fromLayout)));
|
|
2594
|
+
}
|
|
2595
|
+
return Boolean(isStructuredLayoutContainer(fromLayout) && isStructuredLayoutContainer(toLayout));
|
|
2596
|
+
}
|
|
2597
|
+
function classifyMove(move) {
|
|
2598
|
+
const structuralChange = hasStructuralChange(move);
|
|
2599
|
+
const visualIntent = hasVisualIntent(move);
|
|
2600
|
+
if (!structuralChange && !visualIntent) return "noop";
|
|
2601
|
+
if (isExistingFlexWorkflow(move)) return "existing_layout_move";
|
|
2602
|
+
if (move.mode === "free" || move.mode === "position") return "layout_refactor";
|
|
2603
|
+
if (!structuralChange && visualIntent) return "layout_refactor";
|
|
2604
|
+
return "layout_refactor";
|
|
2605
|
+
}
|
|
2606
|
+
function buildNumericClusters(values, tolerance) {
|
|
2607
|
+
if (values.length === 0) return [];
|
|
2608
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2609
|
+
const clusters = [{ center: sorted[0], values: [sorted[0]] }];
|
|
2610
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
2611
|
+
const value = sorted[i];
|
|
2612
|
+
const current = clusters[clusters.length - 1];
|
|
2613
|
+
if (Math.abs(value - current.center) <= tolerance) {
|
|
2614
|
+
current.values.push(value);
|
|
2615
|
+
current.center = current.values.reduce((sum, n) => sum + n, 0) / current.values.length;
|
|
2616
|
+
} else {
|
|
2617
|
+
clusters.push({ center: value, values: [value] });
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return clusters;
|
|
2284
2621
|
}
|
|
2285
|
-
function
|
|
2286
|
-
if (
|
|
2287
|
-
|
|
2622
|
+
function inferFlexDirection(sameRowCount, sameColumnCount, visualDelta) {
|
|
2623
|
+
if (sameRowCount > sameColumnCount) {
|
|
2624
|
+
return { direction: "row", reason: "Subject aligns with neighboring anchors on the same row." };
|
|
2625
|
+
}
|
|
2626
|
+
if (sameColumnCount > sameRowCount) {
|
|
2627
|
+
return { direction: "column", reason: "Subject aligns with neighboring anchors on the same column." };
|
|
2628
|
+
}
|
|
2629
|
+
if (sameRowCount > 0) {
|
|
2630
|
+
return { direction: "row", reason: "Detected row alignment in final geometry." };
|
|
2631
|
+
}
|
|
2632
|
+
if (sameColumnCount > 0) {
|
|
2633
|
+
return { direction: "column", reason: "Detected column alignment in final geometry." };
|
|
2634
|
+
}
|
|
2635
|
+
const horizontalDominant = Math.abs(visualDelta?.x ?? 0) >= Math.abs(visualDelta?.y ?? 0);
|
|
2636
|
+
return {
|
|
2637
|
+
direction: horizontalDominant ? "row" : "column",
|
|
2638
|
+
reason: "Fell back to movement axis because anchor alignment was ambiguous."
|
|
2639
|
+
};
|
|
2288
2640
|
}
|
|
2289
|
-
function
|
|
2290
|
-
|
|
2641
|
+
function inferLayoutPrescription(edit, operation, reasons) {
|
|
2642
|
+
const parent = edit.element.parentElement;
|
|
2643
|
+
if (!parent || !edit.element.isConnected) {
|
|
2644
|
+
return {
|
|
2645
|
+
recommendedSystem: "flex",
|
|
2646
|
+
intentPatterns: ["no_geometry_context"],
|
|
2647
|
+
refactorSteps: [
|
|
2648
|
+
`Reparent ${formatAnchorRef(operation.subject)} under ${formatAnchorRef(operation.to.parent)} at ${operation.to.placement.description}.`
|
|
2649
|
+
],
|
|
2650
|
+
styleSteps: [
|
|
2651
|
+
`Convert ${formatAnchorRef(operation.to.parent)} to flex and set a clear primary axis for this relationship.`,
|
|
2652
|
+
"Use `gap` for spacing and keep positioning static."
|
|
2653
|
+
],
|
|
2654
|
+
itemSteps: [
|
|
2655
|
+
"Remove any inline `left/top/transform` move artifacts from moved elements."
|
|
2656
|
+
]
|
|
2657
|
+
};
|
|
2658
|
+
}
|
|
2659
|
+
const children = Array.from(parent.children).filter(
|
|
2660
|
+
(node) => node instanceof HTMLElement && isInFlowChild(node) && !node.hasAttribute("data-direct-edit")
|
|
2661
|
+
);
|
|
2662
|
+
const childSnapshots = children.map((child) => {
|
|
2663
|
+
const rect = child.getBoundingClientRect();
|
|
2664
|
+
const locator = getElementLocator(child);
|
|
2665
|
+
const anchor = buildAnchorRef(getElementDisplayName(child), locator.domSelector, locator.domSource);
|
|
2666
|
+
return {
|
|
2667
|
+
child,
|
|
2668
|
+
rect,
|
|
2669
|
+
centerX: rect.left + rect.width / 2,
|
|
2670
|
+
centerY: rect.top + rect.height / 2,
|
|
2671
|
+
anchor,
|
|
2672
|
+
anchorLabel: formatAnchorRef(anchor)
|
|
2673
|
+
};
|
|
2674
|
+
});
|
|
2675
|
+
const subjectSnapshot = childSnapshots.find((snapshot2) => snapshot2.child === edit.element);
|
|
2676
|
+
const subjectRect = edit.element.getBoundingClientRect();
|
|
2677
|
+
const subjectCenterX = subjectRect.left + subjectRect.width / 2;
|
|
2678
|
+
const subjectCenterY = subjectRect.top + subjectRect.height / 2;
|
|
2679
|
+
const rowTolerance = Math.max(8, subjectRect.height * 0.35);
|
|
2680
|
+
const colTolerance = Math.max(8, subjectRect.width * 0.35);
|
|
2681
|
+
const sameRowWith = [];
|
|
2682
|
+
const sameColumnWith = [];
|
|
2683
|
+
const sameRowNodes = [];
|
|
2684
|
+
let aboveAnchor = null;
|
|
2685
|
+
let belowAnchor = null;
|
|
2686
|
+
let bestAboveDistance = Number.POSITIVE_INFINITY;
|
|
2687
|
+
let bestBelowDistance = Number.POSITIVE_INFINITY;
|
|
2688
|
+
for (const node of childSnapshots) {
|
|
2689
|
+
if (node.child === edit.element) continue;
|
|
2690
|
+
if (Math.abs(node.centerY - subjectCenterY) <= rowTolerance) {
|
|
2691
|
+
sameRowWith.push(node.anchorLabel);
|
|
2692
|
+
sameRowNodes.push(node);
|
|
2693
|
+
}
|
|
2694
|
+
if (Math.abs(node.centerX - subjectCenterX) <= colTolerance) {
|
|
2695
|
+
sameColumnWith.push(node.anchorLabel);
|
|
2696
|
+
}
|
|
2697
|
+
const yDelta = node.centerY - subjectCenterY;
|
|
2698
|
+
if (yDelta < 0 && Math.abs(yDelta) < bestAboveDistance) {
|
|
2699
|
+
bestAboveDistance = Math.abs(yDelta);
|
|
2700
|
+
aboveAnchor = node.anchorLabel;
|
|
2701
|
+
}
|
|
2702
|
+
if (yDelta > 0 && yDelta < bestBelowDistance) {
|
|
2703
|
+
bestBelowDistance = yDelta;
|
|
2704
|
+
belowAnchor = node.anchorLabel;
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
const rowCenters = childSnapshots.map(({ centerY }) => centerY);
|
|
2708
|
+
const colCenters = childSnapshots.map(({ centerX }) => centerX);
|
|
2709
|
+
const rowClusters = buildNumericClusters(rowCenters, rowTolerance);
|
|
2710
|
+
const colClusters = buildNumericClusters(colCenters, colTolerance);
|
|
2711
|
+
const denseRowClusters = rowClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2712
|
+
const denseColClusters = colClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2713
|
+
const isTwoDimensional = childSnapshots.length >= 4 && denseRowClusters >= 2 && denseColClusters >= 2;
|
|
2714
|
+
const recommendedSystem = isTwoDimensional ? "grid" : "flex";
|
|
2715
|
+
const intentPatterns = [];
|
|
2716
|
+
if (sameRowWith.length > 0) intentPatterns.push(`same_row_with:${sameRowWith.slice(0, 3).join(", ")}`);
|
|
2717
|
+
if (sameColumnWith.length > 0) intentPatterns.push(`same_column_with:${sameColumnWith.slice(0, 3).join(", ")}`);
|
|
2718
|
+
if (aboveAnchor) intentPatterns.push(`below:${aboveAnchor}`);
|
|
2719
|
+
if (belowAnchor) intentPatterns.push(`above:${belowAnchor}`);
|
|
2720
|
+
if (sameRowWith.length === 0 && sameColumnWith.length === 0) intentPatterns.push("separate_cluster");
|
|
2721
|
+
const visualDelta = operation.visualDelta;
|
|
2722
|
+
const flexDirectionInfo = inferFlexDirection(sameRowWith.length, sameColumnWith.length, visualDelta);
|
|
2723
|
+
const flexDirection = flexDirectionInfo.direction;
|
|
2724
|
+
if (recommendedSystem === "grid") {
|
|
2725
|
+
reasons.push("Detected multiple dense row and column clusters; a 2D layout system is likely intentional.");
|
|
2726
|
+
return {
|
|
2727
|
+
recommendedSystem: "grid",
|
|
2728
|
+
intentPatterns,
|
|
2729
|
+
refactorSteps: [
|
|
2730
|
+
`Create/ensure a shared container around ${formatAnchorRef(operation.subject)} and related anchors under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2731
|
+
`Reorder/reparent elements to satisfy placement ${operation.to.placement.description}.`
|
|
2732
|
+
],
|
|
2733
|
+
styleSteps: [
|
|
2734
|
+
`Set ${formatAnchorRef(operation.to.parent)} to grid with explicit template rows/columns for the final layout.`,
|
|
2735
|
+
"Use `gap` for consistent spacing and keep placement structural."
|
|
2736
|
+
],
|
|
2737
|
+
itemSteps: [
|
|
2738
|
+
`Set item alignment on ${formatAnchorRef(operation.subject)} with grid self-alignment (\`justify-self\`/\`align-self\`).`
|
|
2739
|
+
]
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
reasons.push(`${flexDirectionInfo.reason} Use a 1D flex layout instead of literal drag replay.`);
|
|
2743
|
+
let hasStackedCluster = false;
|
|
2744
|
+
const stackedAnchorLabels = /* @__PURE__ */ new Set();
|
|
2745
|
+
if (flexDirection === "row" && subjectSnapshot) {
|
|
2746
|
+
for (const rowPeer of sameRowNodes) {
|
|
2747
|
+
for (const node of childSnapshots) {
|
|
2748
|
+
if (node.child === edit.element || node.child === rowPeer.child) continue;
|
|
2749
|
+
const sameColumnAsPeer = Math.abs(node.centerX - rowPeer.centerX) <= colTolerance;
|
|
2750
|
+
const verticallySeparated = Math.abs(node.centerY - rowPeer.centerY) > rowTolerance;
|
|
2751
|
+
if (sameColumnAsPeer && verticallySeparated) {
|
|
2752
|
+
hasStackedCluster = true;
|
|
2753
|
+
stackedAnchorLabels.add(rowPeer.anchorLabel);
|
|
2754
|
+
stackedAnchorLabels.add(node.anchorLabel);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
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));
|
|
2760
|
+
const refactorSteps = [
|
|
2761
|
+
`Ensure ${formatAnchorRef(operation.subject)} and referenced neighbors share a common container under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2762
|
+
`Reparent/reorder nodes so ${formatAnchorRef(operation.subject)} lands ${operation.to.placement.description}.`
|
|
2763
|
+
];
|
|
2764
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2765
|
+
const clusterSample = Array.from(stackedAnchorLabels).slice(0, 3).join(", ");
|
|
2766
|
+
refactorSteps.push(`Create a left-side content wrapper for vertically stacked items (${clusterSample}), and keep ${formatAnchorRef(operation.subject)} as the opposite-side sibling.`);
|
|
2767
|
+
}
|
|
2768
|
+
if (hasBelowCluster) {
|
|
2769
|
+
refactorSteps.push("Keep lower content sections in a separate block below the horizontal header row; do not force them into the same row.");
|
|
2770
|
+
}
|
|
2771
|
+
const styleSteps = [
|
|
2772
|
+
`Set ${formatAnchorRef(operation.to.parent)} to flex with direction ${flexDirection}.`,
|
|
2773
|
+
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.",
|
|
2774
|
+
"Use `gap` for spacing between siblings."
|
|
2775
|
+
];
|
|
2776
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2777
|
+
styleSteps.push("Set the content wrapper to `display: flex` with `flex-direction: column` and an appropriate vertical gap.");
|
|
2778
|
+
}
|
|
2779
|
+
return {
|
|
2780
|
+
recommendedSystem: "flex",
|
|
2781
|
+
intentPatterns,
|
|
2782
|
+
refactorSteps,
|
|
2783
|
+
styleSteps,
|
|
2784
|
+
itemSteps: [
|
|
2785
|
+
`Apply item-level alignment (\`align-self\` / flex-basis) only when needed for ${formatAnchorRef(operation.subject)}.`,
|
|
2786
|
+
"Do not use absolute positioning, top/left offsets, transforms, or margin hacks to simulate movement."
|
|
2787
|
+
]
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
function buildMoveEntries(edits) {
|
|
2791
|
+
const entries = [];
|
|
2792
|
+
let noopMoveCount = 0;
|
|
2793
|
+
for (const edit of edits) {
|
|
2794
|
+
const move = edit.move;
|
|
2795
|
+
if (!move) continue;
|
|
2796
|
+
const subject = buildAnchorRef(
|
|
2797
|
+
getElementDisplayName(edit.element) || edit.locator.tagName,
|
|
2798
|
+
edit.locator.domSelector,
|
|
2799
|
+
edit.locator.domSource
|
|
2800
|
+
);
|
|
2801
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2802
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2803
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2804
|
+
move.fromSiblingBefore,
|
|
2805
|
+
move.fromSiblingBeforeSelector,
|
|
2806
|
+
move.fromSiblingBeforeSource,
|
|
2807
|
+
move.fromSiblingAfter,
|
|
2808
|
+
move.fromSiblingAfterSelector,
|
|
2809
|
+
move.fromSiblingAfterSource
|
|
2810
|
+
);
|
|
2811
|
+
const toPlacement = buildPlacementFromMove(
|
|
2812
|
+
move.toSiblingBefore,
|
|
2813
|
+
move.toSiblingBeforeSelector,
|
|
2814
|
+
move.toSiblingBeforeSource,
|
|
2815
|
+
move.toSiblingAfter,
|
|
2816
|
+
move.toSiblingAfterSelector,
|
|
2817
|
+
move.toSiblingAfterSource
|
|
2818
|
+
);
|
|
2819
|
+
const reasons = [];
|
|
2820
|
+
const classification = classifyMove(move);
|
|
2821
|
+
if (classification === "noop") {
|
|
2822
|
+
noopMoveCount++;
|
|
2823
|
+
continue;
|
|
2824
|
+
}
|
|
2825
|
+
const interactionMode = move.mode ?? "free";
|
|
2826
|
+
const visualDelta = toRoundedVisualDelta(move);
|
|
2827
|
+
if (visualDelta) {
|
|
2828
|
+
reasons.push(`Non-zero visual delta detected (${visualDelta.x}px, ${visualDelta.y}px).`);
|
|
2829
|
+
}
|
|
2830
|
+
const structuralChange = hasStructuralChange(move);
|
|
2831
|
+
if (structuralChange) reasons.push("Anchor placement changed between source and target.");
|
|
2832
|
+
else reasons.push("No anchor placement change; treating movement as layout intent translation.");
|
|
2833
|
+
const operationBase = {
|
|
2834
|
+
classification,
|
|
2835
|
+
interactionMode,
|
|
2836
|
+
subject,
|
|
2837
|
+
from: { parent: fromParent, placement: fromPlacement },
|
|
2838
|
+
to: { parent: toParent, placement: toPlacement },
|
|
2839
|
+
...visualDelta ? { visualDelta } : {},
|
|
2840
|
+
confidence: classification === "existing_layout_move" ? "high" : structuralChange ? "medium" : "high",
|
|
2841
|
+
reasons
|
|
2842
|
+
};
|
|
2843
|
+
if (classification === "layout_refactor") {
|
|
2844
|
+
operationBase.layoutPrescription = inferLayoutPrescription(edit, operationBase, reasons);
|
|
2845
|
+
}
|
|
2846
|
+
const sortSource = subject.source?.file ? `${subject.source.file}:${subject.source.line ?? 0}:${subject.source.column ?? 0}` : "";
|
|
2847
|
+
const sortKey = [
|
|
2848
|
+
sortSource,
|
|
2849
|
+
anchorKey(subject),
|
|
2850
|
+
anchorKey(toParent),
|
|
2851
|
+
toPlacement.description
|
|
2852
|
+
].join("|");
|
|
2853
|
+
entries.push({ edit, operation: operationBase, sortKey });
|
|
2854
|
+
}
|
|
2855
|
+
entries.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
2856
|
+
return { entries, noopMoveCount };
|
|
2857
|
+
}
|
|
2858
|
+
function buildMovePlanContext(edits, _domContext) {
|
|
2859
|
+
const { entries, noopMoveCount } = buildMoveEntries(edits);
|
|
2860
|
+
if (entries.length === 0) {
|
|
2861
|
+
return {
|
|
2862
|
+
movePlan: null,
|
|
2863
|
+
intentsByEdit: /* @__PURE__ */ new Map(),
|
|
2864
|
+
noopMoveCount
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
const operations = [];
|
|
2868
|
+
const intentsByEdit = /* @__PURE__ */ new Map();
|
|
2869
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2870
|
+
const operationId = `op-${i + 1}`;
|
|
2871
|
+
const operation = { operationId, ...entries[i].operation };
|
|
2872
|
+
operations.push(operation);
|
|
2873
|
+
intentsByEdit.set(entries[i].edit, operation);
|
|
2874
|
+
}
|
|
2875
|
+
const affectedContainerMap = /* @__PURE__ */ new Map();
|
|
2876
|
+
for (const operation of operations) {
|
|
2877
|
+
affectedContainerMap.set(anchorKey(operation.from.parent), operation.from.parent);
|
|
2878
|
+
affectedContainerMap.set(anchorKey(operation.to.parent), operation.to.parent);
|
|
2879
|
+
}
|
|
2880
|
+
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)}.`);
|
|
2881
|
+
const notes = [];
|
|
2882
|
+
if (noopMoveCount > 0) notes.push(`Excluded ${noopMoveCount} no-op move(s).`);
|
|
2883
|
+
if (operations.some((op) => op.classification === "layout_refactor")) {
|
|
2884
|
+
notes.push("Layout refactor operations include best-practice flex/grid prescriptions.");
|
|
2885
|
+
}
|
|
2886
|
+
return {
|
|
2887
|
+
movePlan: {
|
|
2888
|
+
operations,
|
|
2889
|
+
affectedContainers: Array.from(affectedContainerMap.values()),
|
|
2890
|
+
orderingConstraints,
|
|
2891
|
+
notes
|
|
2892
|
+
},
|
|
2893
|
+
intentsByEdit,
|
|
2894
|
+
noopMoveCount
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
function buildMovePlan(edits, domContext) {
|
|
2898
|
+
const context = buildMovePlanContext(edits, domContext);
|
|
2899
|
+
return context.movePlan ?? {
|
|
2900
|
+
operations: [],
|
|
2901
|
+
affectedContainers: [],
|
|
2902
|
+
orderingConstraints: [],
|
|
2903
|
+
notes: context.noopMoveCount > 0 ? [`Excluded ${context.noopMoveCount} no-op move(s).`] : []
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
function getMoveIntentForEdit(edit, context) {
|
|
2907
|
+
if (!edit.move) return null;
|
|
2908
|
+
if (context?.intentsByEdit.has(edit)) return context.intentsByEdit.get(edit) ?? null;
|
|
2909
|
+
const singleContext = buildMovePlanContext([edit]);
|
|
2910
|
+
return singleContext.intentsByEdit.get(edit) ?? null;
|
|
2911
|
+
}
|
|
2912
|
+
function buildMoveInstructionFromIntent(intent) {
|
|
2913
|
+
if (intent.classification === "existing_layout_move") {
|
|
2914
|
+
return `Apply as a structural move in code: place ${formatAnchorRef(intent.subject)} ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`;
|
|
2915
|
+
}
|
|
2916
|
+
const system = intent.layoutPrescription?.recommendedSystem ?? "flex";
|
|
2917
|
+
return `Treat this as a ${system} layout refactor. Implement the listed structure/style steps in source code instead of drag replay.`;
|
|
2918
|
+
}
|
|
2919
|
+
function formatMoveType(classification) {
|
|
2920
|
+
return classification === "existing_layout_move" ? "structural_move" : "layout_refactor";
|
|
2921
|
+
}
|
|
2922
|
+
function buildMoveExportLines(intent) {
|
|
2923
|
+
const moveType = formatMoveType(intent.classification);
|
|
2924
|
+
const implementationSteps = [];
|
|
2925
|
+
if (intent.classification === "existing_layout_move") {
|
|
2926
|
+
implementationSteps.push(`Reorder/reparent ${formatAnchorRef(intent.subject)} to ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`);
|
|
2927
|
+
} else {
|
|
2928
|
+
const prescription = intent.layoutPrescription;
|
|
2929
|
+
if (prescription) {
|
|
2930
|
+
implementationSteps.push(...prescription.refactorSteps);
|
|
2931
|
+
implementationSteps.push(...prescription.styleSteps);
|
|
2932
|
+
implementationSteps.push(...prescription.itemSteps);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
const lines = [
|
|
2291
2936
|
"moved:",
|
|
2292
|
-
`
|
|
2293
|
-
`
|
|
2294
|
-
`
|
|
2295
|
-
`
|
|
2296
|
-
`
|
|
2297
|
-
`
|
|
2298
|
-
`
|
|
2299
|
-
`to_parent_selector: ${formatMoveSelector(move.toParentSelector, "(unknown)")}`,
|
|
2300
|
-
`to_before_selector: ${formatMoveSelector(move.toSiblingBeforeSelector, "(none)")}`,
|
|
2301
|
-
`to_after_selector: ${formatMoveSelector(move.toSiblingAfterSelector, "(none)")}`,
|
|
2302
|
-
`to_parent_source: ${formatMoveSource(move.toParentSource, "(unknown)")}`,
|
|
2303
|
-
`to_before_source: ${formatMoveSource(move.toSiblingBeforeSource, "(none)")}`,
|
|
2304
|
-
`to_after_source: ${formatMoveSource(move.toSiblingAfterSource, "(none)")}`
|
|
2937
|
+
`id: ${intent.operationId}`,
|
|
2938
|
+
`type: ${moveType}`,
|
|
2939
|
+
`subject: ${formatAnchorRef(intent.subject, "(unknown)")}`,
|
|
2940
|
+
`parent: ${formatAnchorRef(intent.to.parent, "(unknown)")}`,
|
|
2941
|
+
`current_anchor: ${intent.from.placement.description}`,
|
|
2942
|
+
`target_anchor: ${intent.to.placement.description}`,
|
|
2943
|
+
...intent.visualDelta ? [`visual_hint: ${intent.visualDelta.x}px horizontal, ${intent.visualDelta.y}px vertical`] : []
|
|
2305
2944
|
];
|
|
2945
|
+
if (intent.layoutPrescription) {
|
|
2946
|
+
lines.push(`recommended_layout: ${intent.layoutPrescription.recommendedSystem}`);
|
|
2947
|
+
}
|
|
2948
|
+
lines.push("implementation_steps:");
|
|
2949
|
+
for (const step of implementationSteps) {
|
|
2950
|
+
lines.push(` - ${step}`);
|
|
2951
|
+
}
|
|
2952
|
+
lines.push("guardrails:");
|
|
2953
|
+
lines.push(" - Do not simulate movement with absolute positioning, left/top offsets, transform, or margin hacks.");
|
|
2954
|
+
lines.push(`instruction: ${buildMoveInstructionFromIntent(intent)}`);
|
|
2955
|
+
return lines;
|
|
2956
|
+
}
|
|
2957
|
+
function getExportContentProfile(edits, comments, movePlanOrContext) {
|
|
2958
|
+
const moveOpCount = movePlanOrContext ? "operations" in movePlanOrContext ? movePlanOrContext.operations.length : movePlanOrContext.movePlan?.operations.length ?? 0 : buildMovePlanContext(edits).movePlan?.operations.length ?? 0;
|
|
2959
|
+
return {
|
|
2960
|
+
hasCssEdits: edits.some((e) => Object.keys(e.pendingStyles).length > 0),
|
|
2961
|
+
hasTextEdits: edits.some((e) => e.textEdit != null),
|
|
2962
|
+
hasMoves: moveOpCount > 0,
|
|
2963
|
+
hasComments: comments.length > 0
|
|
2964
|
+
};
|
|
2306
2965
|
}
|
|
2307
|
-
function
|
|
2966
|
+
function buildExportInstruction(profile) {
|
|
2967
|
+
const { hasCssEdits, hasTextEdits, hasMoves, hasComments } = profile;
|
|
2968
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves && !hasComments) return "";
|
|
2969
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves) {
|
|
2970
|
+
return hasComments ? "Address this feedback on the UI. Use the provided source location and selector to find each element in the codebase." : "";
|
|
2971
|
+
}
|
|
2972
|
+
const parts = [];
|
|
2973
|
+
if (hasCssEdits) parts.push("Apply the CSS changes to the targeted elements using the project's existing styling approach (Tailwind, CSS modules, etc.).");
|
|
2974
|
+
if (hasTextEdits) parts.push("Update the text content as specified.");
|
|
2975
|
+
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.");
|
|
2976
|
+
if (hasComments) parts.push("Address the comments on the relevant elements.");
|
|
2977
|
+
return `${parts.join(" ")} Use the provided source locations, selectors, and context HTML to locate each element in the codebase.`;
|
|
2978
|
+
}
|
|
2979
|
+
function buildSessionExport(edits, comments = [], options) {
|
|
2308
2980
|
const blocks = [];
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2981
|
+
const planContext = options?.movePlanContext ?? buildMovePlanContext(edits);
|
|
2982
|
+
const movePlan = planContext.movePlan;
|
|
2983
|
+
const includeMovePlanHeader = options?.includeMovePlanHeader !== false;
|
|
2984
|
+
if (includeMovePlanHeader && movePlan && movePlan.operations.length > 0) {
|
|
2985
|
+
const planLines = [
|
|
2986
|
+
"=== LAYOUT MOVE PLAN ===",
|
|
2987
|
+
`operations: ${movePlan.operations.length}`
|
|
2988
|
+
];
|
|
2989
|
+
if (movePlan.affectedContainers.length > 0) {
|
|
2990
|
+
planLines.push("containers:");
|
|
2991
|
+
for (const container of movePlan.affectedContainers) {
|
|
2992
|
+
planLines.push(` - ${formatAnchorRef(container, "(unknown)")}`);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
if (movePlan.orderingConstraints.length > 0) {
|
|
2996
|
+
planLines.push("structural_constraints:");
|
|
2997
|
+
for (const constraint of movePlan.orderingConstraints) {
|
|
2998
|
+
planLines.push(` - ${constraint}`);
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
if (movePlan.notes.length > 0) {
|
|
3002
|
+
planLines.push("plan_notes:");
|
|
3003
|
+
for (const note of movePlan.notes) {
|
|
3004
|
+
planLines.push(` - ${note}`);
|
|
3005
|
+
}
|
|
2314
3006
|
}
|
|
2315
|
-
blocks.push(
|
|
3007
|
+
blocks.push(planLines.join("\n"));
|
|
3008
|
+
}
|
|
3009
|
+
for (const edit of edits) {
|
|
3010
|
+
const moveIntent = getMoveIntentForEdit(edit, planContext);
|
|
3011
|
+
const hasMove = Boolean(moveIntent);
|
|
3012
|
+
const hasStyleOrText = Object.keys(edit.pendingStyles).length > 0 || edit.textEdit != null;
|
|
3013
|
+
if (!hasMove && !hasStyleOrText) continue;
|
|
3014
|
+
const block = hasMove ? buildEditExportWithOptions(edit.locator, edit.pendingStyles, edit.textEdit, { skipContext: true }) : buildEditExport(edit.locator, edit.pendingStyles, edit.textEdit);
|
|
3015
|
+
let moveBlock = "";
|
|
3016
|
+
if (moveIntent) {
|
|
3017
|
+
moveBlock = `
|
|
3018
|
+
${buildMoveExportLines(moveIntent).join("\n")}`;
|
|
3019
|
+
}
|
|
3020
|
+
blocks.push(block + moveBlock);
|
|
2316
3021
|
}
|
|
2317
3022
|
for (const comment of comments) {
|
|
2318
3023
|
blocks.push(buildCommentExport(comment.locator, comment.text, comment.replies));
|
|
@@ -2327,6 +3032,9 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
|
|
|
2327
3032
|
buildCommentExport,
|
|
2328
3033
|
buildEditExport,
|
|
2329
3034
|
buildElementContext,
|
|
3035
|
+
buildExportInstruction,
|
|
3036
|
+
buildMovePlan,
|
|
3037
|
+
buildMovePlanContext,
|
|
2330
3038
|
buildSessionExport,
|
|
2331
3039
|
calculateDropPosition,
|
|
2332
3040
|
calculateElementMeasurements,
|
|
@@ -2338,17 +3046,20 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
|
|
|
2338
3046
|
colorPropertyToCSSMap,
|
|
2339
3047
|
colorToTailwind,
|
|
2340
3048
|
computeHoverHighlight,
|
|
3049
|
+
computeIntendedIndex,
|
|
2341
3050
|
detectChildrenDirection,
|
|
2342
3051
|
detectSizingMode,
|
|
2343
3052
|
elementFromPointWithoutOverlays,
|
|
2344
3053
|
ensureDirectTextSpanAtPoint,
|
|
2345
3054
|
findChildAtPoint,
|
|
2346
3055
|
findContainerAtPoint,
|
|
3056
|
+
findLayoutContainerAtPoint,
|
|
2347
3057
|
findTextOwnerAtPoint,
|
|
2348
3058
|
findTextOwnerByRangeScan,
|
|
2349
3059
|
flexPropertyToCSSMap,
|
|
2350
3060
|
formatPropertyValue,
|
|
2351
3061
|
getAllComputedStyles,
|
|
3062
|
+
getChildBriefInfo,
|
|
2352
3063
|
getComputedBorderStyles,
|
|
2353
3064
|
getComputedBoxShadow,
|
|
2354
3065
|
getComputedColorStyles,
|
|
@@ -2359,11 +3070,17 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
|
|
|
2359
3070
|
getElementDisplayName,
|
|
2360
3071
|
getElementInfo,
|
|
2361
3072
|
getElementLocator,
|
|
3073
|
+
getElementSource,
|
|
3074
|
+
getExportContentProfile,
|
|
2362
3075
|
getFlexDirection,
|
|
3076
|
+
getMoveIntentForEdit,
|
|
2363
3077
|
getOriginalInlineStyles,
|
|
3078
|
+
getSelectionColors,
|
|
2364
3079
|
getSizingValue,
|
|
2365
3080
|
isFlexContainer,
|
|
3081
|
+
isInFlowChild,
|
|
2366
3082
|
isInputFocused,
|
|
3083
|
+
isLayoutContainer,
|
|
2367
3084
|
isTextElement,
|
|
2368
3085
|
parseColorValue,
|
|
2369
3086
|
parsePropertyValue,
|