@hyperframes/studio 0.7.28 → 0.7.30
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/assets/{index-BGUJ2C2G.js → index-BhSyGx7y.js} +1 -1
- package/dist/assets/{index-Bz6Eqd_G.js → index-D0yNztV_.js} +1 -1
- package/dist/assets/{index-CLlPjdPl.js → index-kbACg3_I.js} +139 -139
- package/dist/{chunk-AN2EWWK3.js → chunk-JND3XUJL.js} +38 -10
- package/dist/chunk-JND3XUJL.js.map +1 -0
- package/dist/{domEditingLayers-EK7R7R4G.js → domEditingLayers-UIQZJCOA.js} +4 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.html +1 -1
- package/dist/index.js +1145 -675
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/components/editor/DomEditOverlay.test.ts +95 -0
- package/src/components/editor/DomEditOverlay.tsx +5 -3
- package/src/components/editor/domEditOverlayGestures.ts +3 -4
- package/src/components/editor/domEditing.ts +1 -0
- package/src/components/editor/domEditingLayers.test.ts +52 -0
- package/src/components/editor/domEditingLayers.ts +55 -19
- package/src/components/editor/domEditingTypes.ts +1 -0
- package/src/components/editor/persistSeam.integration.test.ts +264 -0
- package/src/components/editor/propertyPanelPrimitives.tsx +15 -1
- package/src/components/editor/useDomEditOverlayGestures.ts +1 -0
- package/src/hooks/domEditCommitRunner.ts +46 -0
- package/src/hooks/domEditPersistFailure.test.ts +123 -0
- package/src/hooks/domEditPersistFailure.ts +89 -0
- package/src/hooks/domEditTextFieldCommitOps.test.ts +111 -0
- package/src/hooks/domEditTextFieldCommitOps.ts +63 -0
- package/src/hooks/domSelectionTestHarness.ts +40 -0
- package/src/hooks/useDomEditAttributeCommits.ts +227 -0
- package/src/hooks/useDomEditCommits.test.tsx +775 -0
- package/src/hooks/useDomEditCommits.ts +33 -5
- package/src/hooks/useDomEditTextCommits.ts +243 -220
- package/src/hooks/useDomSelection.test.ts +134 -0
- package/src/hooks/useDomSelection.ts +29 -15
- package/src/hooks/usePreviewInteraction.test.ts +260 -0
- package/src/hooks/usePreviewInteraction.ts +60 -19
- package/src/utils/sdkCutoverEligibility.test.ts +17 -0
- package/src/utils/sdkCutoverEligibility.ts +8 -2
- package/src/utils/sdkResolverShadow.test.ts +180 -1
- package/src/utils/sdkResolverShadow.ts +94 -1
- package/src/utils/sourcePatcher.ts +2 -0
- package/src/utils/studioPreviewHelpers.test.ts +95 -1
- package/src/utils/studioPreviewHelpers.ts +109 -10
- package/src/utils/studioSaveDiagnostics.ts +5 -2
- package/src/utils/studioTelemetry.ts +27 -20
- package/dist/chunk-AN2EWWK3.js.map +0 -1
- /package/dist/{domEditingLayers-EK7R7R4G.js.map → domEditingLayers-UIQZJCOA.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.30",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"gsap": "^3.13.0",
|
|
47
47
|
"marked": "^14.1.4",
|
|
48
48
|
"mediabunny": "^1.45.3",
|
|
49
|
-
"@hyperframes/core": "0.7.
|
|
50
|
-
"@hyperframes/player": "0.7.
|
|
51
|
-
"@hyperframes/
|
|
52
|
-
"@hyperframes/
|
|
53
|
-
"@hyperframes/
|
|
49
|
+
"@hyperframes/core": "0.7.30",
|
|
50
|
+
"@hyperframes/player": "0.7.30",
|
|
51
|
+
"@hyperframes/parsers": "0.7.30",
|
|
52
|
+
"@hyperframes/sdk": "0.7.30",
|
|
53
|
+
"@hyperframes/studio-server": "0.7.30"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "19",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"vite": "^6.4.2",
|
|
66
66
|
"vitest": "^3.2.4",
|
|
67
67
|
"zustand": "^5.0.0",
|
|
68
|
-
"@hyperframes/producer": "0.7.
|
|
68
|
+
"@hyperframes/producer": "0.7.30"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"react": "19",
|
|
@@ -345,6 +345,101 @@ describe("DomEditOverlay", () => {
|
|
|
345
345
|
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
|
|
346
346
|
host.remove();
|
|
347
347
|
});
|
|
348
|
+
|
|
349
|
+
it("passes the tracked hover selection when clicking the existing selection box", async () => {
|
|
350
|
+
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
|
|
351
|
+
Element.prototype.getBoundingClientRect = function (): DOMRect {
|
|
352
|
+
return {
|
|
353
|
+
left: 0,
|
|
354
|
+
top: 0,
|
|
355
|
+
right: 800,
|
|
356
|
+
bottom: 450,
|
|
357
|
+
width: 800,
|
|
358
|
+
height: 450,
|
|
359
|
+
x: 0,
|
|
360
|
+
y: 0,
|
|
361
|
+
toJSON: () => ({}),
|
|
362
|
+
};
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const host = document.createElement("div");
|
|
366
|
+
document.body.append(host);
|
|
367
|
+
const root = createRoot(host);
|
|
368
|
+
const selection: DomEditSelection = {
|
|
369
|
+
element: document.createElement("div"),
|
|
370
|
+
id: "hero-title",
|
|
371
|
+
selector: ".hero-title",
|
|
372
|
+
selectorIndex: 0,
|
|
373
|
+
sourceFile: "index.html",
|
|
374
|
+
tagName: "div",
|
|
375
|
+
label: "Hero Title",
|
|
376
|
+
textContent: "Hello",
|
|
377
|
+
textFields: [],
|
|
378
|
+
capabilities: {
|
|
379
|
+
canEditText: true,
|
|
380
|
+
canEditLayout: true,
|
|
381
|
+
canMove: false,
|
|
382
|
+
canApplyManualOffset: false,
|
|
383
|
+
canApplyManualSize: false,
|
|
384
|
+
canApplyManualRotation: false,
|
|
385
|
+
canAdjustOpacity: true,
|
|
386
|
+
canAdjustFill: true,
|
|
387
|
+
canAdjustBorderRadius: true,
|
|
388
|
+
canAdjustStroke: true,
|
|
389
|
+
canAdjustShadow: true,
|
|
390
|
+
canAdjustZIndex: true,
|
|
391
|
+
},
|
|
392
|
+
computedStyle: {
|
|
393
|
+
display: "block",
|
|
394
|
+
position: "absolute",
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
const hoverSelection: DomEditSelection = { ...selection, id: "hovered-sibling" };
|
|
398
|
+
const onCanvasMouseDown = vi.fn();
|
|
399
|
+
const iframeRef = { current: document.createElement("iframe") as HTMLIFrameElement | null };
|
|
400
|
+
|
|
401
|
+
function Harness() {
|
|
402
|
+
return React.createElement(DomEditOverlay, {
|
|
403
|
+
...createOverlayProps({
|
|
404
|
+
iframeRef,
|
|
405
|
+
selection,
|
|
406
|
+
hoverSelection,
|
|
407
|
+
onSelectionChange: () => {},
|
|
408
|
+
}),
|
|
409
|
+
onCanvasMouseDown,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
act(() => {
|
|
414
|
+
root.render(React.createElement(Harness));
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
await act(async () => {
|
|
418
|
+
await new Promise<void>((resolve) => {
|
|
419
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
const selectionBox = host.querySelector(
|
|
424
|
+
'[data-dom-edit-selection-box="true"]',
|
|
425
|
+
) as HTMLDivElement;
|
|
426
|
+
expect(selectionBox).toBeTruthy();
|
|
427
|
+
|
|
428
|
+
act(() => {
|
|
429
|
+
selectionBox.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
expect(onCanvasMouseDown).toHaveBeenCalledWith(
|
|
433
|
+
expect.anything(),
|
|
434
|
+
expect.objectContaining({ hoverSelection }),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
act(() => {
|
|
438
|
+
root.unmount();
|
|
439
|
+
});
|
|
440
|
+
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
|
|
441
|
+
host.remove();
|
|
442
|
+
});
|
|
348
443
|
});
|
|
349
444
|
|
|
350
445
|
describe("resolveDomEditCoordinateScale", () => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { memo, useEffect, useMemo, useRef, useState, type RefObject } from "react";
|
|
2
2
|
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
3
3
|
import { type DomEditSelection } from "./domEditing";
|
|
4
|
+
import type { PreviewMouseDownOptions } from "../../hooks/usePreviewInteraction";
|
|
4
5
|
import { useMarqueeGestures } from "./marqueeCommit";
|
|
5
6
|
import { MarqueeOverlay } from "./MarqueeOverlay";
|
|
6
7
|
import { groupAwareOverlayRect, resolveDomEditGroupOverlayRect } from "./domEditOverlayGeometry";
|
|
@@ -44,7 +45,7 @@ interface DomEditOverlayProps {
|
|
|
44
45
|
allowCanvasMovement?: boolean;
|
|
45
46
|
onCanvasMouseDown: (
|
|
46
47
|
event: React.MouseEvent<HTMLDivElement>,
|
|
47
|
-
options?:
|
|
48
|
+
options?: PreviewMouseDownOptions,
|
|
48
49
|
) => void;
|
|
49
50
|
onCanvasPointerMove: (
|
|
50
51
|
event: React.PointerEvent<HTMLDivElement>,
|
|
@@ -277,6 +278,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
277
278
|
iframeRef,
|
|
278
279
|
boxRef,
|
|
279
280
|
selectionRef,
|
|
281
|
+
hoverSelectionRef,
|
|
280
282
|
overlayRectRef,
|
|
281
283
|
groupOverlayItemsRef,
|
|
282
284
|
gestureRef,
|
|
@@ -336,7 +338,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
336
338
|
// Allow clicks anywhere on the overlay — GSAP-translated elements can
|
|
337
339
|
// extend beyond the composition rect into the gray zone, and users need
|
|
338
340
|
// to select/deselect them by clicking there.
|
|
339
|
-
onCanvasMouseDown(event, {
|
|
341
|
+
onCanvasMouseDown(event, { hoverSelection: hoverSelectionRef.current });
|
|
340
342
|
if (event.shiftKey) {
|
|
341
343
|
suppressNextBoxMouseDownRef.current = true;
|
|
342
344
|
suppressNextBoxClickRef.current = true;
|
|
@@ -401,7 +403,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
|
|
|
401
403
|
event.stopPropagation();
|
|
402
404
|
return;
|
|
403
405
|
}
|
|
404
|
-
onCanvasMouseDown(event, {
|
|
406
|
+
onCanvasMouseDown(event, { hoverSelection: hoverSelectionRef.current });
|
|
405
407
|
};
|
|
406
408
|
|
|
407
409
|
const suppressBoxMouseDown = (e: React.MouseEvent) => {
|
|
@@ -9,6 +9,7 @@ import type { ManualOffsetDragMember } from "./manualOffsetDrag";
|
|
|
9
9
|
import type { GroupOverlayItem, OverlayRect } from "./domEditOverlayGeometry";
|
|
10
10
|
import type { SnapContext } from "./snapTargetCollection";
|
|
11
11
|
import type { SnapGuidesState } from "./SnapGuideOverlay";
|
|
12
|
+
import type { PreviewMouseDownOptions } from "../../hooks/usePreviewInteraction";
|
|
12
13
|
|
|
13
14
|
export type GestureKind = "drag" | "resize" | "rotate";
|
|
14
15
|
|
|
@@ -161,6 +162,7 @@ export type UseDomEditOverlayGesturesOptions = {
|
|
|
161
162
|
iframeRef: RefObject<HTMLIFrameElement | null>;
|
|
162
163
|
boxRef: RefObject<HTMLDivElement | null>;
|
|
163
164
|
selectionRef: RefObject<DomEditSelection | null>;
|
|
165
|
+
hoverSelectionRef: RefObject<DomEditSelection | null>;
|
|
164
166
|
overlayRectRef: RefObject<OverlayRect | null>;
|
|
165
167
|
groupOverlayItemsRef: RefObject<GroupOverlayItem[]>;
|
|
166
168
|
gestureRef: RefObject<GestureState | null>;
|
|
@@ -194,9 +196,6 @@ export type UseDomEditOverlayGesturesOptions = {
|
|
|
194
196
|
o?: { preferClipAncestor?: boolean },
|
|
195
197
|
) => Promise<DomEditSelection | null>
|
|
196
198
|
>;
|
|
197
|
-
onCanvasMouseDown: (
|
|
198
|
-
e: React.MouseEvent<HTMLDivElement>,
|
|
199
|
-
o?: { preferClipAncestor?: boolean },
|
|
200
|
-
) => void;
|
|
199
|
+
onCanvasMouseDown: (e: React.MouseEvent<HTMLDivElement>, o?: PreviewMouseDownOptions) => void;
|
|
201
200
|
snapGuidesRef: RefObject<SnapGuidesState | null>;
|
|
202
201
|
};
|
|
@@ -4,11 +4,27 @@ import {
|
|
|
4
4
|
collectDomEditLayerItems,
|
|
5
5
|
resolveDomEditSelection,
|
|
6
6
|
buildDomEditPatchTarget,
|
|
7
|
+
buildTextFieldChildLocator,
|
|
7
8
|
readHfId,
|
|
8
9
|
} from "./domEditingLayers";
|
|
10
|
+
import type { DomEditTextField } from "./domEditingTypes";
|
|
9
11
|
|
|
10
12
|
const opts = { activeCompositionPath: "index.html", isMasterView: true, skipSourceProbe: true };
|
|
11
13
|
|
|
14
|
+
function textField(overrides: Partial<DomEditTextField> = {}): DomEditTextField {
|
|
15
|
+
return {
|
|
16
|
+
key: "child:0:span",
|
|
17
|
+
label: "Text 1",
|
|
18
|
+
value: "Hello",
|
|
19
|
+
tagName: "span",
|
|
20
|
+
attributes: [],
|
|
21
|
+
inlineStyles: {},
|
|
22
|
+
computedStyles: {},
|
|
23
|
+
source: "child",
|
|
24
|
+
...overrides,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
describe("buildDomEditPatchTarget", () => {
|
|
13
29
|
it("includes hfId when selection has hfId", () => {
|
|
14
30
|
const target = buildDomEditPatchTarget({
|
|
@@ -172,3 +188,39 @@ describe("resolveDomEditSelection — data-hf-group capture", () => {
|
|
|
172
188
|
expect(selection?.id).toBe("outside");
|
|
173
189
|
});
|
|
174
190
|
});
|
|
191
|
+
|
|
192
|
+
describe("buildTextFieldChildLocator", () => {
|
|
193
|
+
it("locates a child field using its DOM-derived sourceChildIndex", () => {
|
|
194
|
+
const fields = [textField({ key: "child:0:span", sourceChildIndex: 0 })];
|
|
195
|
+
|
|
196
|
+
expect(buildTextFieldChildLocator(fields, "child:0:span")).toEqual({
|
|
197
|
+
childSelector: ":scope > span",
|
|
198
|
+
childIndex: 0,
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("fails closed for a synthetic child field with no sourceChildIndex", () => {
|
|
203
|
+
// A field built by buildDefaultDomEditTextField (e.g. "add text field")
|
|
204
|
+
// has never been read back from the live DOM, so its true position among
|
|
205
|
+
// same-tag siblings is unknown. Guessing it by counting same-tag "child"
|
|
206
|
+
// fields elsewhere in the array can silently point at the wrong element.
|
|
207
|
+
const fields = [
|
|
208
|
+
textField({ key: "child:0:span", sourceChildIndex: 0 }),
|
|
209
|
+
textField({ key: "child:new:1", tagName: "span" }),
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
expect(buildTextFieldChildLocator(fields, "child:new:1")).toBeNull();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("returns null for a self-sourced field", () => {
|
|
216
|
+
const fields = [textField({ key: "self:0:div", source: "self", sourceChildIndex: 0 })];
|
|
217
|
+
|
|
218
|
+
expect(buildTextFieldChildLocator(fields, "self:0:div")).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("returns null for an unknown field key", () => {
|
|
222
|
+
const fields = [textField({ key: "child:0:span", sourceChildIndex: 0 })];
|
|
223
|
+
|
|
224
|
+
expect(buildTextFieldChildLocator(fields, "missing")).toBeNull();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layer items, text fields, capabilities, selection resolution, and patch operations
|
|
3
|
-
* for dom editing.
|
|
4
|
-
*/
|
|
5
1
|
import type { PatchOperation } from "../../utils/sourcePatcher";
|
|
6
2
|
import {
|
|
7
3
|
resolveEditingAffordances,
|
|
@@ -36,12 +32,20 @@ import {
|
|
|
36
32
|
} from "./domEditingElement";
|
|
37
33
|
import { isCompositionRootLayer } from "./domEditingRootLayer";
|
|
38
34
|
|
|
39
|
-
// ─── Text fields ────────────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
35
|
export function isEditableTextLeaf(el: HTMLElement): boolean {
|
|
42
36
|
return isTextBearingTag(el.tagName.toLowerCase()) && el.children.length === 0;
|
|
43
37
|
}
|
|
44
38
|
|
|
39
|
+
function sameTagChildIndex(el: HTMLElement): number {
|
|
40
|
+
let index = 0;
|
|
41
|
+
let sibling = el.previousElementSibling;
|
|
42
|
+
while (sibling) {
|
|
43
|
+
if (sibling.tagName === el.tagName) index += 1;
|
|
44
|
+
sibling = sibling.previousElementSibling;
|
|
45
|
+
}
|
|
46
|
+
return index;
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
function getTextFieldLabel(
|
|
46
50
|
_tagName: string,
|
|
47
51
|
index: number,
|
|
@@ -57,6 +61,7 @@ function buildTextField(
|
|
|
57
61
|
index: number,
|
|
58
62
|
total: number,
|
|
59
63
|
source: "self" | "child",
|
|
64
|
+
sourceChildIndex?: number,
|
|
60
65
|
): DomEditTextField {
|
|
61
66
|
const tagName = el.tagName.toLowerCase();
|
|
62
67
|
const key = el.getAttribute("data-hf-text-key") ?? `${source}:${index}:${tagName}`;
|
|
@@ -74,6 +79,7 @@ function buildTextField(
|
|
|
74
79
|
inlineStyles: getInlineStyles(el),
|
|
75
80
|
computedStyles: getCuratedComputedStyles(el),
|
|
76
81
|
source,
|
|
82
|
+
...(sourceChildIndex == null ? {} : { sourceChildIndex }),
|
|
77
83
|
};
|
|
78
84
|
}
|
|
79
85
|
|
|
@@ -105,7 +111,9 @@ export function collectDomEditTextFields(el: HTMLElement): DomEditTextField[] {
|
|
|
105
111
|
});
|
|
106
112
|
childIdx++;
|
|
107
113
|
} else if (isHtmlElement(node) && isEditableTextLeaf(node)) {
|
|
108
|
-
fields.push(
|
|
114
|
+
fields.push(
|
|
115
|
+
buildTextField(node, childIdx, childElements.length, "child", sameTagChildIndex(node)),
|
|
116
|
+
);
|
|
109
117
|
childIdx++;
|
|
110
118
|
}
|
|
111
119
|
}
|
|
@@ -113,7 +121,7 @@ export function collectDomEditTextFields(el: HTMLElement): DomEditTextField[] {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
return childElements.map((child, index) =>
|
|
116
|
-
buildTextField(child, index, childElements.length, "child"),
|
|
124
|
+
buildTextField(child, index, childElements.length, "child", sameTagChildIndex(child)),
|
|
117
125
|
);
|
|
118
126
|
}
|
|
119
127
|
|
|
@@ -172,14 +180,30 @@ export function buildDefaultDomEditTextField(base?: Partial<DomEditTextField>):
|
|
|
172
180
|
};
|
|
173
181
|
}
|
|
174
182
|
|
|
175
|
-
|
|
183
|
+
export interface DomEditChildLocator {
|
|
184
|
+
childSelector: string;
|
|
185
|
+
childIndex: number;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function buildTextFieldChildLocator(
|
|
189
|
+
fields: DomEditTextField[],
|
|
190
|
+
fieldKey: string,
|
|
191
|
+
): DomEditChildLocator | null {
|
|
192
|
+
const field = fields.find((candidate) => candidate.key === fieldKey);
|
|
193
|
+
if (!field || field.source !== "child") return null;
|
|
194
|
+
// sourceChildIndex is only absent for a synthetic field that was never read
|
|
195
|
+
// back from the live DOM (e.g. one built by buildDefaultDomEditTextField).
|
|
196
|
+
// Guessing its position by counting same-tag "child" fields elsewhere in
|
|
197
|
+
// the array is unreliable and can silently locate the wrong element — fail
|
|
198
|
+
// closed instead so the caller falls back to the unsupported-structure path.
|
|
199
|
+
if (field.sourceChildIndex == null) return null;
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
childSelector: `:scope > ${field.tagName}`,
|
|
203
|
+
childIndex: field.sourceChildIndex,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
176
206
|
|
|
177
|
-
/**
|
|
178
|
-
* Build the geometry/capability half of EditableElementFacts. Section inputs
|
|
179
|
-
* (text/timing/animation) are irrelevant to capability resolution, so they are
|
|
180
|
-
* zeroed here. Shared by the wrapper and the live-selection path so the two
|
|
181
|
-
* fact-construction sites can't disagree.
|
|
182
|
-
*/
|
|
183
207
|
function capabilityFacts(geometry: {
|
|
184
208
|
hasStableTarget: boolean;
|
|
185
209
|
tag: string;
|
|
@@ -276,8 +300,11 @@ async function probeSourceElement(
|
|
|
276
300
|
},
|
|
277
301
|
);
|
|
278
302
|
if (!response.ok) return true;
|
|
279
|
-
const data =
|
|
280
|
-
|
|
303
|
+
const data = await response.json();
|
|
304
|
+
if (data && typeof data === "object" && "exists" in data && data.exists === false) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return true;
|
|
281
308
|
} catch {
|
|
282
309
|
return true;
|
|
283
310
|
}
|
|
@@ -475,19 +502,28 @@ export function collectDomEditLayerItems(
|
|
|
475
502
|
|
|
476
503
|
// ─── Patch operations ────────────────────────────────────────────────────────
|
|
477
504
|
|
|
478
|
-
export function buildDomEditStylePatchOperation(
|
|
505
|
+
export function buildDomEditStylePatchOperation(
|
|
506
|
+
property: string,
|
|
507
|
+
value: string | null,
|
|
508
|
+
childLocator?: DomEditChildLocator,
|
|
509
|
+
): PatchOperation {
|
|
479
510
|
return {
|
|
480
511
|
type: "inline-style",
|
|
481
512
|
property,
|
|
482
513
|
value,
|
|
514
|
+
...childLocator,
|
|
483
515
|
};
|
|
484
516
|
}
|
|
485
517
|
|
|
486
|
-
export function buildDomEditTextPatchOperation(
|
|
518
|
+
export function buildDomEditTextPatchOperation(
|
|
519
|
+
value: string,
|
|
520
|
+
childLocator?: DomEditChildLocator,
|
|
521
|
+
): PatchOperation {
|
|
487
522
|
return {
|
|
488
523
|
type: "text-content",
|
|
489
524
|
property: "text",
|
|
490
525
|
value,
|
|
526
|
+
...childLocator,
|
|
491
527
|
};
|
|
492
528
|
}
|
|
493
529
|
|