@parhelia/core 0.1.12927 → 0.1.12931
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/agents-view/AgentCard.js +4 -1
- package/dist/agents-view/AgentCard.js.map +1 -1
- package/dist/agents-view/AgentProfileIcon.js +8 -8
- package/dist/agents-view/AgentProfileIcon.js.map +1 -1
- package/dist/agents-view/AgentsView.js +1 -1
- 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/WorkspaceHomeView.d.ts +13 -0
- package/dist/components/WorkspaceHomeView.js +13 -0
- package/dist/components/WorkspaceHomeView.js.map +1 -0
- package/dist/components/ui/styled-dialog-title.d.ts +3 -1
- package/dist/components/ui/styled-dialog-title.js +2 -2
- package/dist/components/ui/styled-dialog-title.js.map +1 -1
- package/dist/config/config.js +7 -19
- package/dist/config/config.js.map +1 -1
- package/dist/config/notificationRoutes.js +1 -1
- package/dist/config/notificationRoutes.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +1 -1
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/FieldListField.js +8 -0
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ImageEditButton.js +14 -2
- package/dist/editor/ImageEditButton.js.map +1 -1
- package/dist/editor/ImageEditor.js +1 -1
- package/dist/editor/ImageEditor.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/PictureEditor.js +1 -1
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/ai/Agents.js +15 -79
- package/dist/editor/ai/Agents.js.map +1 -1
- 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/AgentMemoryPanel.d.ts +12 -0
- package/dist/editor/ai/terminal/components/AgentMemoryPanel.js +131 -0
- package/dist/editor/ai/terminal/components/AgentMemoryPanel.js.map +1 -0
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js +10 -2
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.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/AgentTerminalContextPanels.js +11 -0
- package/dist/editor/ai/terminal/components/AgentTerminalContextPanels.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js +27 -20
- package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.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/useAgentSettingsData.d.ts +8 -1
- package/dist/editor/ai/terminal/useAgentSettingsData.js +44 -5
- 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 +19 -0
- package/dist/editor/bridge/protocol.js +1 -1
- package/dist/editor/bridge/protocol.js.map +1 -1
- package/dist/editor/client/EditorShell.js +163 -67
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +35 -2
- package/dist/editor/client/editContext.js +8 -0
- package/dist/editor/client/editContext.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 +166 -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/componentCommands.js +2 -1
- package/dist/editor/commands/componentCommands.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/context-menu/InsertMenu.js +1 -1
- package/dist/editor/context-menu/InsertMenu.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/PageSelector.js +1 -1
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -2
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +6 -0
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -5
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/DragInsertionOverlay.d.ts +8 -1
- package/dist/editor/page-editor-chrome/DragInsertionOverlay.js +203 -51
- package/dist/editor/page-editor-chrome/DragInsertionOverlay.js.map +1 -1
- package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.d.ts +10 -0
- package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.js +236 -0
- package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.js.map +1 -0
- 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 +18 -4
- package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.d.ts +3 -0
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +5 -8
- 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 +20 -5
- 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.d.ts +10 -0
- package/dist/editor/page-editor-chrome/overlay/placeholderZones.js +61 -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 +94 -7
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/MiniMap.js +74 -9
- 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 +390 -54
- 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/minimapVisibility.d.ts +33 -0
- package/dist/editor/page-viewer/minimapVisibility.js +72 -0
- package/dist/editor/page-viewer/minimapVisibility.js.map +1 -0
- 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/page-viewer/renderUrl.js +29 -7
- package/dist/editor/page-viewer/renderUrl.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/CreateReviewConfirmStep.js +5 -4
- package/dist/editor/reviews/CreateReviewConfirmStep.js.map +1 -1
- package/dist/editor/reviews/CreateReviewDialog.js +120 -202
- package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
- package/dist/editor/reviews/CreateReviewItemsStep.d.ts +18 -0
- package/dist/editor/reviews/CreateReviewItemsStep.js +6 -0
- package/dist/editor/reviews/CreateReviewItemsStep.js.map +1 -0
- package/dist/editor/reviews/CreateReviewLanguagesStep.d.ts +9 -0
- package/dist/editor/reviews/CreateReviewLanguagesStep.js +36 -0
- package/dist/editor/reviews/CreateReviewLanguagesStep.js.map +1 -0
- package/dist/editor/reviews/CreateReviewReviewersStep.d.ts +17 -0
- package/dist/editor/reviews/CreateReviewReviewersStep.js +33 -0
- package/dist/editor/reviews/CreateReviewReviewersStep.js.map +1 -0
- package/dist/editor/reviews/CreateReviewSettingsStep.d.ts +21 -0
- package/dist/editor/reviews/CreateReviewSettingsStep.js +33 -0
- package/dist/editor/reviews/CreateReviewSettingsStep.js.map +1 -0
- package/dist/editor/reviews/MultiReviewManager.js +1 -4
- package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +1 -1
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/reviews/openCommentsFeedback.d.ts +2 -6
- package/dist/editor/reviews/openCommentsFeedback.js +3 -11
- package/dist/editor/reviews/openCommentsFeedback.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +38 -0
- package/dist/editor/services/agentService.js +42 -0
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +4 -1
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/editService.d.ts +1 -1
- package/dist/editor/services/editService.js +2 -2
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/services/reviewsService.d.ts +6 -0
- package/dist/editor/services/reviewsService.js +15 -0
- package/dist/editor/services/reviewsService.js.map +1 -1
- package/dist/editor/settings/panels/AgentProfileConfigPanel.js +2 -1
- package/dist/editor/settings/panels/AgentProfileConfigPanel.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +31 -10
- 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/tree-indicators/GutterColumns.js +24 -6
- package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
- package/dist/editor/ui/ItemCollectionEditor.js +4 -4
- package/dist/editor/ui/ItemCollectionEditor.js.map +1 -1
- package/dist/editor/ui/PerfectTree.js +5 -5
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/ui/PublishItemDialog.js +1 -1
- package/dist/editor/ui/PublishItemDialog.js.map +1 -1
- package/dist/editor/ui/PublishRestrictionsDialog.js +179 -148
- package/dist/editor/ui/PublishRestrictionsDialog.js.map +1 -1
- package/dist/editor/utils/keyboardNavigation.js +9 -2
- package/dist/editor/utils/keyboardNavigation.js.map +1 -1
- package/dist/editor/views/SingleEditView.js +32 -2
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/task-board/components/TaskBoardHomeView.js +3 -6
- package/dist/task-board/components/TaskBoardHomeView.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, GripVertical, } from "lucide-react";
|
|
5
|
+
import { Ban, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, GripVertical, } from "lucide-react";
|
|
6
6
|
import { useEditContext } from "../client/editContext";
|
|
7
7
|
import { useSlotContext } from "../views/editorSlotContext";
|
|
8
8
|
import { getAllPlaceholders, getComponentById } from "../componentTreeHelper";
|
|
9
|
+
import { CANVAS_REFRESH_COMPLETED_EVENT, } from "../page-viewer/pageViewContext";
|
|
9
10
|
import { cn } from "../../lib/utils";
|
|
10
11
|
import { useReducedMotion } from "../client/hooks/useReducedMotion";
|
|
11
12
|
import { clientToOverlay, findBridgeComponentTarget, hostViewportToOverlay, FRAME_OUTSET, measureBridgeComponentRect, measureBridgePlaceholderRect, rectEquals, } from "./overlay/geometry";
|
|
12
13
|
import { IFRAME_OVERLAY_BRIDGE_DRAG_GESTURE_EVENT, useIframeOverlay, } from "./overlay/IframeOverlayProvider";
|
|
13
14
|
import { useOverlayMeasurement } from "./overlay/useOverlayMeasurement";
|
|
14
15
|
import { getAutoScrollStep } from "../../lib/pointerDrag";
|
|
15
|
-
import { computePlaceholderZones, layoutAwareIndex, placeholderZonesEqual, resolveNearestGap, } from "./overlay/placeholderZones";
|
|
16
|
+
import { computePlaceholderZones, isWrapInsertionGap, layoutAwareIndex, placeholderZonesEqual, resolveNearestGap, } from "./overlay/placeholderZones";
|
|
16
17
|
import { getBetweenInsertionZoneRect, getDirectionalInsertionHitZoneRect, getDirectionalInsertionZoneRect, getEmptyPlaceholderNeutralZoneRect, InsertionLine, } from "./overlay/InsertionLine";
|
|
17
18
|
import { buildComponentDragObject } from "./componentDragObject";
|
|
18
19
|
import { buildInsertTargetLabel, formatPlaceholderDisplayName, } from "./insertTargetLabel";
|
|
@@ -31,6 +32,9 @@ const TEMPLATE_THUMBNAIL_WIDTH_PX = 176;
|
|
|
31
32
|
const TEMPLATE_THUMBNAIL_IMAGE_HEIGHT_PX = 96;
|
|
32
33
|
const OFFSCREEN_GUIDE_EDGE_INSET_PX = 18;
|
|
33
34
|
const OFFSCREEN_GUIDE_CROSS_AXIS_PADDING_PX = 24;
|
|
35
|
+
// Zones start empty for a frame or two while the first overlay measurement
|
|
36
|
+
// runs; only call the drag hopeless after they stay empty this long.
|
|
37
|
+
const NO_DROP_HINT_DELAY_MS = 200;
|
|
34
38
|
/**
|
|
35
39
|
* Full-canvas drop affordance shown while a component/template is being dragged.
|
|
36
40
|
* It drives the continuous {@link InsertionLine} from two sources:
|
|
@@ -233,7 +237,7 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
233
237
|
}, []);
|
|
234
238
|
const announce = useCallback((target) => {
|
|
235
239
|
const displayPlaceholderName = formatPlaceholderDisplayName(target.zone.description || target.zone.placeholder.name);
|
|
236
|
-
editContextRef.current?.setStatusMessage(_jsxs("div", { children: ["Drop into placeholder", " ", _jsx("span", { className: "font-bold", children: target.zone.placeholder.parentComponent?.name }), " ", "/
|
|
240
|
+
editContextRef.current?.setStatusMessage(_jsxs("div", { children: ["Drop into placeholder", " ", _jsx("span", { className: "font-bold", children: target.zone.placeholder.parentComponent?.name }), " ", "/ ", _jsx("span", { className: "font-bold", children: displayPlaceholderName }), " at position ", _jsx("span", { className: "font-bold", children: target.adjustedIndex + 1 })] }));
|
|
237
241
|
}, []);
|
|
238
242
|
// Ask the host to open a gap (shift siblings) at the resolved zone. Only for
|
|
239
243
|
// pointer gestures, where we know the moving element + its size.
|
|
@@ -277,6 +281,56 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
277
281
|
originalPlaceholderPreviewActiveRef.current = false;
|
|
278
282
|
pageViewContextRef.current?.requestBridgePreviewGap?.({ clear: true });
|
|
279
283
|
}, []);
|
|
284
|
+
// Pending "keep the original-position placeholder up" hold after a committed
|
|
285
|
+
// move; holds the clear until the canvas repaints (or the backstop fires).
|
|
286
|
+
const previewGapHoldCleanupRef = useRef(null);
|
|
287
|
+
const cancelPreviewGapHold = useCallback(() => {
|
|
288
|
+
previewGapHoldCleanupRef.current?.();
|
|
289
|
+
}, []);
|
|
290
|
+
// After a committed component move, the grayed-out placeholder the bridge
|
|
291
|
+
// draws over the original position must outlive the drag: the operation
|
|
292
|
+
// resolves when the server accepts it, but the canvas repaints later -
|
|
293
|
+
// clearing at drop leaves the stale component fully visible at its old spot
|
|
294
|
+
// until the refresh lands. Mirror holdInsertingUntilCanvasRefresh
|
|
295
|
+
// (EditorShell): clear on the first canvas refresh, with a timeout backstop,
|
|
296
|
+
// or immediately if the operation did not commit.
|
|
297
|
+
const holdPreviewGapUntilCanvasRefresh = useCallback((commit) => {
|
|
298
|
+
previewGapHoldCleanupRef.current?.();
|
|
299
|
+
const detach = () => {
|
|
300
|
+
if (previewGapHoldCleanupRef.current === detach) {
|
|
301
|
+
previewGapHoldCleanupRef.current = null;
|
|
302
|
+
}
|
|
303
|
+
window.removeEventListener(CANVAS_REFRESH_COMPLETED_EVENT, finish);
|
|
304
|
+
window.clearTimeout(timeoutId);
|
|
305
|
+
};
|
|
306
|
+
const finish = () => {
|
|
307
|
+
detach();
|
|
308
|
+
clearPreviewGap();
|
|
309
|
+
};
|
|
310
|
+
const timeoutId = window.setTimeout(finish, 15000);
|
|
311
|
+
window.addEventListener(CANVAS_REFRESH_COMPLETED_EVENT, finish);
|
|
312
|
+
// Cancel (a new drag owns the preview now): stop watching WITHOUT
|
|
313
|
+
// sending a clear that would wipe the new drag's placeholder.
|
|
314
|
+
previewGapHoldCleanupRef.current = detach;
|
|
315
|
+
const isCurrentHold = () => previewGapHoldCleanupRef.current === detach;
|
|
316
|
+
// Promise.resolve guards non-async doubles (test mocks) of
|
|
317
|
+
// droppedInPlaceholder.
|
|
318
|
+
void Promise.resolve(commit)
|
|
319
|
+
.then((result) => {
|
|
320
|
+
if (result?.committed === false && isCurrentHold())
|
|
321
|
+
finish();
|
|
322
|
+
})
|
|
323
|
+
.catch(() => {
|
|
324
|
+
if (isCurrentHold())
|
|
325
|
+
finish();
|
|
326
|
+
});
|
|
327
|
+
}, [clearPreviewGap]);
|
|
328
|
+
useEffect(() => () => {
|
|
329
|
+
// Detach only: unmount tears down the whole chrome (view switch or the
|
|
330
|
+
// refresh itself re-keying the overlay); an unconditional clear here
|
|
331
|
+
// would race a hold that a remounted overlay no longer tracks.
|
|
332
|
+
previewGapHoldCleanupRef.current?.();
|
|
333
|
+
}, []);
|
|
280
334
|
const startCanceledDragReturnAnimation = useCallback(() => {
|
|
281
335
|
if (reducedMotionRef.current)
|
|
282
336
|
return false;
|
|
@@ -485,6 +539,32 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
485
539
|
window.removeEventListener("keydown", cancelOnEscape, true);
|
|
486
540
|
};
|
|
487
541
|
}, [cancelCurrentDrag, isDragging]);
|
|
542
|
+
// No gap anywhere accepts this drag — for a component move that means it
|
|
543
|
+
// already sits in its only possible position (its own adjacent gaps are
|
|
544
|
+
// no-ops and hidden). Surface that instead of leaving the drag mute: a hint
|
|
545
|
+
// rides the drag cursor (gesture drags) or tops the canvas (native drags),
|
|
546
|
+
// and the status line announces it. Delayed so the initial empty measurement
|
|
547
|
+
// doesn't flash the hint at every drag start.
|
|
548
|
+
const noDropPositionMessage = isDragging
|
|
549
|
+
? editContext?.dragObject?.type === "component"
|
|
550
|
+
? "Already in its only possible position — nowhere else to move it"
|
|
551
|
+
: "No compatible placeholder on this page"
|
|
552
|
+
: null;
|
|
553
|
+
const zonesEmptyWhileDragging = isDragging && zones.length === 0;
|
|
554
|
+
const [showNoDropHint, setShowNoDropHint] = useState(false);
|
|
555
|
+
useEffect(() => {
|
|
556
|
+
if (!zonesEmptyWhileDragging) {
|
|
557
|
+
setShowNoDropHint(false);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const timer = window.setTimeout(() => setShowNoDropHint(true), NO_DROP_HINT_DELAY_MS);
|
|
561
|
+
return () => window.clearTimeout(timer);
|
|
562
|
+
}, [zonesEmptyWhileDragging]);
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
if (showNoDropHint && noDropPositionMessage) {
|
|
565
|
+
editContextRef.current?.setStatusMessage(_jsx("div", { children: noDropPositionMessage }));
|
|
566
|
+
}
|
|
567
|
+
}, [showNoDropHint, noDropPositionMessage]);
|
|
488
568
|
// Tell the "+" dots which gap is currently targeted so the matching dot can
|
|
489
569
|
// light up - but only while the insertion LINE is actually visible at that gap,
|
|
490
570
|
// so the dot highlight and the line appear together. (The resolved gap is set
|
|
@@ -571,6 +651,9 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
571
651
|
};
|
|
572
652
|
if (detail.phase === "start") {
|
|
573
653
|
clearReturnPreview();
|
|
654
|
+
// A pending post-drop hold must stop watching: the new drag owns the
|
|
655
|
+
// original-position placeholder from here on.
|
|
656
|
+
cancelPreviewGapHold();
|
|
574
657
|
canceledGestureHandledRef.current = false;
|
|
575
658
|
let component;
|
|
576
659
|
let dragObject = detail.dragObject;
|
|
@@ -581,7 +664,7 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
581
664
|
: undefined;
|
|
582
665
|
if (!component)
|
|
583
666
|
return;
|
|
584
|
-
const selection = ec.
|
|
667
|
+
const selection = ec.getSelection();
|
|
585
668
|
inMulti = selection.length > 1 && selection.includes(component.id);
|
|
586
669
|
const selectedIds = inMulti ? selection : [component.id];
|
|
587
670
|
dragObject = buildComponentDragObject(component, selectedIds, pvc.page);
|
|
@@ -657,9 +740,16 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
657
740
|
: null;
|
|
658
741
|
const target = point ? resolveZoneAtOverlayPoint(point) : null;
|
|
659
742
|
const commitTarget = !detail.canceled && isDropCommitTarget(target) ? target : null;
|
|
660
|
-
|
|
743
|
+
// On a committed move, keep the original-position placeholder up past
|
|
744
|
+
// the drag - it is cleared when the canvas refresh lands (see
|
|
745
|
+
// holdPreviewGapUntilCanvasRefresh).
|
|
746
|
+
const holdOriginalPreview = !!commitTarget && originalPlaceholderPreviewActiveRef.current;
|
|
747
|
+
clearDragPresentation({ clearPreview: !holdOriginalPreview });
|
|
661
748
|
if (commitTarget) {
|
|
662
|
-
ec.droppedInPlaceholder(commitTarget.zone.placeholder.key, commitTarget.adjustedIndex, undefined, commitTarget.zone.anchor, undefined, commitTarget.zone.position, commitTarget.color);
|
|
749
|
+
const commit = ec.droppedInPlaceholder(commitTarget.zone.placeholder.key, commitTarget.adjustedIndex, undefined, commitTarget.zone.anchor, undefined, commitTarget.zone.position, commitTarget.color);
|
|
750
|
+
if (holdOriginalPreview) {
|
|
751
|
+
holdPreviewGapUntilCanvasRefresh(commit);
|
|
752
|
+
}
|
|
663
753
|
}
|
|
664
754
|
ec.dragEnd();
|
|
665
755
|
}
|
|
@@ -670,16 +760,22 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
670
760
|
overlay.iframe,
|
|
671
761
|
resolveZoneAtOverlayPoint,
|
|
672
762
|
applyResolvedZone,
|
|
763
|
+
cancelPreviewGapHold,
|
|
673
764
|
clearReturnPreview,
|
|
674
765
|
clearDragPresentation,
|
|
766
|
+
holdPreviewGapUntilCanvasRefresh,
|
|
675
767
|
startCanceledDragReturnAnimation,
|
|
676
768
|
]);
|
|
677
|
-
// Reset when a drag ends.
|
|
769
|
+
// Reset when a drag ends. A pending post-drop hold keeps the original
|
|
770
|
+
// placeholder alive until the canvas refresh, so don't clear the preview
|
|
771
|
+
// out from under it here.
|
|
678
772
|
useEffect(() => {
|
|
679
773
|
if (!isDragging) {
|
|
680
774
|
if (returnPreviewRef.current)
|
|
681
775
|
return;
|
|
682
|
-
clearDragPresentation(
|
|
776
|
+
clearDragPresentation({
|
|
777
|
+
clearPreview: !previewGapHoldCleanupRef.current,
|
|
778
|
+
});
|
|
683
779
|
}
|
|
684
780
|
}, [isDragging, clearDragPresentation]);
|
|
685
781
|
// Never leave a status message lingering after unmount.
|
|
@@ -708,8 +804,12 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
708
804
|
const dragCursorSnapshotSize = snapshot
|
|
709
805
|
? snapshotDisplaySize(snapshot, dragCursorShrinkPx)
|
|
710
806
|
: null;
|
|
711
|
-
const
|
|
712
|
-
|
|
807
|
+
const templateThumbnailSize = shrinkSizeProportionally({
|
|
808
|
+
width: TEMPLATE_THUMBNAIL_WIDTH_PX,
|
|
809
|
+
height: TEMPLATE_THUMBNAIL_IMAGE_HEIGHT_PX,
|
|
810
|
+
}, dragCursorShrinkPx);
|
|
811
|
+
const templateThumbnailWidth = templateThumbnailSize.width;
|
|
812
|
+
const templateThumbnailImageHeight = templateThumbnailSize.height;
|
|
713
813
|
const dragCursorSizeTransition = reducedMotion
|
|
714
814
|
? undefined
|
|
715
815
|
: "width 120ms ease-out, height 120ms ease-out";
|
|
@@ -775,31 +875,37 @@ export function DragInsertionOverlay({ pageViewContext, onActiveTargetChange, })
|
|
|
775
875
|
// of remounting and replaying the fade-in at every gap - that per-gap
|
|
776
876
|
// re-fade is the "spots quickly flashing" as the cursor sweeps. The
|
|
777
877
|
// enter fade now plays once when the bar first appears.
|
|
778
|
-
, { zone: active.zone, placeholderRect: active.placeholderRect, visualMode: active.visualMode, visualAnchor: active.visualAnchor, visualTargetRect: active.visualTargetRect, color: active.color, label: buildActiveInsertionLabel(active), animate: !reducedMotion, phase: "enter" }, "active-bar")), offscreenGuides.map((guide) => (_jsx(OffscreenInsertionGuidePill, { guide: guide }, guide.direction)))
|
|
878
|
+
, { zone: active.zone, placeholderRect: active.placeholderRect, visualMode: active.visualMode, visualAnchor: active.visualAnchor, visualTargetRect: active.visualTargetRect, color: active.color, label: buildActiveInsertionLabel(active), animate: !reducedMotion, phase: "enter" }, "active-bar")), offscreenGuides.map((guide) => (_jsx(OffscreenInsertionGuidePill, { guide: guide }, guide.direction))), !gestureActive && showNoDropHint && noDropPositionMessage && (
|
|
879
|
+
// Native (HTML5) drags have no cursor-anchored preview to attach the
|
|
880
|
+
// hint to, so it tops the canvas instead.
|
|
881
|
+
_jsxs("div", { "data-testid": "drag-no-drop-banner", className: "pointer-events-none absolute top-4 left-1/2 flex -translate-x-1/2 items-center gap-1.5 rounded-[4px] bg-neutral-grey-100/90 px-2.5 py-1.5 text-xs leading-none font-medium whitespace-nowrap text-white shadow-md backdrop-blur-sm", style: { zIndex: 900 }, children: [_jsx(Ban, { className: "h-3.5 w-3.5 shrink-0", strokeWidth: 2 }), _jsx("span", { className: "truncate", children: noDropPositionMessage })] }))] }), gestureActive &&
|
|
779
882
|
cursorClient &&
|
|
780
883
|
editContext.dragObject &&
|
|
781
884
|
createPortal(
|
|
782
885
|
// Cursor-anchored: the dragged thing (live snapshot, else a chip). The
|
|
783
886
|
// insertion bar names the target placeholder, so the cursor stays clean.
|
|
784
|
-
|
|
887
|
+
_jsxs("div", { ref: dragCursorPreviewRef, "data-testid": "drag-cursor-preview", className: "pointer-events-none fixed flex flex-col items-start gap-1.5", style: {
|
|
785
888
|
left: cursorClient.x + 14,
|
|
786
889
|
top: cursorClient.y + 14,
|
|
787
890
|
zIndex: 99999,
|
|
788
|
-
}, children:
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
width: templateThumbnailWidth,
|
|
796
|
-
transition: dragCursorSizeTransition,
|
|
797
|
-
}, children: [_jsx("div", { "data-testid": "drag-cursor-thumbnail-label", className: "border-border-default truncate border-b px-2 py-1 text-xs leading-4 font-medium text-neutral-grey-100", title: dragCursorLabel, children: dragCursorLabel }), _jsx("div", { className: "p-1.5", children: _jsx("img", { src: dragCursorThumbnailUrl, alt: "", "aria-hidden": "true", draggable: false, className: "block w-full rounded-sm object-contain", style: {
|
|
798
|
-
height: templateThumbnailImageHeight,
|
|
891
|
+
}, children: [showNoDropHint && noDropPositionMessage && (
|
|
892
|
+
// Ride ABOVE the dragged preview so the hint sits on top of the
|
|
893
|
+
// snapshot/thumbnail image rather than dangling below it.
|
|
894
|
+
_jsxs("div", { "data-testid": "drag-cursor-no-drop", className: "bg-neutral-grey-100/90 flex items-center gap-1.5 rounded-[4px] px-2 py-1 text-xs leading-none font-medium whitespace-nowrap text-white shadow-md", children: [_jsx(Ban, { className: "h-3.5 w-3.5 shrink-0", strokeWidth: 2 }), _jsx("span", { className: "truncate", children: noDropPositionMessage })] })), snapshot ? (_jsx("div", { "data-testid": "drag-cursor-snapshot", className: "overflow-hidden rounded-md bg-white shadow-lg ring-1 ring-black/10", style: { opacity: 0.96 }, children: _jsx("div", { className: "p-1.5", children: _jsx("img", { src: snapshot.dataUrl, alt: "", "aria-hidden": "true", draggable: false, className: "block rounded-sm", style: {
|
|
895
|
+
width: dragCursorSnapshotSize?.width,
|
|
896
|
+
height: dragCursorSnapshotSize?.height,
|
|
897
|
+
objectFit: "contain",
|
|
799
898
|
transition: dragCursorSizeTransition,
|
|
800
|
-
} }) })
|
|
801
|
-
|
|
802
|
-
:
|
|
899
|
+
} }) }) })) : dragCursorThumbnailUrl ? (_jsxs("div", { "data-testid": "drag-cursor-thumbnail", className: "overflow-hidden rounded-md bg-white shadow-lg ring-1 ring-black/10", style: {
|
|
900
|
+
opacity: 0.96,
|
|
901
|
+
width: templateThumbnailWidth,
|
|
902
|
+
transition: dragCursorSizeTransition,
|
|
903
|
+
}, children: [_jsx("div", { "data-testid": "drag-cursor-thumbnail-label", className: "border-border-default text-neutral-grey-100 truncate border-b px-2 py-1 text-xs leading-4 font-medium", title: dragCursorLabel, children: dragCursorLabel }), _jsx("div", { className: "p-1.5", children: _jsx("img", { src: dragCursorThumbnailUrl, alt: "", "aria-hidden": "true", draggable: false, className: "block w-full rounded-sm object-contain", style: {
|
|
904
|
+
height: templateThumbnailImageHeight,
|
|
905
|
+
transition: dragCursorSizeTransition,
|
|
906
|
+
} }) })] })) : (_jsxs("div", { "data-testid": "drag-cursor-chip", className: "bg-component-default flex items-center gap-1 rounded px-2 py-1 text-sm whitespace-nowrap text-white shadow-md", children: [_jsx(GripVertical, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), (editContext.dragObject.components?.length ?? 0) > 1
|
|
907
|
+
? `${editContext.dragObject.components.length} components`
|
|
908
|
+
: editContext.dragObject.name] }))] }), document.body), returnPreviewPortal] }));
|
|
803
909
|
}
|
|
804
910
|
function ReturningDragPreview({ preview }) {
|
|
805
911
|
const [atRest, setAtRest] = useState(false);
|
|
@@ -1039,22 +1145,32 @@ function ParentComponentOutline({ component, pageViewContext, color, animate, })
|
|
|
1039
1145
|
} }));
|
|
1040
1146
|
}
|
|
1041
1147
|
// Size the floating snapshot from 75% of the component's captured width, capped
|
|
1042
|
-
// to the viewport.
|
|
1043
|
-
// width/height boxes do not distort captures with unusual aspect ratios.
|
|
1148
|
+
// to the viewport.
|
|
1044
1149
|
function snapshotDisplaySize(s, shrinkPx = 0) {
|
|
1045
1150
|
const viewportWidth = typeof window !== "undefined" ? window.innerWidth : 1280;
|
|
1046
1151
|
const viewportHeight = typeof window !== "undefined" ? window.innerHeight : 800;
|
|
1047
1152
|
const width = Math.round(Math.min(Math.max(1, s.width * 0.75), viewportWidth * 0.9));
|
|
1048
1153
|
const aspectHeight = s.width > 0 ? Math.round((width * Math.max(1, s.height)) / s.width) : width;
|
|
1049
1154
|
const height = Math.round(Math.min(Math.max(1, aspectHeight), viewportHeight * 0.9));
|
|
1155
|
+
return shrinkSizeProportionally({ width, height }, shrinkPx);
|
|
1156
|
+
}
|
|
1157
|
+
// Shrink both dimensions by ONE scale factor (the larger dimension loses
|
|
1158
|
+
// `shrinkPx`), preserving aspect ratio. Subtracting the pixels per axis
|
|
1159
|
+
// squashed extreme aspect ratios: a flat accordion-row capture lost most of
|
|
1160
|
+
// its height but almost none of its width, so the hover-over-drop-zone shrink
|
|
1161
|
+
// squeezed it into a sliver.
|
|
1162
|
+
export function shrinkSizeProportionally(size, shrinkPx) {
|
|
1163
|
+
const width = Math.max(1, Math.round(size.width));
|
|
1164
|
+
const height = Math.max(1, Math.round(size.height));
|
|
1165
|
+
if (shrinkPx <= 0)
|
|
1166
|
+
return { width, height };
|
|
1167
|
+
const maxDimension = Math.max(width, height);
|
|
1168
|
+
const factor = Math.max(1, maxDimension - shrinkPx) / maxDimension;
|
|
1050
1169
|
return {
|
|
1051
|
-
width:
|
|
1052
|
-
height:
|
|
1170
|
+
width: Math.max(1, Math.round(width * factor)),
|
|
1171
|
+
height: Math.max(1, Math.round(height * factor)),
|
|
1053
1172
|
};
|
|
1054
1173
|
}
|
|
1055
|
-
function shrinkDimension(value, shrinkPx) {
|
|
1056
|
-
return Math.max(1, Math.round(value) - shrinkPx);
|
|
1057
|
-
}
|
|
1058
1174
|
function formatDragCursorLabel(dragObject) {
|
|
1059
1175
|
const componentCount = dragObject.components?.length ?? 0;
|
|
1060
1176
|
if (componentCount > 1)
|
|
@@ -1170,28 +1286,20 @@ function resolveInsertionZoneVisualTarget(zone, point, placeholderRect, geometry
|
|
|
1170
1286
|
}
|
|
1171
1287
|
: null;
|
|
1172
1288
|
if (!previousComponent && beforeNext) {
|
|
1173
|
-
|
|
1174
|
-
return pointInDirectionalEdgeBand(point, beforeNext.rect, beforeNext.anchor)
|
|
1175
|
-
? { ...beforeNext, mode: "directional" }
|
|
1176
|
-
: {
|
|
1177
|
-
...resolveHiddenInsertionVisual(zone),
|
|
1178
|
-
ownerComponentId: beforeNext.ownerComponentId,
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
return resolveNeutralInsertionVisual(zone, placeholderRect, point);
|
|
1289
|
+
return resolveSingleEdgeVisual(zone, point, placeholderRect, beforeNext);
|
|
1182
1290
|
}
|
|
1183
1291
|
if (!nextComponent && afterPrevious) {
|
|
1184
|
-
|
|
1185
|
-
return pointInDirectionalEdgeBand(point, afterPrevious.rect, afterPrevious.anchor)
|
|
1186
|
-
? { ...afterPrevious, mode: "directional" }
|
|
1187
|
-
: {
|
|
1188
|
-
...resolveHiddenInsertionVisual(zone),
|
|
1189
|
-
ownerComponentId: afterPrevious.ownerComponentId,
|
|
1190
|
-
};
|
|
1191
|
-
}
|
|
1192
|
-
return resolveNeutralInsertionVisual(zone, placeholderRect, point);
|
|
1292
|
+
return resolveSingleEdgeVisual(zone, point, placeholderRect, afterPrevious);
|
|
1193
1293
|
}
|
|
1194
1294
|
if (afterPrevious && beforeNext) {
|
|
1295
|
+
// A wrap gap (next starts a new row/column of a wrapping grid) has no
|
|
1296
|
+
// shared gutter for a "between" band - the min→max cross span would
|
|
1297
|
+
// stretch the bar across both rows. Treat the gap as the previous
|
|
1298
|
+
// component's trailing edge instead, which is where `zone.position`
|
|
1299
|
+
// (and the "+" dot) already sits.
|
|
1300
|
+
if (isWrapInsertionGap(zone.orientation, afterPrevious.rect, beforeNext.rect)) {
|
|
1301
|
+
return resolveSingleEdgeVisual(zone, point, placeholderRect, afterPrevious);
|
|
1302
|
+
}
|
|
1195
1303
|
const betweenRect = getBetweenInsertionZoneRect({
|
|
1196
1304
|
zone,
|
|
1197
1305
|
previousRect: afterPrevious.rect,
|
|
@@ -1225,6 +1333,50 @@ function resolveInsertionZoneVisualTarget(zone, point, placeholderRect, geometry
|
|
|
1225
1333
|
}
|
|
1226
1334
|
return resolveNeutralInsertionVisual(zone, placeholderRect, point);
|
|
1227
1335
|
}
|
|
1336
|
+
// A gap with a single visual owner: a terminal gap (before the first / after
|
|
1337
|
+
// the last component) or a wrap gap resolved to its previous component. The
|
|
1338
|
+
// component's edge band and the outside band both light the directional bar;
|
|
1339
|
+
// anywhere else falls back to neutral/hidden.
|
|
1340
|
+
function resolveSingleEdgeVisual(zone, point, placeholderRect, edge) {
|
|
1341
|
+
if (pointInOverlayRect(point, edge.rect)) {
|
|
1342
|
+
return pointInDirectionalEdgeBand(point, edge.rect, edge.anchor)
|
|
1343
|
+
? { ...edge, mode: "directional" }
|
|
1344
|
+
: {
|
|
1345
|
+
...resolveHiddenInsertionVisual(zone),
|
|
1346
|
+
ownerComponentId: edge.ownerComponentId,
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
const outsideEdge = resolveDirectionalOutsideEdgeVisual(zone, point, edge);
|
|
1350
|
+
if (outsideEdge)
|
|
1351
|
+
return outsideEdge;
|
|
1352
|
+
return resolveNeutralInsertionVisual(zone, placeholderRect, point);
|
|
1353
|
+
}
|
|
1354
|
+
// An end gap's "+" dot and insertion band hang OUTSIDE the terminal
|
|
1355
|
+
// component's edge (below the last / above the first component), where the
|
|
1356
|
+
// component-rect checks above can't match. Without this, the first approach
|
|
1357
|
+
// from that side resolved "neutral" (nothing lights up) while the same spot
|
|
1358
|
+
// activated fine once the zone was owned - the pointer had to cross into the
|
|
1359
|
+
// component's edge band once to arm it. Reuse the owned-zone hit rect so the
|
|
1360
|
+
// first approach and the hysteresis accept the exact same region.
|
|
1361
|
+
function resolveDirectionalOutsideEdgeVisual(zone, point, edge) {
|
|
1362
|
+
const hitRect = getDirectionalInsertionHitZoneRect({
|
|
1363
|
+
zone,
|
|
1364
|
+
targetRect: edge.rect,
|
|
1365
|
+
anchor: edge.anchor,
|
|
1366
|
+
});
|
|
1367
|
+
if (!pointInOverlayRect(point, hitRect))
|
|
1368
|
+
return null;
|
|
1369
|
+
const visualDropZoneRect = getDirectionalInsertionZoneRect({
|
|
1370
|
+
zone,
|
|
1371
|
+
targetRect: edge.rect,
|
|
1372
|
+
anchor: edge.anchor,
|
|
1373
|
+
});
|
|
1374
|
+
return {
|
|
1375
|
+
...edge,
|
|
1376
|
+
mode: "directional",
|
|
1377
|
+
zoneHovered: pointInOverlayRect(point, visualDropZoneRect),
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1228
1380
|
function resolveNeutralInsertionVisual(zone, placeholderRect, point) {
|
|
1229
1381
|
const targetRect = zone.targetRect ?? placeholderRect;
|
|
1230
1382
|
const isEmptyPlaceholder = zone.placeholder.components.length === 0;
|