@parhelia/core 0.1.12926 → 0.1.12928
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/client-components/index.d.ts +1 -1
- package/dist/client-components/index.js +1 -1
- package/dist/client-components/index.js.map +1 -1
- package/dist/components/ui/close-icon.d.ts +6 -1
- package/dist/components/ui/close-icon.js +12 -7
- package/dist/components/ui/close-icon.js.map +1 -1
- package/dist/editor/FieldListField.js +8 -0
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/MainLayout.js +2 -1
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/PerfProfiler.d.ts +11 -0
- package/dist/editor/PerfProfiler.js +29 -0
- package/dist/editor/PerfProfiler.js.map +1 -0
- package/dist/editor/ai/GuidanceOverlay.js +4 -9
- package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
- package/dist/editor/ai/InlineAiDialog.js +2 -2
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/dialogs/capturePageScreenshot.js +1 -1
- package/dist/editor/ai/dialogs/capturePageScreenshot.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentSettingsContent.d.ts +2 -1
- package/dist/editor/ai/terminal/components/AgentSettingsContent.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentSettingsSections.d.ts +2 -1
- package/dist/editor/ai/terminal/components/AgentSettingsSections.js +9 -2
- package/dist/editor/ai/terminal/components/AgentSettingsSections.js.map +1 -1
- package/dist/editor/ai/terminal/components/ContextInfoBar.js +4 -3
- package/dist/editor/ai/terminal/components/ContextInfoBar.js.map +1 -1
- package/dist/editor/ai/terminal/components/TodoListPanel.js +21 -48
- package/dist/editor/ai/terminal/components/TodoListPanel.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentSettingsData.d.ts +8 -1
- package/dist/editor/ai/terminal/useAgentSettingsData.js +42 -3
- package/dist/editor/ai/terminal/useAgentSettingsData.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentSettingsPanel.d.ts +1 -1
- package/dist/editor/bridge/BridgeClient.js +9 -1
- package/dist/editor/bridge/BridgeClient.js.map +1 -1
- package/dist/editor/bridge/protocol.d.ts +25 -0
- package/dist/editor/bridge/protocol.js +1 -1
- package/dist/editor/bridge/protocol.js.map +1 -1
- package/dist/editor/client/EditorShell.js +156 -20
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +32 -1
- package/dist/editor/client/editContext.js +8 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/errorToast.js +21 -5
- package/dist/editor/client/errorToast.js.map +1 -1
- package/dist/editor/client/fieldModificationStore.js +64 -0
- package/dist/editor/client/fieldModificationStore.js.map +1 -1
- package/dist/editor/client/itemsRepository.js +95 -3
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.d.ts +1 -0
- package/dist/editor/client/operations.js +147 -13
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.js +6 -5
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/commands/keyboardCommands.js +1 -1
- package/dist/editor/commands/keyboardCommands.js.map +1 -1
- package/dist/editor/componentTreeHelper.js +15 -5
- package/dist/editor/componentTreeHelper.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +79 -9
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/fieldValueDiagnostics.d.ts +79 -0
- package/dist/editor/fieldValueDiagnostics.js +133 -0
- package/dist/editor/fieldValueDiagnostics.js.map +1 -0
- package/dist/editor/menubar/FavoritesControls.js +2 -2
- package/dist/editor/menubar/FavoritesControls.js.map +1 -1
- package/dist/editor/notifications/NotificationCenter.js +4 -3
- package/dist/editor/notifications/NotificationCenter.js.map +1 -1
- package/dist/editor/notifications/WatchButton.js +6 -31
- package/dist/editor/notifications/WatchButton.js.map +1 -1
- package/dist/editor/notifications/watchEventOptions.js +3 -3
- package/dist/editor/notifications/watchEventOptions.js.map +1 -1
- package/dist/editor/page-editor-chrome/DragInsertionOverlay.js +51 -19
- package/dist/editor/page-editor-chrome/DragInsertionOverlay.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.d.ts +2 -0
- package/dist/editor/page-editor-chrome/FrameMenu.js +8 -6
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenus.d.ts +3 -1
- package/dist/editor/page-editor-chrome/FrameMenus.js +4 -4
- package/dist/editor/page-editor-chrome/FrameMenus.js.map +1 -1
- package/dist/editor/page-editor-chrome/PageEditorChrome.js +17 -4
- package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +4 -7
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +8 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +14 -3
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.d.ts +3 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +2 -2
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
- package/dist/editor/page-editor-chrome/overlay/placeholderZones.js +43 -1
- package/dist/editor/page-editor-chrome/overlay/placeholderZones.js.map +1 -1
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js +121 -48
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +54 -5
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/MiniMap.d.ts +12 -1
- package/dist/editor/page-viewer/MiniMap.js +65 -26
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.js +9 -7
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +290 -37
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/bridgeFieldPatch.js +49 -2
- package/dist/editor/page-viewer/bridgeFieldPatch.js.map +1 -1
- package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
- package/dist/editor/page-viewer/pageViewContext.js +7 -0
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/perfTrace.d.ts +46 -0
- package/dist/editor/perfTrace.js +213 -0
- package/dist/editor/perfTrace.js.map +1 -0
- package/dist/editor/reviews/CommentPopover.js +2 -3
- package/dist/editor/reviews/CommentPopover.js.map +1 -1
- package/dist/editor/reviews/Comments.js +12 -3
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +1 -1
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +2 -0
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +1 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/settings/panels/CreateAgentProfileDialog.js +3 -2
- package/dist/editor/settings/panels/CreateAgentProfileDialog.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +10 -9
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/ContentTreePanel.d.ts +2 -2
- package/dist/editor/sidebar/ContentTreePanel.js +6 -2
- package/dist/editor/sidebar/ContentTreePanel.js.map +1 -1
- package/dist/editor/sidebar/MainContentTree.js +3 -2
- package/dist/editor/sidebar/MainContentTree.js.map +1 -1
- package/dist/editor/sidebar/SidebarPanel.js +1 -1
- package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
- package/dist/editor/utils/keyboardNavigation.js +2 -2
- package/dist/editor/utils/keyboardNavigation.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { BRIDGE_DOM_UPDATED_EVENT, MiniMap } from "./MiniMap";
|
|
4
|
-
import { useEditContext, useEditContextRef, useFieldsEditContext, useFieldsEditContextRef, } from "../client/editContext";
|
|
4
|
+
import { useCanvasSelection, useEditContext, useEditContextRef, useFieldsEditContext, useFieldsEditContextRef, } from "../client/editContext";
|
|
5
5
|
import { useSlotContext } from "../views/editorSlotContext";
|
|
6
6
|
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
|
|
7
7
|
import { PageEditorChrome } from "../page-editor-chrome/PageEditorChrome";
|
|
8
8
|
import { useBridgeInlineEditing, } from "../page-editor-chrome/useBridgeInlineEditing";
|
|
9
9
|
import { getInlineAiAnchorFromBridgeToolbar, getVisibleBridgeToolbarRect, waitForVisibleBridgeToolbarRect, } from "../page-editor-chrome/bridgeInlineFormatToolbarLayout";
|
|
10
10
|
import { IFRAME_OVERLAY_BRIDGE_DRAG_GESTURE_EVENT, IFRAME_OVERLAY_BRIDGE_GEOMETRY_EVENT, IFRAME_OVERLAY_BRIDGE_SCROLL_EVENT, IframeOverlayProvider, } from "../page-editor-chrome/overlay/IframeOverlayProvider";
|
|
11
|
-
import { DEVICE_CHANGE_EVENT, useViewportChangeSignal, } from "./pageViewContext";
|
|
11
|
+
import { CANVAS_REFRESH_COMPLETED_EVENT, DEVICE_CHANGE_EVENT, useViewportChangeSignal, } from "./pageViewContext";
|
|
12
12
|
import uuid from "react-uuid";
|
|
13
13
|
import { cn } from "../../lib/utils";
|
|
14
14
|
import { normalizeMarkerId } from "../utils";
|
|
@@ -26,8 +26,10 @@ import { DeviceToolbar } from "./DeviceToolbar";
|
|
|
26
26
|
import { FullscreenControls } from "../client/ui/FullscreenControls";
|
|
27
27
|
import { toSitecoreDate } from "../utils/sitecoreDate";
|
|
28
28
|
import { BridgeClient } from "../bridge/BridgeClient";
|
|
29
|
+
import { beginPerfTrace, isPerfTraceActive, isPerfTraceEnabled, perfTraceStep, } from "../perfTrace";
|
|
29
30
|
import { getSuggestionDisplayValue } from "../reviews/suggestionDisplayValue";
|
|
30
31
|
import { buildBridgeFieldPatchPayload, getBridgeFieldPatchValue, isBridgePatchableTextField, } from "./bridgeFieldPatch";
|
|
32
|
+
import { logFieldValueDiagnostic, summarizeField, summarizeFieldDescriptor, summarizeFieldValue, } from "../fieldValueDiagnostics";
|
|
31
33
|
import { createKeyboardEventFromBridgeInteraction } from "./bridgeKeyboardShortcuts";
|
|
32
34
|
import { formatParentBridgeInjectionDiagnostic, injectParheliaBridgeFromParent, } from "./parentBridgeInjection";
|
|
33
35
|
import { getTextCompletion, normalizeTextCompletion, } from "../services/textCompletionService";
|
|
@@ -39,6 +41,9 @@ const BRIDGE_INLINE_EDIT_RELEASE_EVENT = "parhelia:bridge-inline-edit-release";
|
|
|
39
41
|
const INLINE_AI_CLOSE_EVENT = "inline-ai-close";
|
|
40
42
|
const BRIDGE_INLINE_LINK_OPEN_EVENT = "bridge-inline-link-open";
|
|
41
43
|
const SCROLL_INTO_VIEW_BRIDGE_SOURCE = "scroll-into-view";
|
|
44
|
+
// How long a requested canvas refresh may suppress viewport reactions
|
|
45
|
+
// (selection auto-scroll, scrollIntoView) before it is presumed lost.
|
|
46
|
+
const CANVAS_REFRESH_IN_FLIGHT_MAX_MS = 15000;
|
|
42
47
|
// Suggestions-mode cursor: a text I-beam (the click insertion point) badged with
|
|
43
48
|
// the lucide PenLine icon from the "Suggestions mode" toolbar button. A white
|
|
44
49
|
// halo keeps it visible on any page background; the hotspot is the I-beam centre.
|
|
@@ -602,6 +607,7 @@ export function PageViewerFrame(props) {
|
|
|
602
607
|
function PageViewerFrameContent({ compareView, pageViewContext, editContext, className, slotCloseButton, }) {
|
|
603
608
|
const fieldsContext = useFieldsEditContext();
|
|
604
609
|
const slotContext = useSlotContext();
|
|
610
|
+
const canvasSelection = useCanvasSelection();
|
|
605
611
|
const pathname = usePathname();
|
|
606
612
|
const pageViewContextRef = useRef(undefined);
|
|
607
613
|
useEffect(() => {
|
|
@@ -636,6 +642,27 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
636
642
|
const directOverlayScrollSyncRef = useRef(false);
|
|
637
643
|
const lastBridgeHoverInteractionRef = useRef(undefined);
|
|
638
644
|
const pendingRefreshScrollRef = useRef(undefined);
|
|
645
|
+
// Set when a canvas refresh (bridge patch or full reload) has been requested
|
|
646
|
+
// and its result has not landed yet. While in flight, bridgeGeometry still
|
|
647
|
+
// describes the PRE-refresh DOM — a moved component is still measured at its
|
|
648
|
+
// old position — so viewport-affecting reactions (selection auto-scroll,
|
|
649
|
+
// scrollIntoView) must wait for the refreshed geometry instead of scrolling
|
|
650
|
+
// toward stale rects. Timestamped so a refresh that never completes cannot
|
|
651
|
+
// suppress scrolling forever.
|
|
652
|
+
const canvasRefreshInFlightSinceRef = useRef(null);
|
|
653
|
+
// Set when refreshCompleted arrived with applied:"host-app": the page's own
|
|
654
|
+
// app accepted the refresh but its re-render lands later as DOM mutations.
|
|
655
|
+
// The in-flight window then stays open until the next structureUpdated.
|
|
656
|
+
const awaitingHostAppRefreshRenderRef = useRef(false);
|
|
657
|
+
const isCanvasRefreshInFlight = useCallback(() => {
|
|
658
|
+
const since = canvasRefreshInFlightSinceRef.current;
|
|
659
|
+
return (since !== null && Date.now() - since < CANVAS_REFRESH_IN_FLIGHT_MAX_MS);
|
|
660
|
+
}, []);
|
|
661
|
+
const completeCanvasRefresh = useCallback(() => {
|
|
662
|
+
awaitingHostAppRefreshRenderRef.current = false;
|
|
663
|
+
canvasRefreshInFlightSinceRef.current = null;
|
|
664
|
+
window.dispatchEvent(new CustomEvent(CANVAS_REFRESH_COMPLETED_EVENT));
|
|
665
|
+
}, []);
|
|
639
666
|
const restorePendingRefreshScroll = useCallback((iframe, targetScroll, observedScroll) => {
|
|
640
667
|
if (!targetScroll)
|
|
641
668
|
return;
|
|
@@ -644,6 +671,17 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
644
671
|
pendingRefreshScrollRef.current = undefined;
|
|
645
672
|
}
|
|
646
673
|
};
|
|
674
|
+
const currentScroll = getIframeWindowScroll(iframe) ??
|
|
675
|
+
observedScroll ?? { x: 0, y: 0 };
|
|
676
|
+
const deltaX = targetScroll.x - currentScroll.x;
|
|
677
|
+
const deltaY = targetScroll.y - currentScroll.y;
|
|
678
|
+
// Already at the target: re-asserting the position would be a no-op
|
|
679
|
+
// except that scrollTo cancels any in-flight smooth scroll (e.g. a
|
|
680
|
+
// minimap navigation issued while the refresh was still running).
|
|
681
|
+
if (Math.abs(deltaX) <= 1 && Math.abs(deltaY) <= 1) {
|
|
682
|
+
clearPendingTarget();
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
647
685
|
const restoreStarted = restoreIframeWindowScroll(iframe, targetScroll, {
|
|
648
686
|
maxAttempts: 150,
|
|
649
687
|
onSettled: clearPendingTarget,
|
|
@@ -652,17 +690,11 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
652
690
|
if (!restoreStarted) {
|
|
653
691
|
clearPendingTarget();
|
|
654
692
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
bridgeClientRef.current?.sendCommand("scrollBy", {
|
|
661
|
-
x: deltaX,
|
|
662
|
-
y: deltaY,
|
|
663
|
-
behavior: "instant",
|
|
664
|
-
});
|
|
665
|
-
}
|
|
693
|
+
bridgeClientRef.current?.sendCommand("scrollBy", {
|
|
694
|
+
x: deltaX,
|
|
695
|
+
y: deltaY,
|
|
696
|
+
behavior: "instant",
|
|
697
|
+
});
|
|
666
698
|
}, []);
|
|
667
699
|
const suppressNextSelectionScrollRef = useRef(false);
|
|
668
700
|
// Timestamp (ms) until which selection auto-scroll is suppressed. Held open for
|
|
@@ -770,16 +802,34 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
770
802
|
// Iframe blur is handled by the bridge inline-edit lifecycle now.
|
|
771
803
|
}, []);
|
|
772
804
|
const sendBeginInlineEdit = useCallback((payload) => {
|
|
773
|
-
|
|
774
|
-
false
|
|
805
|
+
const sent = bridgeClientRef.current?.sendCommand("beginInlineEdit", payload) ??
|
|
806
|
+
false;
|
|
807
|
+
logFieldValueDiagnostic("bridge-frame", "beginInlineEdit command", {
|
|
808
|
+
sent,
|
|
809
|
+
payload,
|
|
810
|
+
});
|
|
811
|
+
return sent;
|
|
775
812
|
}, []);
|
|
776
813
|
const sendCancelInlineEdit = useCallback((payload) => {
|
|
777
|
-
|
|
778
|
-
false
|
|
814
|
+
const sent = bridgeClientRef.current?.sendCommand("cancelInlineEdit", payload) ??
|
|
815
|
+
false;
|
|
816
|
+
logFieldValueDiagnostic("bridge-frame", "cancelInlineEdit command", {
|
|
817
|
+
sent,
|
|
818
|
+
payload,
|
|
819
|
+
});
|
|
820
|
+
return sent;
|
|
779
821
|
}, []);
|
|
780
822
|
const sendApplyFieldPatchCommand = useCallback((payload) => {
|
|
781
|
-
|
|
782
|
-
false
|
|
823
|
+
const sent = bridgeClientRef.current?.sendCommand("applyFieldPatch", payload) ??
|
|
824
|
+
false;
|
|
825
|
+
logFieldValueDiagnostic("bridge-frame", "applyFieldPatch command", {
|
|
826
|
+
sent,
|
|
827
|
+
payload: {
|
|
828
|
+
...payload,
|
|
829
|
+
value: summarizeFieldValue(payload.value),
|
|
830
|
+
},
|
|
831
|
+
});
|
|
832
|
+
return sent;
|
|
783
833
|
}, []);
|
|
784
834
|
const sendApplyRichTextCommand = useCallback((payload) => {
|
|
785
835
|
return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
|
|
@@ -828,6 +878,18 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
828
878
|
sendApplyFieldPatch: sendApplyFieldPatchCommand,
|
|
829
879
|
});
|
|
830
880
|
const beginTrackedBridgeInlineEdit = useCallback(async (interaction) => {
|
|
881
|
+
logFieldValueDiagnostic("bridge-frame", "begin tracked inline edit", {
|
|
882
|
+
previousActiveInlineEdit: activeBridgeInlineEditRef.current,
|
|
883
|
+
interaction: {
|
|
884
|
+
kind: interaction.kind,
|
|
885
|
+
elementKey: interaction.elementKey,
|
|
886
|
+
fieldId: interaction.fieldId,
|
|
887
|
+
item: interaction.item,
|
|
888
|
+
selectedText: summarizeFieldValue(interaction.selectedText),
|
|
889
|
+
selectionStartOffset: interaction.selectionStartOffset,
|
|
890
|
+
selectionEndOffset: interaction.selectionEndOffset,
|
|
891
|
+
},
|
|
892
|
+
});
|
|
831
893
|
clearActiveBridgeInlineEdit();
|
|
832
894
|
const started = await beginBridgeInlineEdit(interaction);
|
|
833
895
|
if (started && interaction.elementKey && interaction.fieldId) {
|
|
@@ -852,6 +914,10 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
852
914
|
};
|
|
853
915
|
setIsBridgeInlineEditing(true);
|
|
854
916
|
}
|
|
917
|
+
logFieldValueDiagnostic("bridge-frame", "tracked inline edit result", {
|
|
918
|
+
started,
|
|
919
|
+
nextActiveInlineEdit: activeBridgeInlineEditRef.current,
|
|
920
|
+
});
|
|
855
921
|
return started;
|
|
856
922
|
}, [
|
|
857
923
|
beginBridgeInlineEdit,
|
|
@@ -860,9 +926,17 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
860
926
|
editContextRef,
|
|
861
927
|
]);
|
|
862
928
|
const endTrackedBridgeFieldFocus = useCallback(() => {
|
|
929
|
+
logFieldValueDiagnostic("bridge-frame", "ending tracked field focus", {
|
|
930
|
+
activeInlineEdit: activeBridgeInlineEditRef.current,
|
|
931
|
+
});
|
|
863
932
|
clearActiveBridgeInlineEdit();
|
|
864
933
|
endBridgeFieldFocus();
|
|
865
934
|
}, [clearActiveBridgeInlineEdit, endBridgeFieldFocus]);
|
|
935
|
+
const beginTrackedBridgeInlineEditAfterSelection = useCallback((interaction) => {
|
|
936
|
+
window.setTimeout(() => {
|
|
937
|
+
void beginTrackedBridgeInlineEdit(interaction);
|
|
938
|
+
}, 0);
|
|
939
|
+
}, [beginTrackedBridgeInlineEdit]);
|
|
866
940
|
useEffect(() => {
|
|
867
941
|
const handleBridgeInlineEditRelease = (event) => {
|
|
868
942
|
const detail = event
|
|
@@ -876,6 +950,10 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
876
950
|
if (activeEdit.item && !bridgeItemMatches(activeEdit.item, field.item)) {
|
|
877
951
|
return;
|
|
878
952
|
}
|
|
953
|
+
logFieldValueDiagnostic("bridge-frame", "inline edit released by form", {
|
|
954
|
+
activeInlineEdit: activeEdit,
|
|
955
|
+
field: summarizeFieldDescriptor(field),
|
|
956
|
+
});
|
|
879
957
|
clearActiveBridgeInlineEdit();
|
|
880
958
|
bridgePatchSignatureRef.current.delete(`${activeEdit.elementKey}:${activeEdit.fieldId}`);
|
|
881
959
|
};
|
|
@@ -927,12 +1005,33 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
927
1005
|
},
|
|
928
1006
|
mode: showSuggestions ? "suggestions" : "baseline",
|
|
929
1007
|
});
|
|
930
|
-
|
|
1008
|
+
const result = {
|
|
931
1009
|
value: showSuggestions ? display.mergedValue : display.baselineValue,
|
|
932
1010
|
source: showSuggestions && display.hasSuggestions
|
|
933
1011
|
? "suggestion-preview"
|
|
934
1012
|
: "real",
|
|
935
1013
|
};
|
|
1014
|
+
logFieldValueDiagnostic("bridge-patch", "display value resolved", {
|
|
1015
|
+
showSuggestions,
|
|
1016
|
+
preferRepositoryValue,
|
|
1017
|
+
field: summarizeField(field),
|
|
1018
|
+
structureField: {
|
|
1019
|
+
elementKey: structureField.elementKey,
|
|
1020
|
+
fieldId: structureField.fieldId,
|
|
1021
|
+
item: structureField.item,
|
|
1022
|
+
textContent: summarizeFieldValue(structureField.textContent),
|
|
1023
|
+
isRichText: structureField.isRichText,
|
|
1024
|
+
},
|
|
1025
|
+
itemDescriptor,
|
|
1026
|
+
repositoryValue: summarizeFieldValue(repositoryValue),
|
|
1027
|
+
result: {
|
|
1028
|
+
...result,
|
|
1029
|
+
value: summarizeFieldValue(result.value),
|
|
1030
|
+
},
|
|
1031
|
+
modifiedFieldCount: fieldsContext?.modifiedFields?.length,
|
|
1032
|
+
suggestedEditCount: editContext.suggestedEdits?.length,
|
|
1033
|
+
});
|
|
1034
|
+
return result;
|
|
936
1035
|
}, [
|
|
937
1036
|
editContext.mode,
|
|
938
1037
|
editContext.showSuggestedEdits,
|
|
@@ -967,10 +1066,28 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
967
1066
|
source: patch.source ?? "",
|
|
968
1067
|
observedTextContent: observedTextContent ?? "",
|
|
969
1068
|
});
|
|
970
|
-
|
|
1069
|
+
const previousSignature = bridgePatchSignatureRef.current.get(cacheKey);
|
|
1070
|
+
if (previousSignature === signature) {
|
|
1071
|
+
logFieldValueDiagnostic("bridge-patch", "skipped duplicate send", {
|
|
1072
|
+
cacheKey,
|
|
1073
|
+
patch: {
|
|
1074
|
+
...patch,
|
|
1075
|
+
value: summarizeFieldValue(patch.value),
|
|
1076
|
+
},
|
|
1077
|
+
observedTextContent: summarizeFieldValue(observedTextContent),
|
|
1078
|
+
});
|
|
971
1079
|
return false;
|
|
972
1080
|
}
|
|
973
1081
|
bridgePatchSignatureRef.current.set(cacheKey, signature);
|
|
1082
|
+
logFieldValueDiagnostic("bridge-patch", "sending patch", {
|
|
1083
|
+
cacheKey,
|
|
1084
|
+
patch: {
|
|
1085
|
+
...patch,
|
|
1086
|
+
value: summarizeFieldValue(patch.value),
|
|
1087
|
+
},
|
|
1088
|
+
observedTextContent: summarizeFieldValue(observedTextContent),
|
|
1089
|
+
previousSignature,
|
|
1090
|
+
});
|
|
974
1091
|
bridge.sendCommand("applyFieldPatch", patch);
|
|
975
1092
|
return true;
|
|
976
1093
|
}, []);
|
|
@@ -1512,9 +1629,11 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1512
1629
|
if (!editor)
|
|
1513
1630
|
return;
|
|
1514
1631
|
const selectFromBridgeInteraction = (ids) => {
|
|
1515
|
-
|
|
1632
|
+
const unchanged = bridgeComponentSelectionsMatch(editor.getSelection(), ids);
|
|
1633
|
+
if (!unchanged) {
|
|
1516
1634
|
suppressNextSelectionScrollRef.current = true;
|
|
1517
1635
|
}
|
|
1636
|
+
perfTraceStep("frame:select-dispatched", { ids, unchanged });
|
|
1518
1637
|
editor.select(ids);
|
|
1519
1638
|
};
|
|
1520
1639
|
let effectiveInteraction = interaction;
|
|
@@ -1576,7 +1695,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1576
1695
|
selectFromBridgeInteraction([]);
|
|
1577
1696
|
if (isEditableFieldClick) {
|
|
1578
1697
|
clearSelectedRangeForEditableClick();
|
|
1579
|
-
|
|
1698
|
+
beginTrackedBridgeInlineEditAfterSelection(effectiveInteraction);
|
|
1580
1699
|
}
|
|
1581
1700
|
else if (interaction.kind === "click") {
|
|
1582
1701
|
closeInlineAiIfOpen();
|
|
@@ -1584,7 +1703,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1584
1703
|
}
|
|
1585
1704
|
return;
|
|
1586
1705
|
}
|
|
1587
|
-
const currentSelection = editor.
|
|
1706
|
+
const currentSelection = editor.getSelection();
|
|
1588
1707
|
if (interaction.kind === "contextMenu") {
|
|
1589
1708
|
if (pageViewContextRef.current) {
|
|
1590
1709
|
pageViewContextRef.current.bridgeInteraction = effectiveInteraction;
|
|
@@ -1714,7 +1833,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1714
1833
|
selectFromBridgeInteraction([componentId]);
|
|
1715
1834
|
if (isEditableFieldClick) {
|
|
1716
1835
|
clearSelectedRangeForEditableClick();
|
|
1717
|
-
|
|
1836
|
+
beginTrackedBridgeInlineEditAfterSelection(effectiveInteraction);
|
|
1718
1837
|
}
|
|
1719
1838
|
else {
|
|
1720
1839
|
closeInlineAiIfOpen();
|
|
@@ -1723,6 +1842,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1723
1842
|
}, [
|
|
1724
1843
|
applyBridgeTextCompletion,
|
|
1725
1844
|
beginTrackedBridgeInlineEdit,
|
|
1845
|
+
beginTrackedBridgeInlineEditAfterSelection,
|
|
1726
1846
|
clearBridgeTextCompletion,
|
|
1727
1847
|
endTrackedBridgeFieldFocus,
|
|
1728
1848
|
requestBridgeTextCompletion,
|
|
@@ -1844,9 +1964,12 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1844
1964
|
suggestionsCursorActive: !activeBridgeInlineEditRef.current &&
|
|
1845
1965
|
(editContextRef.current?.mode === "suggestions" ||
|
|
1846
1966
|
!!editContextRef.current?.showSuggestedEdits),
|
|
1847
|
-
selectedComponentIds: editContextRef.current?.
|
|
1967
|
+
selectedComponentIds: editContextRef.current?.getSelection() ?? [],
|
|
1848
1968
|
featureFlags: {
|
|
1849
1969
|
crossOriginBridge: true,
|
|
1970
|
+
// Lets the host emit its own perf logs + interaction perf
|
|
1971
|
+
// timestamps without needing a flag in the iframe's origin.
|
|
1972
|
+
perfTrace: isPerfTraceEnabled(),
|
|
1850
1973
|
},
|
|
1851
1974
|
version: bridge.getDiagnostics().editorVersion,
|
|
1852
1975
|
acknowledgedCapabilities: bridge.getAcknowledgedCapabilities(),
|
|
@@ -1859,12 +1982,31 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1859
1982
|
}
|
|
1860
1983
|
break;
|
|
1861
1984
|
case "structureUpdated":
|
|
1985
|
+
if (isPerfTraceActive()) {
|
|
1986
|
+
perfTraceStep("frame:structure-updated-received", {
|
|
1987
|
+
components: event.payload.structure.components.length,
|
|
1988
|
+
fields: event.payload.structure.fields.length,
|
|
1989
|
+
transportMs: Date.now() - event.timestamp,
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1862
1992
|
pageViewContext.setBridgeStructure(event.payload.structure);
|
|
1993
|
+
if (awaitingHostAppRefreshRenderRef.current) {
|
|
1994
|
+
// First structure change after a host-app refresh: the page's
|
|
1995
|
+
// own re-render has reached the DOM — the refresh is now truly
|
|
1996
|
+
// complete.
|
|
1997
|
+
completeCanvasRefresh();
|
|
1998
|
+
}
|
|
1863
1999
|
break;
|
|
1864
2000
|
case "pageSkeletonUpdated":
|
|
1865
2001
|
pageViewContext.setPageSkeleton(event.payload.pageSkeleton);
|
|
1866
2002
|
break;
|
|
1867
2003
|
case "geometryUpdated": {
|
|
2004
|
+
if (isPerfTraceActive()) {
|
|
2005
|
+
perfTraceStep("frame:geometry-updated-received", {
|
|
2006
|
+
targets: event.payload.geometry.targets.length,
|
|
2007
|
+
transportMs: Date.now() - event.timestamp,
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
1868
2010
|
latestBridgeScrollRef.current = event.payload.geometry.scroll;
|
|
1869
2011
|
pageViewContext.bridgeGeometry = event.payload.geometry;
|
|
1870
2012
|
pageViewContext.setBridgeGeometry(event.payload.geometry);
|
|
@@ -1888,11 +2030,22 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1888
2030
|
window.dispatchEvent(new CustomEvent(BRIDGE_DOM_UPDATED_EVENT));
|
|
1889
2031
|
break;
|
|
1890
2032
|
case "refreshStarted":
|
|
2033
|
+
logFieldValueDiagnostic("bridge-frame", "refresh started", {
|
|
2034
|
+
revision: event.payload.revision,
|
|
2035
|
+
activeInlineEdit: activeBridgeInlineEditRef.current,
|
|
2036
|
+
patchSignatureCount: bridgePatchSignatureRef.current.size,
|
|
2037
|
+
});
|
|
1891
2038
|
clearBridgeTextCompletion();
|
|
1892
2039
|
clearBridgePatchSignatures();
|
|
1893
2040
|
clearActiveBridgeInlineEdit();
|
|
1894
2041
|
break;
|
|
1895
2042
|
case "refreshCompleted":
|
|
2043
|
+
logFieldValueDiagnostic("bridge-frame", "refresh completed", {
|
|
2044
|
+
activeInlineEdit: activeBridgeInlineEditRef.current,
|
|
2045
|
+
hasStructure: !!event.payload.structure,
|
|
2046
|
+
hasGeometry: !!event.payload.geometry,
|
|
2047
|
+
structureFieldCount: event.payload.structure?.fields.length,
|
|
2048
|
+
});
|
|
1896
2049
|
// Runtime refresh replaces host DOM without a new bridge ready event.
|
|
1897
2050
|
// Field patches sent during the refresh window may have been cached
|
|
1898
2051
|
// against the previous DOM generation, so force the refreshed host to
|
|
@@ -1915,7 +2068,13 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1915
2068
|
scrollHandlerRef.current(event.payload.geometry.scroll.y);
|
|
1916
2069
|
}
|
|
1917
2070
|
}
|
|
1918
|
-
if (
|
|
2071
|
+
if (event.payload.scrollIntentApplied) {
|
|
2072
|
+
// The host already replayed a scroll issued during the refresh
|
|
2073
|
+
// (e.g. a minimap navigation) — restoring the pre-refresh
|
|
2074
|
+
// position now would clobber that newer intent.
|
|
2075
|
+
pendingRefreshScrollRef.current = undefined;
|
|
2076
|
+
}
|
|
2077
|
+
else if (pendingRefreshScrollRef.current) {
|
|
1919
2078
|
const targetScroll = pendingRefreshScrollRef.current;
|
|
1920
2079
|
const observedScroll = event.payload.geometry?.scroll ?? {
|
|
1921
2080
|
x: 0,
|
|
@@ -1923,6 +2082,18 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1923
2082
|
};
|
|
1924
2083
|
restorePendingRefreshScroll(iframe, targetScroll, observedScroll);
|
|
1925
2084
|
}
|
|
2085
|
+
if (event.payload.applied === "host-app") {
|
|
2086
|
+
// The page's own app (e.g. a Next.js rendering host) accepted
|
|
2087
|
+
// the refresh, but its re-render lands later as ordinary DOM
|
|
2088
|
+
// mutations. Completing now would reopen viewport reactions
|
|
2089
|
+
// (selection auto-scroll, scrollIntoView, the inserting
|
|
2090
|
+
// indicator) while geometry still describes the pre-edit DOM —
|
|
2091
|
+
// wait for the mutation-driven structureUpdated instead.
|
|
2092
|
+
awaitingHostAppRefreshRenderRef.current = true;
|
|
2093
|
+
}
|
|
2094
|
+
else {
|
|
2095
|
+
completeCanvasRefresh();
|
|
2096
|
+
}
|
|
1926
2097
|
setShowSpinner(false);
|
|
1927
2098
|
break;
|
|
1928
2099
|
case "selectionChanged":
|
|
@@ -1933,11 +2104,32 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1933
2104
|
if (event.payload.elementKey && event.payload.fieldId) {
|
|
1934
2105
|
setIsBridgeInlineEditing(true);
|
|
1935
2106
|
}
|
|
2107
|
+
logFieldValueDiagnostic("bridge-frame", "fieldValueChanged event", {
|
|
2108
|
+
elementKey: event.payload.elementKey,
|
|
2109
|
+
fieldId: event.payload.fieldId,
|
|
2110
|
+
item: event.payload.item,
|
|
2111
|
+
isRichText: event.payload.isRichText,
|
|
2112
|
+
value: summarizeFieldValue(event.payload.value),
|
|
2113
|
+
activeInlineEdit: activeBridgeInlineEditRef.current,
|
|
2114
|
+
});
|
|
1936
2115
|
handleBridgeFieldValueChanged(event.payload);
|
|
1937
2116
|
break;
|
|
1938
2117
|
case "inlineEditEnded":
|
|
2118
|
+
logFieldValueDiagnostic("bridge-frame", "inlineEditEnded event", {
|
|
2119
|
+
reason: event.payload.reason,
|
|
2120
|
+
elementKey: event.payload.elementKey,
|
|
2121
|
+
fieldId: event.payload.fieldId,
|
|
2122
|
+
item: event.payload.item,
|
|
2123
|
+
isRichText: event.payload.isRichText,
|
|
2124
|
+
value: summarizeFieldValue(event.payload.value),
|
|
2125
|
+
activeInlineEdit: activeBridgeInlineEditRef.current,
|
|
2126
|
+
});
|
|
1939
2127
|
handleBridgeInlineEditEnded(event.payload);
|
|
1940
2128
|
clearActiveBridgeInlineEdit();
|
|
2129
|
+
logFieldValueDiagnostic("bridge-frame", "active inline edit cleared after end", {
|
|
2130
|
+
fieldId: event.payload.fieldId,
|
|
2131
|
+
reason: event.payload.reason,
|
|
2132
|
+
});
|
|
1941
2133
|
break;
|
|
1942
2134
|
case "interaction":
|
|
1943
2135
|
if (event.payload.kind === "hover") {
|
|
@@ -1949,6 +2141,24 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1949
2141
|
else {
|
|
1950
2142
|
lastBridgeHoverInteractionRef.current = undefined;
|
|
1951
2143
|
}
|
|
2144
|
+
if (isPerfTraceEnabled() &&
|
|
2145
|
+
(event.payload.kind === "click" ||
|
|
2146
|
+
event.payload.kind === "doubleClick" ||
|
|
2147
|
+
event.payload.kind === "contextMenu")) {
|
|
2148
|
+
// Backdate to the host's DOM-event timestamp when available so
|
|
2149
|
+
// host-side dispatch (hit-test + deferred-click debounce) is
|
|
2150
|
+
// part of the waterfall; the envelope timestamp is when the
|
|
2151
|
+
// host posted the message.
|
|
2152
|
+
const eventAt = event.payload.perf?.eventAt;
|
|
2153
|
+
beginPerfTrace(`select-${event.payload.kind}`, {
|
|
2154
|
+
componentId: event.payload.componentId,
|
|
2155
|
+
fieldId: event.payload.fieldId,
|
|
2156
|
+
hostDispatchMs: eventAt !== undefined
|
|
2157
|
+
? event.timestamp - eventAt
|
|
2158
|
+
: "n/a (host without perf tracing)",
|
|
2159
|
+
transportMs: Date.now() - event.timestamp,
|
|
2160
|
+
}, eventAt ?? event.timestamp);
|
|
2161
|
+
}
|
|
1952
2162
|
pageViewContext.setBridgeInteraction(event.payload);
|
|
1953
2163
|
void handleBridgeInteraction(event.payload, iframe);
|
|
1954
2164
|
break;
|
|
@@ -2021,6 +2231,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2021
2231
|
scheduleBridgeGeometryRevision,
|
|
2022
2232
|
shouldTrackMinimapScroll,
|
|
2023
2233
|
restorePendingRefreshScroll,
|
|
2234
|
+
completeCanvasRefresh,
|
|
2024
2235
|
]);
|
|
2025
2236
|
useEffect(() => {
|
|
2026
2237
|
const repository = editContext.itemsRepository;
|
|
@@ -2410,6 +2621,8 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2410
2621
|
pendingRefreshScrollRef.current ??=
|
|
2411
2622
|
getIframeWindowScroll(iframeRef.current) ??
|
|
2412
2623
|
latestBridgeScrollRef.current;
|
|
2624
|
+
canvasRefreshInFlightSinceRef.current = Date.now();
|
|
2625
|
+
awaitingHostAppRefreshRenderRef.current = false;
|
|
2413
2626
|
setShowSpinner(true);
|
|
2414
2627
|
console.log(initialLoad
|
|
2415
2628
|
? "Initial load - setting iframe src"
|
|
@@ -2432,6 +2645,8 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2432
2645
|
}
|
|
2433
2646
|
console.log("Bridge - requesting iframe refresh");
|
|
2434
2647
|
pageViewContext.setIframeSupportsRefresh(true);
|
|
2648
|
+
canvasRefreshInFlightSinceRef.current = Date.now();
|
|
2649
|
+
awaitingHostAppRefreshRenderRef.current = false;
|
|
2435
2650
|
const accepted = bridge.sendCommand("refresh", {
|
|
2436
2651
|
url: renderUrl.toString(),
|
|
2437
2652
|
revision: editContext.revision,
|
|
@@ -2439,6 +2654,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2439
2654
|
});
|
|
2440
2655
|
if (!accepted) {
|
|
2441
2656
|
pendingRefreshScrollRef.current = undefined;
|
|
2657
|
+
canvasRefreshInFlightSinceRef.current = null;
|
|
2442
2658
|
pageViewContext.setIframeSupportsRefresh(false);
|
|
2443
2659
|
runFallbackRefresh();
|
|
2444
2660
|
}
|
|
@@ -2475,8 +2691,18 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2475
2691
|
]);
|
|
2476
2692
|
useEffect(() => {
|
|
2477
2693
|
if (fieldsContext?.focusedField) {
|
|
2478
|
-
|
|
2479
|
-
|
|
2694
|
+
// A refresh is being applied: the field's rect still describes the
|
|
2695
|
+
// pre-edit DOM (after a move, its old position — where the drag
|
|
2696
|
+
// started), so scrolling now would yank the canvas to a stale spot.
|
|
2697
|
+
// This fires on the post-operation auto-focus of the moved/added
|
|
2698
|
+
// component; a genuine user focus during a refresh loses one
|
|
2699
|
+
// auto-scroll, which is the lesser evil.
|
|
2700
|
+
if (isCanvasRefreshInFlight())
|
|
2701
|
+
return;
|
|
2702
|
+
// Event-time read: this effect is keyed on focusedField, not selection.
|
|
2703
|
+
const currentSelection = editContext.getSelection();
|
|
2704
|
+
if (currentSelection.length > 0 &&
|
|
2705
|
+
fieldsContext.focusedField.item.id !== currentSelection[0])
|
|
2480
2706
|
return;
|
|
2481
2707
|
const bounds = findBridgeFieldDocumentBounds(pageViewContextRef.current?.bridgeGeometry, fieldsContext.focusedField);
|
|
2482
2708
|
const activePageViewContext = pageViewContextRef.current;
|
|
@@ -2484,7 +2710,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2484
2710
|
scrollBridgeBoundsIntoView(activePageViewContext, bounds, latestBridgeScrollRef.current);
|
|
2485
2711
|
}
|
|
2486
2712
|
}
|
|
2487
|
-
}, [fieldsContext?.focusedField]);
|
|
2713
|
+
}, [fieldsContext?.focusedField, isCanvasRefreshInFlight]);
|
|
2488
2714
|
// Drive the selection-scroll suppression window from the drag lifecycle.
|
|
2489
2715
|
// Declared BEFORE the selection-scroll effect so it runs first in the same
|
|
2490
2716
|
// commit (when a drop changes both dragObject and selection at once).
|
|
@@ -2501,7 +2727,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2501
2727
|
}
|
|
2502
2728
|
}, [editContext.dragObject]);
|
|
2503
2729
|
useEffect(() => {
|
|
2504
|
-
const selectionKey =
|
|
2730
|
+
const selectionKey = canvasSelection.join("|");
|
|
2505
2731
|
const selectionChanged = selectionKey !== lastScrolledSelectionKeyRef.current;
|
|
2506
2732
|
lastScrolledSelectionKeyRef.current = selectionKey;
|
|
2507
2733
|
// The effect also re-runs when focusedField changes (e.g. a field blurs
|
|
@@ -2514,24 +2740,43 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2514
2740
|
suppressNextSelectionScrollRef.current = false;
|
|
2515
2741
|
return;
|
|
2516
2742
|
}
|
|
2743
|
+
// Programmatic selections (the drop target's parent, the post-operation
|
|
2744
|
+
// focus of a moved/added component) arm this suppression at their source:
|
|
2745
|
+
// their targets may still measure at pre-edit geometry, and unlike user
|
|
2746
|
+
// clicks they carry no intent to see the component. Deterministic — no
|
|
2747
|
+
// time window to lose against a slow operation or frozen host.
|
|
2748
|
+
if (editContext.consumeNextSelectionAutoScrollSuppression?.())
|
|
2749
|
+
return;
|
|
2517
2750
|
// Suppressed during a drag and briefly after it ends. The drag owns
|
|
2518
2751
|
// scrolling, and the drop's re-selection must not jump the page away from
|
|
2519
2752
|
// where the user dropped (toward stale pre-move geometry).
|
|
2520
2753
|
if (Date.now() < suppressSelectionScrollUntilRef.current)
|
|
2521
2754
|
return;
|
|
2522
|
-
|
|
2523
|
-
|
|
2755
|
+
// Suppressed while a refresh is applying: geometry still measures the
|
|
2756
|
+
// pre-refresh DOM (a just-moved component still sits at its old rect), so
|
|
2757
|
+
// scrolling now would target a stale position. The refresh restore owns
|
|
2758
|
+
// the viewport until the refreshed geometry lands.
|
|
2759
|
+
if (isCanvasRefreshInFlight())
|
|
2760
|
+
return;
|
|
2761
|
+
if (!fieldsContext?.focusedField && canvasSelection.length > 0) {
|
|
2762
|
+
const lastSelectedComponent = getComponentById(canvasSelection[canvasSelection.length - 1], pageViewContextRef.current.page);
|
|
2524
2763
|
if (lastSelectedComponent) {
|
|
2525
2764
|
editContext.setScrollIntoView(lastSelectedComponent.id);
|
|
2526
2765
|
}
|
|
2527
2766
|
}
|
|
2528
|
-
}, [
|
|
2767
|
+
}, [canvasSelection, fieldsContext?.focusedField, isCanvasRefreshInFlight]);
|
|
2529
2768
|
useEffect(() => {
|
|
2530
2769
|
if (!editContext.scrollIntoView)
|
|
2531
2770
|
return;
|
|
2532
2771
|
const activePageViewContext = pageViewContextRef.current;
|
|
2533
2772
|
if (!activePageViewContext)
|
|
2534
2773
|
return;
|
|
2774
|
+
// Defer (without consuming the request) while a refresh is applying:
|
|
2775
|
+
// resolving now would measure the target against pre-refresh geometry and
|
|
2776
|
+
// scroll to a stale rect. The effect re-runs when the refreshed geometry
|
|
2777
|
+
// arrives (bridgeGeometry dependency) and resolves against fresh bounds.
|
|
2778
|
+
if (isCanvasRefreshInFlight())
|
|
2779
|
+
return;
|
|
2535
2780
|
const scrollTarget = editContext.scrollIntoView;
|
|
2536
2781
|
const requestState = scrollIntoViewRequestRef.current;
|
|
2537
2782
|
const signature = getScrollIntoViewSignature(scrollTarget);
|
|
@@ -2566,6 +2811,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2566
2811
|
editContext.scrollIntoView,
|
|
2567
2812
|
pageViewContext.page,
|
|
2568
2813
|
pageViewContext.bridgeGeometry,
|
|
2814
|
+
isCanvasRefreshInFlight,
|
|
2569
2815
|
]);
|
|
2570
2816
|
useEffect(() => {
|
|
2571
2817
|
const handleMessage = (message) => {
|
|
@@ -2640,10 +2886,16 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2640
2886
|
};
|
|
2641
2887
|
}, [iframeElement]);
|
|
2642
2888
|
useEffect(() => {
|
|
2643
|
-
bridgeClientRef.current?.sendCommand("setSelection", {
|
|
2644
|
-
componentIds:
|
|
2889
|
+
const sent = bridgeClientRef.current?.sendCommand("setSelection", {
|
|
2890
|
+
componentIds: canvasSelection,
|
|
2645
2891
|
});
|
|
2646
|
-
|
|
2892
|
+
if (isPerfTraceActive()) {
|
|
2893
|
+
perfTraceStep("frame:setSelection-command-sent", {
|
|
2894
|
+
ids: canvasSelection,
|
|
2895
|
+
sent: sent ?? false,
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2898
|
+
}, [canvasSelection]);
|
|
2647
2899
|
useEffect(() => {
|
|
2648
2900
|
const bridge = bridgeClientRef.current;
|
|
2649
2901
|
bridge?.sendCommand("setPreviewMode", {
|
|
@@ -2776,6 +3028,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
2776
3028
|
if (pendingRefreshScrollRef.current) {
|
|
2777
3029
|
restorePendingRefreshScroll(iframeRef.current, pendingRefreshScrollRef.current);
|
|
2778
3030
|
}
|
|
3031
|
+
completeCanvasRefresh();
|
|
2779
3032
|
}
|
|
2780
3033
|
} }), iframeElement && (_jsx(IframeOverlayProvider, { iframe: iframeElement, mode: "scrolling", scrollScale: 1, visualScale: 1, invalidationKey: `${zoom}:${bridgeGeometryRevision}`, deferredInvalidationMs: ZOOM_TRANSITION_MS + 50, bleed: { top: 2, bottom: 2, left: 2 }, children: _jsx(PageEditorChrome, { iframe: iframeElement, compareView: compareView, pageViewContext: pageViewContext, onBridgeRichTextCommand: sendApplyRichTextCommand }) })), pageViewContext.deviceHeight && pageViewContext.device && (_jsx("div", { className: "bg-neutral-grey-5 relative z-40 h-full w-full" }))] }), shouldRenderMiniMap && (_jsx(MiniMap, { compareView: compareView, scroll: scroll, mainViewIframeRef: iframeRef, pageViewContext: pageViewContext })), showSpinner && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bg-neutral-grey-5/50 absolute top-0 left-0 h-full w-full" }), _jsx("div", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform", children: _jsx(Spinner, {}) })] }))] }), _jsx(FieldActionsOverlay, { ref: fieldActionsOverlay, generatorButtons: contextMenuFieldButtons, onActionClick: handleActionClick, onParameterizedActionExecute: handleParameterizedActionExecute, currentOverlay: editContext?.currentOverlay, fieldId: contextMenuField?.fieldId || "", setCurrentOverlay: editContext?.setCurrentOverlay || (() => { }), preSelectedAction: preSelectedAction })] }));
|
|
2781
3034
|
}
|