made-refine 0.2.11 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +411 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +411 -62
- package/dist/index.mjs.map +1 -1
- package/dist/{utils-Dn_oW8f_.d.mts → utils-lksVP2Wq.d.mts} +14 -1
- package/dist/{utils-Dn_oW8f_.d.ts → utils-lksVP2Wq.d.ts} +14 -1
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +222 -20
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +220 -20
- package/dist/utils.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -117,6 +117,225 @@ function getZoomScale() {
|
|
|
117
117
|
return snap.active ? snap.zoom : 1;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// src/utils/react-fiber.ts
|
|
121
|
+
function getFiberForElement(element) {
|
|
122
|
+
if (typeof window !== "undefined") {
|
|
123
|
+
const devtools = window.__DIRECT_EDIT_DEVTOOLS__;
|
|
124
|
+
if (devtools?.getFiberForElement) {
|
|
125
|
+
const fiber = devtools.getFiberForElement(element);
|
|
126
|
+
if (fiber) return fiber;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const fiberKey = Object.keys(element).find(
|
|
130
|
+
(key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")
|
|
131
|
+
);
|
|
132
|
+
if (!fiberKey) return null;
|
|
133
|
+
return element[fiberKey] || null;
|
|
134
|
+
}
|
|
135
|
+
function getSourceFromFiber(fiber) {
|
|
136
|
+
const debugSource = fiber?._debugSource;
|
|
137
|
+
if (debugSource?.fileName) return debugSource;
|
|
138
|
+
const owner = fiber?._debugOwner;
|
|
139
|
+
const ownerPending = owner?.pendingProps?.__source;
|
|
140
|
+
if (ownerPending?.fileName) return ownerPending;
|
|
141
|
+
const ownerMemo = owner?.memoizedProps?.__source;
|
|
142
|
+
if (ownerMemo?.fileName) return ownerMemo;
|
|
143
|
+
const pending = fiber?.pendingProps?.__source;
|
|
144
|
+
if (pending?.fileName) return pending;
|
|
145
|
+
const memo = fiber?.memoizedProps?.__source;
|
|
146
|
+
if (memo?.fileName) return memo;
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
function buildFrame(fiber) {
|
|
150
|
+
const type = fiber?.type;
|
|
151
|
+
if (typeof type !== "function" && typeof type !== "object") return null;
|
|
152
|
+
const name = type?.displayName || type?.name || null;
|
|
153
|
+
if (!name || name === "Fragment") return null;
|
|
154
|
+
const frame = { name };
|
|
155
|
+
const source = getSourceFromFiber(fiber);
|
|
156
|
+
if (source?.fileName) {
|
|
157
|
+
frame.file = source.fileName;
|
|
158
|
+
if (typeof source.lineNumber === "number") {
|
|
159
|
+
frame.line = source.lineNumber;
|
|
160
|
+
}
|
|
161
|
+
if (typeof source.columnNumber === "number") {
|
|
162
|
+
frame.column = source.columnNumber;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return frame;
|
|
166
|
+
}
|
|
167
|
+
function shouldIncludeFrame(frame, lastFrame) {
|
|
168
|
+
if (!lastFrame) return true;
|
|
169
|
+
if (frame.name !== lastFrame.name) return true;
|
|
170
|
+
if (!lastFrame.file && frame.file) return true;
|
|
171
|
+
if (lastFrame.file && frame.file && lastFrame.line == null && frame.line != null) return true;
|
|
172
|
+
if (lastFrame.file && frame.file && lastFrame.line != null && frame.line != null && lastFrame.column == null && frame.column != null) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
function getOwnerStack(fiber) {
|
|
178
|
+
const frames = [];
|
|
179
|
+
let current = fiber;
|
|
180
|
+
let lastFrame = null;
|
|
181
|
+
let nearestComponentFiber = null;
|
|
182
|
+
while (current) {
|
|
183
|
+
const frame = buildFrame(current);
|
|
184
|
+
if (frame && shouldIncludeFrame(frame, lastFrame)) {
|
|
185
|
+
frames.push(frame);
|
|
186
|
+
lastFrame = frame;
|
|
187
|
+
if (!nearestComponentFiber) {
|
|
188
|
+
nearestComponentFiber = current;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
current = current._debugOwner;
|
|
192
|
+
}
|
|
193
|
+
return { frames, nearestComponentFiber };
|
|
194
|
+
}
|
|
195
|
+
function getRenderStack(fiber) {
|
|
196
|
+
const frames = [];
|
|
197
|
+
let current = fiber;
|
|
198
|
+
let lastFrame = null;
|
|
199
|
+
let nearestComponentFiber = null;
|
|
200
|
+
while (current) {
|
|
201
|
+
const frame = buildFrame(current);
|
|
202
|
+
if (frame && shouldIncludeFrame(frame, lastFrame)) {
|
|
203
|
+
frames.push(frame);
|
|
204
|
+
lastFrame = frame;
|
|
205
|
+
if (!nearestComponentFiber) {
|
|
206
|
+
nearestComponentFiber = current;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
current = current.return;
|
|
210
|
+
}
|
|
211
|
+
return { frames, nearestComponentFiber };
|
|
212
|
+
}
|
|
213
|
+
function getReactComponentInfo(element) {
|
|
214
|
+
const fiber = getFiberForElement(element);
|
|
215
|
+
if (!fiber) return { frames: [], nearestComponentFiber: null };
|
|
216
|
+
const elementSource = getSourceFromFiber(fiber);
|
|
217
|
+
const elementSourceFile = elementSource?.fileName || void 0;
|
|
218
|
+
const ownerResult = getOwnerStack(fiber);
|
|
219
|
+
if (ownerResult.frames.length > 0) {
|
|
220
|
+
return { ...ownerResult, elementSourceFile };
|
|
221
|
+
}
|
|
222
|
+
return { ...getRenderStack(fiber), elementSourceFile };
|
|
223
|
+
}
|
|
224
|
+
var EXCLUDED_PROP_KEYS = /* @__PURE__ */ new Set([
|
|
225
|
+
"className",
|
|
226
|
+
"style",
|
|
227
|
+
"children",
|
|
228
|
+
"ref",
|
|
229
|
+
"key",
|
|
230
|
+
"render"
|
|
231
|
+
]);
|
|
232
|
+
function serializePropValue(value) {
|
|
233
|
+
if (typeof value === "function") return "[function]";
|
|
234
|
+
if (typeof value === "symbol") return void 0;
|
|
235
|
+
if (value === void 0) return void 0;
|
|
236
|
+
if (value !== null && typeof value === "object") {
|
|
237
|
+
if ("$$typeof" in value) return "[element]";
|
|
238
|
+
try {
|
|
239
|
+
JSON.stringify(value);
|
|
240
|
+
return value;
|
|
241
|
+
} catch {
|
|
242
|
+
return "[object]";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return value;
|
|
246
|
+
}
|
|
247
|
+
function getComponentProps(fiber) {
|
|
248
|
+
const props = fiber?.memoizedProps ?? fiber?.pendingProps;
|
|
249
|
+
if (!props || typeof props !== "object") return {};
|
|
250
|
+
const result = {};
|
|
251
|
+
for (const [key, value] of Object.entries(props)) {
|
|
252
|
+
if (EXCLUDED_PROP_KEYS.has(key)) continue;
|
|
253
|
+
if (key.startsWith("data-")) continue;
|
|
254
|
+
const serialized = serializePropValue(value);
|
|
255
|
+
if (serialized !== void 0) {
|
|
256
|
+
result[key] = serialized;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
function getCallSiteSource(fiber) {
|
|
262
|
+
const source = fiber?._debugSource;
|
|
263
|
+
if (source?.fileName) {
|
|
264
|
+
return {
|
|
265
|
+
file: source.fileName,
|
|
266
|
+
line: typeof source.lineNumber === "number" ? source.lineNumber : void 0,
|
|
267
|
+
column: typeof source.columnNumber === "number" ? source.columnNumber : void 0
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const pending = fiber?.pendingProps?.__source;
|
|
271
|
+
if (pending?.fileName) {
|
|
272
|
+
return {
|
|
273
|
+
file: pending.fileName,
|
|
274
|
+
line: typeof pending.lineNumber === "number" ? pending.lineNumber : void 0,
|
|
275
|
+
column: typeof pending.columnNumber === "number" ? pending.columnNumber : void 0
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
function deriveDefinitionSource(frames) {
|
|
281
|
+
for (const frame of frames) {
|
|
282
|
+
if (frame.file && isComponentPrimitivePath(frame.file)) {
|
|
283
|
+
return { file: frame.file, line: frame.line, column: frame.column };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
var PRIMITIVE_PATH_PATTERNS = [
|
|
289
|
+
/\/components\/ui\//,
|
|
290
|
+
/\/ui\/primitives\//,
|
|
291
|
+
/\/design-system\//
|
|
292
|
+
];
|
|
293
|
+
var PRIMITIVE_NPM_PATTERNS = [
|
|
294
|
+
/@base-ui\//,
|
|
295
|
+
/@radix-ui\//,
|
|
296
|
+
/@headlessui\//,
|
|
297
|
+
/@chakra-ui\//,
|
|
298
|
+
/@mantine\//,
|
|
299
|
+
/@mui\//,
|
|
300
|
+
/@ark-ui\//
|
|
301
|
+
];
|
|
302
|
+
var FRAMEWORK_EXCLUSION_PATTERNS = [
|
|
303
|
+
/node_modules\/react\//,
|
|
304
|
+
/node_modules\/react-dom\//,
|
|
305
|
+
/node_modules\/next\/dist\//,
|
|
306
|
+
/node_modules\/scheduler\//,
|
|
307
|
+
/node_modules\/react-server\//
|
|
308
|
+
];
|
|
309
|
+
function isComponentPrimitivePath(filePath) {
|
|
310
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
311
|
+
for (const pattern of FRAMEWORK_EXCLUSION_PATTERNS) {
|
|
312
|
+
if (pattern.test(normalized)) return false;
|
|
313
|
+
}
|
|
314
|
+
for (const pattern of PRIMITIVE_NPM_PATTERNS) {
|
|
315
|
+
if (pattern.test(normalized)) return true;
|
|
316
|
+
}
|
|
317
|
+
for (const pattern of PRIMITIVE_PATH_PATTERNS) {
|
|
318
|
+
if (pattern.test(normalized)) return true;
|
|
319
|
+
}
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
function classifyComponentFiber(fiber, frames, elementSourceFile) {
|
|
323
|
+
if (elementSourceFile && isComponentPrimitivePath(elementSourceFile)) {
|
|
324
|
+
return { isComponentPrimitive: true };
|
|
325
|
+
}
|
|
326
|
+
if (!fiber) return { isComponentPrimitive: false };
|
|
327
|
+
const callSite = getCallSiteSource(fiber);
|
|
328
|
+
if (callSite?.file && isComponentPrimitivePath(callSite.file)) {
|
|
329
|
+
return { isComponentPrimitive: true };
|
|
330
|
+
}
|
|
331
|
+
for (const frame of frames) {
|
|
332
|
+
if (frame.file && isComponentPrimitivePath(frame.file)) {
|
|
333
|
+
return { isComponentPrimitive: true };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return { isComponentPrimitive: false };
|
|
337
|
+
}
|
|
338
|
+
|
|
120
339
|
// src/utils.ts
|
|
121
340
|
function clamp(value, min, max) {
|
|
122
341
|
if (!Number.isFinite(value)) return min;
|
|
@@ -1504,7 +1723,7 @@ function calculateDropPosition(container, pointerX, pointerY, draggedElement) {
|
|
|
1504
1723
|
};
|
|
1505
1724
|
return { insertBefore, indicator };
|
|
1506
1725
|
}
|
|
1507
|
-
function
|
|
1726
|
+
function getFiberForElement2(element) {
|
|
1508
1727
|
if (typeof window !== "undefined") {
|
|
1509
1728
|
const devtools = window.__DIRECT_EDIT_DEVTOOLS__;
|
|
1510
1729
|
if (devtools?.getFiberForElement) {
|
|
@@ -1765,7 +1984,7 @@ function getSourceFromDebugStack(fiber) {
|
|
|
1765
1984
|
}
|
|
1766
1985
|
return null;
|
|
1767
1986
|
}
|
|
1768
|
-
function
|
|
1987
|
+
function getSourceFromFiber2(fiber) {
|
|
1769
1988
|
const debugSource = fiber?._debugSource;
|
|
1770
1989
|
if (debugSource?.fileName) return debugSource;
|
|
1771
1990
|
const owner = fiber?._debugOwner;
|
|
@@ -1781,13 +2000,13 @@ function getSourceFromFiber(fiber) {
|
|
|
1781
2000
|
if (fromDebugStack?.fileName) return fromDebugStack;
|
|
1782
2001
|
return null;
|
|
1783
2002
|
}
|
|
1784
|
-
function
|
|
2003
|
+
function buildFrame2(fiber) {
|
|
1785
2004
|
const type = fiber?.type;
|
|
1786
2005
|
if (typeof type !== "function" && typeof type !== "object") return null;
|
|
1787
2006
|
const name = type?.displayName || type?.name || null;
|
|
1788
2007
|
if (!name || name === "Fragment") return null;
|
|
1789
2008
|
const frame = { name };
|
|
1790
|
-
const source =
|
|
2009
|
+
const source = getSourceFromFiber2(fiber);
|
|
1791
2010
|
if (source?.fileName) {
|
|
1792
2011
|
frame.file = source.fileName;
|
|
1793
2012
|
if (typeof source.lineNumber === "number") {
|
|
@@ -1799,7 +2018,7 @@ function buildFrame(fiber) {
|
|
|
1799
2018
|
}
|
|
1800
2019
|
return frame;
|
|
1801
2020
|
}
|
|
1802
|
-
function
|
|
2021
|
+
function shouldIncludeFrame2(frame, lastFrame) {
|
|
1803
2022
|
if (!lastFrame) return true;
|
|
1804
2023
|
if (frame.name !== lastFrame.name) return true;
|
|
1805
2024
|
if (!lastFrame.file && frame.file) return true;
|
|
@@ -1809,42 +2028,52 @@ function shouldIncludeFrame(frame, lastFrame) {
|
|
|
1809
2028
|
}
|
|
1810
2029
|
return false;
|
|
1811
2030
|
}
|
|
1812
|
-
function
|
|
2031
|
+
function getOwnerStack2(fiber) {
|
|
1813
2032
|
const frames = [];
|
|
1814
2033
|
let current = fiber;
|
|
1815
2034
|
let lastFrame = null;
|
|
2035
|
+
let nearestComponentFiber = null;
|
|
1816
2036
|
while (current) {
|
|
1817
|
-
const frame =
|
|
1818
|
-
if (frame &&
|
|
2037
|
+
const frame = buildFrame2(current);
|
|
2038
|
+
if (frame && shouldIncludeFrame2(frame, lastFrame)) {
|
|
1819
2039
|
frames.push(frame);
|
|
1820
2040
|
lastFrame = frame;
|
|
2041
|
+
if (!nearestComponentFiber) {
|
|
2042
|
+
nearestComponentFiber = current;
|
|
2043
|
+
}
|
|
1821
2044
|
}
|
|
1822
2045
|
current = current._debugOwner;
|
|
1823
2046
|
}
|
|
1824
|
-
return frames;
|
|
2047
|
+
return { frames, nearestComponentFiber };
|
|
1825
2048
|
}
|
|
1826
|
-
function
|
|
2049
|
+
function getRenderStack2(fiber) {
|
|
1827
2050
|
const frames = [];
|
|
1828
2051
|
let current = fiber;
|
|
1829
2052
|
let lastFrame = null;
|
|
2053
|
+
let nearestComponentFiber = null;
|
|
1830
2054
|
while (current) {
|
|
1831
|
-
const frame =
|
|
1832
|
-
if (frame &&
|
|
2055
|
+
const frame = buildFrame2(current);
|
|
2056
|
+
if (frame && shouldIncludeFrame2(frame, lastFrame)) {
|
|
1833
2057
|
frames.push(frame);
|
|
1834
2058
|
lastFrame = frame;
|
|
2059
|
+
if (!nearestComponentFiber) {
|
|
2060
|
+
nearestComponentFiber = current;
|
|
2061
|
+
}
|
|
1835
2062
|
}
|
|
1836
2063
|
current = current.return;
|
|
1837
2064
|
}
|
|
1838
|
-
return frames;
|
|
2065
|
+
return { frames, nearestComponentFiber };
|
|
1839
2066
|
}
|
|
1840
|
-
function
|
|
1841
|
-
const fiber =
|
|
1842
|
-
if (!fiber) return [];
|
|
1843
|
-
const
|
|
1844
|
-
|
|
1845
|
-
|
|
2067
|
+
function getReactComponentInfo2(element) {
|
|
2068
|
+
const fiber = getFiberForElement2(element);
|
|
2069
|
+
if (!fiber) return { frames: [], nearestComponentFiber: null };
|
|
2070
|
+
const elementSource = getSourceFromFiber2(fiber);
|
|
2071
|
+
const elementSourceFile = elementSource?.fileName || void 0;
|
|
2072
|
+
const ownerResult = getOwnerStack2(fiber);
|
|
2073
|
+
if (ownerResult.frames.length > 0) {
|
|
2074
|
+
return { ...ownerResult, elementSourceFile };
|
|
1846
2075
|
}
|
|
1847
|
-
return
|
|
2076
|
+
return { ...getRenderStack2(fiber), elementSourceFile };
|
|
1848
2077
|
}
|
|
1849
2078
|
function getElementDisplayName(element) {
|
|
1850
2079
|
const tag = element.tagName.toLowerCase();
|
|
@@ -2139,14 +2368,35 @@ function parseDomSource(element) {
|
|
|
2139
2368
|
}
|
|
2140
2369
|
return { file, line, column };
|
|
2141
2370
|
}
|
|
2371
|
+
var MAX_SUB_ELEMENT_SOURCES = 20;
|
|
2372
|
+
function collectSubElementSources(element) {
|
|
2373
|
+
const sources = {};
|
|
2374
|
+
const children = element.querySelectorAll("[data-direct-edit-source]");
|
|
2375
|
+
const labelCounts = /* @__PURE__ */ new Map();
|
|
2376
|
+
let count = 0;
|
|
2377
|
+
for (const child of children) {
|
|
2378
|
+
if (count >= MAX_SUB_ELEMENT_SOURCES) break;
|
|
2379
|
+
if (!(child instanceof HTMLElement)) continue;
|
|
2380
|
+
const source = parseDomSource(child);
|
|
2381
|
+
if (!source) continue;
|
|
2382
|
+
const text = ((child.innerText || child.textContent) ?? "").trim();
|
|
2383
|
+
let baseLabel = text.length > 0 && text.length <= 30 ? text.slice(0, 30).toLowerCase().replace(/\s+/g, "_") : child.tagName.toLowerCase();
|
|
2384
|
+
const existing = labelCounts.get(baseLabel) ?? 0;
|
|
2385
|
+
labelCounts.set(baseLabel, existing + 1);
|
|
2386
|
+
const label = existing > 0 ? `${baseLabel}_${existing + 1}` : baseLabel;
|
|
2387
|
+
sources[label] = source;
|
|
2388
|
+
count++;
|
|
2389
|
+
}
|
|
2390
|
+
return sources;
|
|
2391
|
+
}
|
|
2142
2392
|
function getElementSource(element) {
|
|
2143
2393
|
const domSource = parseDomSource(element);
|
|
2144
2394
|
if (domSource) return domSource;
|
|
2145
2395
|
const seenFibers = /* @__PURE__ */ new Set();
|
|
2146
|
-
let fiber =
|
|
2396
|
+
let fiber = getFiberForElement2(element);
|
|
2147
2397
|
while (fiber && !seenFibers.has(fiber)) {
|
|
2148
2398
|
seenFibers.add(fiber);
|
|
2149
|
-
const fiberSource =
|
|
2399
|
+
const fiberSource = getSourceFromFiber2(fiber);
|
|
2150
2400
|
if (fiberSource?.fileName) {
|
|
2151
2401
|
return {
|
|
2152
2402
|
file: fiberSource.fileName,
|
|
@@ -2161,8 +2411,15 @@ function getElementSource(element) {
|
|
|
2161
2411
|
function getElementLocator(element) {
|
|
2162
2412
|
const elementInfo = getElementInfo(element);
|
|
2163
2413
|
const domSource = getElementSource(element);
|
|
2414
|
+
const { frames, nearestComponentFiber, elementSourceFile } = getReactComponentInfo2(element);
|
|
2415
|
+
const componentName = nearestComponentFiber?.type?.displayName || nearestComponentFiber?.type?.name || void 0;
|
|
2416
|
+
const authoredProps = nearestComponentFiber ? getComponentProps(nearestComponentFiber) : void 0;
|
|
2417
|
+
const classification = classifyComponentFiber(nearestComponentFiber, frames, elementSourceFile);
|
|
2418
|
+
const callSite = nearestComponentFiber ? getCallSiteSource(nearestComponentFiber) : null;
|
|
2419
|
+
const definitionSrc = classification.isComponentPrimitive ? deriveDefinitionSource(frames) : null;
|
|
2420
|
+
const subSources = collectSubElementSources(element);
|
|
2164
2421
|
return {
|
|
2165
|
-
reactStack:
|
|
2422
|
+
reactStack: frames,
|
|
2166
2423
|
domSelector: buildDomSelector(element),
|
|
2167
2424
|
domContextHtml: buildDomContextHtml(element),
|
|
2168
2425
|
targetHtml: buildTargetHtml(element),
|
|
@@ -2170,36 +2427,80 @@ function getElementLocator(element) {
|
|
|
2170
2427
|
tagName: elementInfo.tagName,
|
|
2171
2428
|
id: elementInfo.id,
|
|
2172
2429
|
classList: elementInfo.classList,
|
|
2173
|
-
domSource: domSource ?? void 0
|
|
2430
|
+
domSource: domSource ?? void 0,
|
|
2431
|
+
reactComponentName: componentName,
|
|
2432
|
+
authoredProps: authoredProps && Object.keys(authoredProps).length > 0 ? authoredProps : void 0,
|
|
2433
|
+
subElementSources: Object.keys(subSources).length > 0 ? subSources : void 0,
|
|
2434
|
+
callSiteSource: callSite ?? void 0,
|
|
2435
|
+
definitionSource: definitionSrc ?? void 0,
|
|
2436
|
+
isComponentPrimitive: nearestComponentFiber || elementSourceFile ? classification.isComponentPrimitive : void 0
|
|
2174
2437
|
};
|
|
2175
2438
|
}
|
|
2176
2439
|
function getLocatorHeader(locator) {
|
|
2177
2440
|
const primaryFrame = getPrimaryFrame(locator);
|
|
2178
|
-
const componentLabel =
|
|
2179
|
-
|
|
2180
|
-
|
|
2441
|
+
const componentLabel = locator.reactComponentName ?? primaryFrame?.name ?? locator.tagName;
|
|
2442
|
+
let formattedSource;
|
|
2443
|
+
if (locator.isComponentPrimitive && locator.definitionSource?.file) {
|
|
2444
|
+
formattedSource = formatSourceLocation(
|
|
2445
|
+
locator.definitionSource.file,
|
|
2446
|
+
locator.definitionSource.line,
|
|
2447
|
+
locator.definitionSource.column
|
|
2448
|
+
);
|
|
2449
|
+
} else {
|
|
2450
|
+
formattedSource = locator.domSource?.file ? formatSourceLocation(locator.domSource.file, locator.domSource.line, locator.domSource.column) : primaryFrame?.file ? formatSourceLocation(primaryFrame.file, primaryFrame.line, primaryFrame.column) : null;
|
|
2451
|
+
}
|
|
2452
|
+
const formattedCallSite = locator.callSiteSource?.file ? formatSourceLocation(locator.callSiteSource.file, locator.callSiteSource.line, locator.callSiteSource.column) : null;
|
|
2453
|
+
return { componentLabel, formattedSource, formattedCallSite };
|
|
2454
|
+
}
|
|
2455
|
+
function formatComponentTree(reactStack) {
|
|
2456
|
+
const names = reactStack.map((f) => f.name).filter(Boolean);
|
|
2457
|
+
if (names.length === 0) return null;
|
|
2458
|
+
if (names.length === 1) return names[0];
|
|
2459
|
+
const [component, ...ancestors] = names;
|
|
2460
|
+
return `${component} (in ${ancestors.join(" > ")})`;
|
|
2181
2461
|
}
|
|
2182
2462
|
function buildLocatorContextLines(locator, options) {
|
|
2183
2463
|
const lines = [];
|
|
2184
|
-
const { componentLabel, formattedSource } = getLocatorHeader(locator);
|
|
2464
|
+
const { componentLabel, formattedSource, formattedCallSite } = getLocatorHeader(locator);
|
|
2185
2465
|
const target = (locator.targetHtml || locator.domContextHtml || "").trim();
|
|
2186
2466
|
const context = locator.domContextHtml?.trim() || "";
|
|
2187
|
-
const
|
|
2467
|
+
const path = locator.domSelector?.trim();
|
|
2188
2468
|
const text = locator.textPreview?.trim();
|
|
2189
2469
|
lines.push(`@<${componentLabel}>`);
|
|
2190
2470
|
lines.push("");
|
|
2471
|
+
const tree = formatComponentTree(locator.reactStack);
|
|
2472
|
+
if (tree) {
|
|
2473
|
+
lines.push(`react: ${tree}`);
|
|
2474
|
+
}
|
|
2475
|
+
if (locator.authoredProps && Object.keys(locator.authoredProps).length > 0) {
|
|
2476
|
+
lines.push(`props: ${JSON.stringify(locator.authoredProps)}`);
|
|
2477
|
+
}
|
|
2478
|
+
if (locator.isComponentPrimitive != null) {
|
|
2479
|
+
lines.push(`type: ${locator.isComponentPrimitive ? "component" : "instance"}`);
|
|
2480
|
+
}
|
|
2481
|
+
lines.push(`source: ${formattedSource ?? "(file not available)"}`);
|
|
2482
|
+
if (formattedCallSite && formattedCallSite !== formattedSource) {
|
|
2483
|
+
lines.push(`call-site: ${formattedCallSite}`);
|
|
2484
|
+
}
|
|
2485
|
+
if (locator.subElementSources && Object.keys(locator.subElementSources).length > 0) {
|
|
2486
|
+
lines.push("source-map:");
|
|
2487
|
+
for (const [label, source] of Object.entries(locator.subElementSources)) {
|
|
2488
|
+
lines.push(` - ${label}: ${formatSourceLocation(source.file, source.line, source.column)}`);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
if (path) {
|
|
2492
|
+
lines.push(`path: ${path}`);
|
|
2493
|
+
}
|
|
2191
2494
|
if (target) {
|
|
2495
|
+
lines.push("");
|
|
2192
2496
|
lines.push("target:");
|
|
2193
2497
|
lines.push(target);
|
|
2194
2498
|
}
|
|
2195
2499
|
if (!options?.skipContext && context && context !== target) {
|
|
2500
|
+
lines.push("");
|
|
2196
2501
|
lines.push("context:");
|
|
2197
2502
|
lines.push(context);
|
|
2198
2503
|
}
|
|
2199
|
-
lines.push(`in ${formattedSource ?? "(file not available)"}`);
|
|
2200
|
-
if (selector) {
|
|
2201
|
-
lines.push(`selector: ${selector}`);
|
|
2202
|
-
}
|
|
2203
2504
|
if (text) {
|
|
2204
2505
|
lines.push(`text: ${text}`);
|
|
2205
2506
|
}
|
|
@@ -2900,7 +3201,7 @@ function buildExportInstruction(profile) {
|
|
|
2900
3201
|
return hasComments ? "Address this feedback on the UI. Use the provided source location and selector to find each element in the codebase." : "";
|
|
2901
3202
|
}
|
|
2902
3203
|
const parts = [];
|
|
2903
|
-
if (hasCssEdits) parts.push("Apply the CSS changes to the targeted elements using the project's existing styling approach (Tailwind, CSS modules, etc.).");
|
|
3204
|
+
if (hasCssEdits) parts.push("Apply the CSS changes to the targeted elements using the project's existing styling approach (Tailwind, CSS modules, etc.). Map values to existing CSS variables, design tokens, or utility classes already used in the project whenever possible.");
|
|
2904
3205
|
if (hasTextEdits) parts.push("Update the text content as specified.");
|
|
2905
3206
|
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.");
|
|
2906
3207
|
if (hasComments) parts.push("Address the comments on the relevant elements.");
|
|
@@ -3612,11 +3913,6 @@ function useStyleUpdaters({
|
|
|
3612
3913
|
import * as React3 from "react";
|
|
3613
3914
|
|
|
3614
3915
|
// src/clipboard.ts
|
|
3615
|
-
function buildAgentClipboardText(markdown) {
|
|
3616
|
-
return `implement the visual edits
|
|
3617
|
-
|
|
3618
|
-
${markdown}`;
|
|
3619
|
-
}
|
|
3620
3916
|
function tryRestoreFocus(element) {
|
|
3621
3917
|
if (!(element instanceof HTMLElement)) return;
|
|
3622
3918
|
try {
|
|
@@ -3949,6 +4245,8 @@ function useSessionManager({
|
|
|
3949
4245
|
const originalStyles = options?.originalStyles ?? existingEdit?.originalStyles ?? getOriginalInlineStyles(nextSingleElement);
|
|
3950
4246
|
const pendingStyles = options?.pendingStyles ?? existingEdit?.pendingStyles ?? {};
|
|
3951
4247
|
const elementInfo = getElementInfo(nextSingleElement);
|
|
4248
|
+
const { frames, nearestComponentFiber, elementSourceFile } = getReactComponentInfo(nextSingleElement);
|
|
4249
|
+
const isPrimitive = classifyComponentFiber(nearestComponentFiber, frames, elementSourceFile).isComponentPrimitive;
|
|
3952
4250
|
setState((prev) => ({
|
|
3953
4251
|
comments: prev.activeCommentId ? prev.comments.filter((comment) => {
|
|
3954
4252
|
if (comment.id !== prev.activeCommentId) return true;
|
|
@@ -3967,6 +4265,7 @@ function useSessionManager({
|
|
|
3967
4265
|
computedColor: computed.color,
|
|
3968
4266
|
computedBoxShadow: computed.boxShadow,
|
|
3969
4267
|
computedTypography: computed.typography,
|
|
4268
|
+
isComponentPrimitive: isPrimitive,
|
|
3970
4269
|
originalStyles,
|
|
3971
4270
|
pendingStyles,
|
|
3972
4271
|
editModeActive: prev.editModeActive,
|
|
@@ -3997,6 +4296,7 @@ function useSessionManager({
|
|
|
3997
4296
|
computedColor: null,
|
|
3998
4297
|
computedBoxShadow: null,
|
|
3999
4298
|
computedTypography: null,
|
|
4299
|
+
isComponentPrimitive: false,
|
|
4000
4300
|
originalStyles: {},
|
|
4001
4301
|
pendingStyles: {},
|
|
4002
4302
|
activeCommentId: null,
|
|
@@ -4537,6 +4837,7 @@ function useSessionManager({
|
|
|
4537
4837
|
computedColor: computed.color,
|
|
4538
4838
|
computedBoxShadow: computed.boxShadow,
|
|
4539
4839
|
computedTypography: computed.typography,
|
|
4840
|
+
isComponentPrimitive: prev.isComponentPrimitive,
|
|
4540
4841
|
originalStyles: styleState.originalStyles,
|
|
4541
4842
|
pendingStyles: styleState.pendingStyles,
|
|
4542
4843
|
editModeActive: prev.editModeActive,
|
|
@@ -5314,7 +5615,15 @@ async function checkAgentConnection() {
|
|
|
5314
5615
|
}
|
|
5315
5616
|
|
|
5316
5617
|
// src/use-agent-comms.ts
|
|
5618
|
+
function withInstruction(profile, markdown) {
|
|
5619
|
+
const instruction = buildExportInstruction(profile);
|
|
5620
|
+
return instruction ? `${instruction}
|
|
5621
|
+
|
|
5622
|
+
${markdown}` : markdown;
|
|
5623
|
+
}
|
|
5317
5624
|
function buildLocatorPayload(locator) {
|
|
5625
|
+
const { componentLabel, formattedSource, formattedCallSite } = getLocatorHeader(locator);
|
|
5626
|
+
const reactTree = formatComponentTree(locator.reactStack);
|
|
5318
5627
|
return {
|
|
5319
5628
|
element: {
|
|
5320
5629
|
tagName: locator.tagName,
|
|
@@ -5322,10 +5631,22 @@ function buildLocatorPayload(locator) {
|
|
|
5322
5631
|
classList: locator.classList,
|
|
5323
5632
|
domSelector: locator.domSelector,
|
|
5324
5633
|
targetHtml: locator.targetHtml,
|
|
5634
|
+
contextHtml: locator.domContextHtml || null,
|
|
5325
5635
|
textPreview: locator.textPreview
|
|
5326
5636
|
},
|
|
5327
|
-
|
|
5328
|
-
|
|
5637
|
+
componentLabel,
|
|
5638
|
+
reactTree,
|
|
5639
|
+
reactStack: locator.reactStack,
|
|
5640
|
+
reactComponentName: locator.reactComponentName ?? null,
|
|
5641
|
+
authoredProps: locator.authoredProps ?? null,
|
|
5642
|
+
type: locator.isComponentPrimitive != null ? locator.isComponentPrimitive ? "component" : "instance" : null,
|
|
5643
|
+
isComponentPrimitive: locator.isComponentPrimitive ?? false,
|
|
5644
|
+
source: formattedSource,
|
|
5645
|
+
callSite: formattedCallSite,
|
|
5646
|
+
rawSource: locator.domSource || null,
|
|
5647
|
+
callSiteSource: locator.callSiteSource ?? null,
|
|
5648
|
+
definitionSource: locator.definitionSource ?? null,
|
|
5649
|
+
subElementSources: locator.subElementSources ?? null
|
|
5329
5650
|
};
|
|
5330
5651
|
}
|
|
5331
5652
|
function useAgentComms({ stateRef, sessionEditsRef, getSessionItems, saveCurrentToSession, removeSessionEdit, deleteComment }) {
|
|
@@ -5406,6 +5727,11 @@ function useAgentComms({ stateRef, sessionEditsRef, getSessionItems, saveCurrent
|
|
|
5406
5727
|
const movePlan = includeBatchMoveEnvelope ? resolvedPlanContext.movePlan : null;
|
|
5407
5728
|
const hasMeaningfulPayload = changes.length > 0 || sessionEdit.textEdit != null || moveIntent != null;
|
|
5408
5729
|
if (!hasMeaningfulPayload) return true;
|
|
5730
|
+
const profile = getExportContentProfile(
|
|
5731
|
+
[sessionEdit],
|
|
5732
|
+
[],
|
|
5733
|
+
resolvedPlanContext
|
|
5734
|
+
);
|
|
5409
5735
|
try {
|
|
5410
5736
|
const result = await sendEditToAgent({
|
|
5411
5737
|
...buildLocatorPayload(locator),
|
|
@@ -5413,7 +5739,7 @@ function useAgentComms({ stateRef, sessionEditsRef, getSessionItems, saveCurrent
|
|
|
5413
5739
|
textChange: sessionEdit.textEdit ?? null,
|
|
5414
5740
|
moveIntent,
|
|
5415
5741
|
...movePlan ? { movePlan } : {},
|
|
5416
|
-
exportMarkdown
|
|
5742
|
+
exportMarkdown: withInstruction(profile, exportMarkdown)
|
|
5417
5743
|
});
|
|
5418
5744
|
if (result.ok) {
|
|
5419
5745
|
removeSessionEdit(sessionEdit.element);
|
|
@@ -5425,12 +5751,13 @@ function useAgentComms({ stateRef, sessionEditsRef, getSessionItems, saveCurrent
|
|
|
5425
5751
|
}, [updateAgentAvailability, removeSessionEdit]);
|
|
5426
5752
|
const sendSessionCommentToAgent = React5.useCallback(async (comment) => {
|
|
5427
5753
|
const exportMarkdown = buildCommentExport(comment.locator, comment.text, comment.replies);
|
|
5754
|
+
const commentProfile = { hasCssEdits: false, hasTextEdits: false, hasMoves: false, hasComments: true };
|
|
5428
5755
|
try {
|
|
5429
5756
|
const result = await sendCommentToAgent({
|
|
5430
5757
|
...buildLocatorPayload(comment.locator),
|
|
5431
5758
|
commentText: comment.text,
|
|
5432
5759
|
replies: comment.replies,
|
|
5433
|
-
exportMarkdown
|
|
5760
|
+
exportMarkdown: withInstruction(commentProfile, exportMarkdown)
|
|
5434
5761
|
});
|
|
5435
5762
|
if (result.ok) {
|
|
5436
5763
|
deleteComment(comment.id);
|
|
@@ -5450,12 +5777,14 @@ function useAgentComms({ stateRef, sessionEditsRef, getSessionItems, saveCurrent
|
|
|
5450
5777
|
);
|
|
5451
5778
|
if (editsWithChanges.length === 0 && contextBlocks.length === 0) return false;
|
|
5452
5779
|
const markdownParts = [];
|
|
5780
|
+
let movePlanCtx = null;
|
|
5453
5781
|
if (editsWithChanges.length > 0) {
|
|
5454
|
-
|
|
5455
|
-
markdownParts.push(buildSessionExport(editsWithChanges, [], { movePlanContext }));
|
|
5782
|
+
movePlanCtx = buildMovePlanContext(editsWithChanges);
|
|
5783
|
+
markdownParts.push(buildSessionExport(editsWithChanges, [], { movePlanContext: movePlanCtx }));
|
|
5456
5784
|
}
|
|
5457
5785
|
markdownParts.push(...contextBlocks);
|
|
5458
5786
|
const exportMarkdown = markdownParts.join("\n\n");
|
|
5787
|
+
const multiProfile = getExportContentProfile(editsWithChanges, [], movePlanCtx);
|
|
5459
5788
|
const primaryEl = current.selectedElements.find((el) => el.isConnected);
|
|
5460
5789
|
if (!primaryEl) return false;
|
|
5461
5790
|
const primary = getElementLocator(primaryEl);
|
|
@@ -5465,7 +5794,7 @@ function useAgentComms({ stateRef, sessionEditsRef, getSessionItems, saveCurrent
|
|
|
5465
5794
|
changes: [],
|
|
5466
5795
|
textChange: null,
|
|
5467
5796
|
moveIntent: null,
|
|
5468
|
-
exportMarkdown
|
|
5797
|
+
exportMarkdown: withInstruction(multiProfile, exportMarkdown)
|
|
5469
5798
|
});
|
|
5470
5799
|
if (result.ok) {
|
|
5471
5800
|
for (const el of current.selectedElements) {
|
|
@@ -6293,6 +6622,7 @@ function DirectEditProvider({ children }) {
|
|
|
6293
6622
|
computedColor: null,
|
|
6294
6623
|
computedBoxShadow: null,
|
|
6295
6624
|
computedTypography: null,
|
|
6625
|
+
isComponentPrimitive: false,
|
|
6296
6626
|
originalStyles: {},
|
|
6297
6627
|
pendingStyles: {},
|
|
6298
6628
|
editModeActive: false,
|
|
@@ -6414,9 +6744,10 @@ function DirectEditProvider({ children }) {
|
|
|
6414
6744
|
enterCanvas();
|
|
6415
6745
|
}
|
|
6416
6746
|
if (wasActive) {
|
|
6747
|
+
clearSelection();
|
|
6417
6748
|
closePanel();
|
|
6418
6749
|
}
|
|
6419
|
-
}, [toggleEditModeBase, stateRef, exitCanvas, enterCanvas, closePanel]);
|
|
6750
|
+
}, [toggleEditModeBase, stateRef, exitCanvas, enterCanvas, clearSelection, closePanel]);
|
|
6420
6751
|
const toggleCanvasWithPreference = React8.useCallback(() => {
|
|
6421
6752
|
const willBeActive = !stateRef.current.canvas.active;
|
|
6422
6753
|
toggleCanvas();
|
|
@@ -8584,6 +8915,7 @@ function useSelectionResize({
|
|
|
8584
8915
|
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
8585
8916
|
var BLUE3 = "#0D99FF";
|
|
8586
8917
|
var MAGENTA = "#E11BB6";
|
|
8918
|
+
var COMPONENT_PURPLE = "#8B5CF6";
|
|
8587
8919
|
var DRAG_THRESHOLD = 4;
|
|
8588
8920
|
var DBLCLICK_DELAY = 300;
|
|
8589
8921
|
var HANDLE_SIZE = 12;
|
|
@@ -8627,9 +8959,11 @@ function SelectionOverlay({
|
|
|
8627
8959
|
onHoverElement,
|
|
8628
8960
|
onClickThrough,
|
|
8629
8961
|
onSelectPageFrame,
|
|
8962
|
+
isComponentPrimitive = false,
|
|
8630
8963
|
enableResizeHandles = false,
|
|
8631
8964
|
onResizeSizingChange
|
|
8632
8965
|
}) {
|
|
8966
|
+
const selectionColor = isComponentPrimitive ? COMPONENT_PURPLE : BLUE3;
|
|
8633
8967
|
const rectElement = isDragging && draggedElement ? draggedElement : selectedElement;
|
|
8634
8968
|
const [rect, setRect] = React19.useState(() => rectElement.getBoundingClientRect());
|
|
8635
8969
|
const [pageFrameRect, setPageFrameRect] = React19.useState(() => pageFrameElement ? pageFrameElement.getBoundingClientRect() : null);
|
|
@@ -8957,7 +9291,7 @@ function SelectionOverlay({
|
|
|
8957
9291
|
zIndex: 99997,
|
|
8958
9292
|
background: "transparent",
|
|
8959
9293
|
border: "none",
|
|
8960
|
-
color:
|
|
9294
|
+
color: selectionColor,
|
|
8961
9295
|
fontSize: `${pageLabelFontSize}px`,
|
|
8962
9296
|
lineHeight: `${pageLabelLineHeight}px`,
|
|
8963
9297
|
padding: 0,
|
|
@@ -8985,7 +9319,7 @@ function SelectionOverlay({
|
|
|
8985
9319
|
height: rect.height,
|
|
8986
9320
|
pointerEvents: "none",
|
|
8987
9321
|
zIndex: 99996,
|
|
8988
|
-
border: `1px solid ${
|
|
9322
|
+
border: `1px solid ${selectionColor}`,
|
|
8989
9323
|
borderRadius: "0px",
|
|
8990
9324
|
boxSizing: "border-box"
|
|
8991
9325
|
}
|
|
@@ -9002,18 +9336,28 @@ function SelectionOverlay({
|
|
|
9002
9336
|
transform: "translateX(-50%)",
|
|
9003
9337
|
pointerEvents: "none",
|
|
9004
9338
|
zIndex: 99992,
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
lineHeight: "20px",
|
|
9009
|
-
padding: "0 6px",
|
|
9010
|
-
borderRadius: "4px",
|
|
9011
|
-
whiteSpace: "nowrap",
|
|
9012
|
-
fontFamily: "system-ui, sans-serif",
|
|
9013
|
-
fontWeight: 500,
|
|
9014
|
-
letterSpacing: "-0.01em"
|
|
9339
|
+
display: "flex",
|
|
9340
|
+
alignItems: "center",
|
|
9341
|
+
gap: "4px"
|
|
9015
9342
|
},
|
|
9016
|
-
children:
|
|
9343
|
+
children: /* @__PURE__ */ jsx9(
|
|
9344
|
+
"span",
|
|
9345
|
+
{
|
|
9346
|
+
style: {
|
|
9347
|
+
background: selectionColor,
|
|
9348
|
+
color: "white",
|
|
9349
|
+
fontSize: "11px",
|
|
9350
|
+
lineHeight: "20px",
|
|
9351
|
+
padding: "0 6px",
|
|
9352
|
+
borderRadius: "4px",
|
|
9353
|
+
whiteSpace: "nowrap",
|
|
9354
|
+
fontFamily: "system-ui, sans-serif",
|
|
9355
|
+
fontWeight: 500,
|
|
9356
|
+
letterSpacing: "-0.01em"
|
|
9357
|
+
},
|
|
9358
|
+
children: dimensionText
|
|
9359
|
+
}
|
|
9360
|
+
)
|
|
9017
9361
|
}
|
|
9018
9362
|
),
|
|
9019
9363
|
!isDragging && !isTextEditing && /* @__PURE__ */ jsxs5(
|
|
@@ -9069,7 +9413,7 @@ function SelectionOverlay({
|
|
|
9069
9413
|
position: "absolute",
|
|
9070
9414
|
width: RESIZE_CORNER_SIZE,
|
|
9071
9415
|
height: RESIZE_CORNER_SIZE,
|
|
9072
|
-
border: `1px solid ${
|
|
9416
|
+
border: `1px solid ${selectionColor}`,
|
|
9073
9417
|
background: "#fff",
|
|
9074
9418
|
borderRadius: 1,
|
|
9075
9419
|
boxSizing: "border-box",
|
|
@@ -12494,6 +12838,7 @@ function DirectEditPanelContent() {
|
|
|
12494
12838
|
computedColor,
|
|
12495
12839
|
computedBoxShadow,
|
|
12496
12840
|
computedTypography,
|
|
12841
|
+
isComponentPrimitive,
|
|
12497
12842
|
borderStyleControlPreference,
|
|
12498
12843
|
pendingStyles,
|
|
12499
12844
|
editModeActive,
|
|
@@ -12814,6 +13159,7 @@ function DirectEditPanelContent() {
|
|
|
12814
13159
|
selectElement(child);
|
|
12815
13160
|
}
|
|
12816
13161
|
},
|
|
13162
|
+
isComponentPrimitive,
|
|
12817
13163
|
enableResizeHandles: true,
|
|
12818
13164
|
onResizeSizingChange: updateSizingProperties
|
|
12819
13165
|
}
|
|
@@ -14805,7 +15151,10 @@ function DirectEditDemo() {
|
|
|
14805
15151
|
const handleExportEdits = async () => {
|
|
14806
15152
|
if (Object.keys(pendingStyles).length === 0) return false;
|
|
14807
15153
|
const exportMarkdown = buildEditExport(DEMO_LOCATOR, pendingStyles);
|
|
14808
|
-
|
|
15154
|
+
const instruction = buildExportInstruction({ hasCssEdits: true, hasTextEdits: false, hasMoves: false, hasComments: false });
|
|
15155
|
+
return copyText(`${instruction}
|
|
15156
|
+
|
|
15157
|
+
${exportMarkdown}`);
|
|
14809
15158
|
};
|
|
14810
15159
|
return /* @__PURE__ */ jsx36("div", { className: "min-h-screen p-8", children: /* @__PURE__ */ jsxs29("div", { className: "mx-auto max-w-4xl", children: [
|
|
14811
15160
|
/* @__PURE__ */ jsx36("h1", { className: "mb-2 text-2xl font-bold", children: "Direct Edit Panel" }),
|