@alpaca-editor/core 1.0.4064 → 1.0.4065
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/editor/ContextMenu.js +0 -2
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/ai/Agents.js +33 -8
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/client/EditorClient.js +17 -19
- package/dist/editor/client/EditorClient.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +2 -2
- package/dist/editor/client/operations.js +9 -6
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +1 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.js +0 -3
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
- package/dist/editor/menubar/ToolbarFactory.js +5 -2
- package/dist/editor/menubar/ToolbarFactory.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +70 -4
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/reviews/CommentEditor.js +2 -2
- package/dist/editor/reviews/CommentEditor.js.map +1 -1
- package/dist/editor/reviews/Reviews.js +2 -2
- package/dist/editor/reviews/Reviews.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +157 -49
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/Debug.js +1 -1
- package/dist/editor/sidebar/Debug.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/styles.css +5 -4
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/editor/ContextMenu.tsx +0 -2
- package/src/editor/ai/Agents.tsx +62 -15
- package/src/editor/client/EditorClient.tsx +18 -20
- package/src/editor/client/editContext.ts +2 -2
- package/src/editor/client/operations.ts +9 -8
- package/src/editor/commands/componentCommands.tsx +1 -1
- package/src/editor/field-types/richtext/components/EditorDropdown.css +1 -0
- package/src/editor/field-types/richtext/contextMenuFactory.tsx +0 -4
- package/src/editor/menubar/ToolbarFactory.tsx +6 -2
- package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +23 -19
- package/src/editor/page-viewer/PageViewerFrame.tsx +80 -4
- package/src/editor/reviews/CommentEditor.tsx +2 -2
- package/src/editor/reviews/Reviews.tsx +2 -0
- package/src/editor/sidebar/ComponentTree.tsx +223 -69
- package/src/editor/sidebar/Debug.tsx +1 -1
- package/src/revision.ts +2 -2
- package/src/types.ts +1 -1
- package/styles.css +0 -5
|
@@ -406,13 +406,69 @@ export function PageViewerFrame({
|
|
|
406
406
|
|
|
407
407
|
if (editContextRef.current?.isRefreshing) return;
|
|
408
408
|
|
|
409
|
-
|
|
409
|
+
let componentId = findNearestEditableComponentId(target as HTMLElement);
|
|
410
|
+
|
|
411
|
+
// If layout components are hidden, block selection on layout components
|
|
412
|
+
if (
|
|
413
|
+
componentId &&
|
|
414
|
+
editContextRef.current &&
|
|
415
|
+
editContextRef.current.page &&
|
|
416
|
+
editContextRef.current.showLayoutComponents === false
|
|
417
|
+
) {
|
|
418
|
+
const comp = getComponentById(componentId, editContextRef.current.page);
|
|
419
|
+
if (comp?.layoutId) {
|
|
420
|
+
componentId = undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
410
423
|
if (editContextRef.current?.currentOverlay)
|
|
411
424
|
editContextRef.current?.setCurrentOverlay(undefined);
|
|
412
425
|
|
|
413
426
|
if (componentId) {
|
|
414
|
-
|
|
415
|
-
|
|
427
|
+
const currentSelection = editContextRef.current?.selection || [];
|
|
428
|
+
|
|
429
|
+
if (event.shiftKey && currentSelection.length > 0) {
|
|
430
|
+
const page = pageViewContextRef.current?.page;
|
|
431
|
+
if (page) {
|
|
432
|
+
// Build ordered list of visible component ids by DOM order
|
|
433
|
+
const doc = iframeRef.current?.contentDocument;
|
|
434
|
+
const orderedIds: string[] = [];
|
|
435
|
+
if (doc) {
|
|
436
|
+
const all = Array.from(
|
|
437
|
+
doc.querySelectorAll("[data-component-id]"),
|
|
438
|
+
) as HTMLElement[];
|
|
439
|
+
for (const el of all) {
|
|
440
|
+
const id = el.getAttribute("data-component-id");
|
|
441
|
+
if (!id) continue;
|
|
442
|
+
// Skip layout components when hidden
|
|
443
|
+
if (
|
|
444
|
+
editContextRef.current?.showLayoutComponents === false &&
|
|
445
|
+
getComponentById(id, page)?.layoutId
|
|
446
|
+
)
|
|
447
|
+
continue;
|
|
448
|
+
orderedIds.push(id);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const anchorId = currentSelection[currentSelection.length - 1]!;
|
|
453
|
+
const aIdx = orderedIds.indexOf(anchorId);
|
|
454
|
+
const bIdx = orderedIds.indexOf(componentId);
|
|
455
|
+
if (aIdx !== -1 && bIdx !== -1) {
|
|
456
|
+
const start = Math.min(aIdx, bIdx);
|
|
457
|
+
const end = Math.max(aIdx, bIdx);
|
|
458
|
+
const range = orderedIds.slice(start, end + 1);
|
|
459
|
+
if (event.ctrlKey) {
|
|
460
|
+
const union = new Set<string>([...currentSelection, ...range]);
|
|
461
|
+
editContextRef.current?.select(Array.from(union));
|
|
462
|
+
} else {
|
|
463
|
+
editContextRef.current?.select(range);
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
editContextRef.current?.select([componentId]);
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
editContextRef.current?.select([componentId]);
|
|
470
|
+
}
|
|
471
|
+
} else if (event.ctrlKey) {
|
|
416
472
|
if (currentSelection.indexOf(componentId) === -1)
|
|
417
473
|
editContextRef.current?.select([...currentSelection, componentId]);
|
|
418
474
|
} else {
|
|
@@ -488,7 +544,19 @@ export function PageViewerFrame({
|
|
|
488
544
|
event.preventDefault();
|
|
489
545
|
event.stopPropagation();
|
|
490
546
|
|
|
491
|
-
|
|
547
|
+
let componentId = findNearestEditableComponentId(target as HTMLElement);
|
|
548
|
+
|
|
549
|
+
if (
|
|
550
|
+
componentId &&
|
|
551
|
+
editContextRef.current &&
|
|
552
|
+
editContextRef.current.page &&
|
|
553
|
+
editContextRef.current.showLayoutComponents === false
|
|
554
|
+
) {
|
|
555
|
+
const comp = getComponentById(componentId, editContextRef.current.page);
|
|
556
|
+
if (comp?.layoutId) {
|
|
557
|
+
componentId = undefined;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
492
560
|
|
|
493
561
|
if (componentId) {
|
|
494
562
|
// Only change selection if right-clicking on a component that's not in the current selection
|
|
@@ -511,6 +579,14 @@ export function PageViewerFrame({
|
|
|
511
579
|
.map((id) => getComponentById(id, pageViewContextRef.current!.page!))
|
|
512
580
|
.filter((x) => x) as Component[];
|
|
513
581
|
|
|
582
|
+
// If layout components are hidden, do not show context menu for them
|
|
583
|
+
if (
|
|
584
|
+
editContextRef.current?.showLayoutComponents === false &&
|
|
585
|
+
selectedComponents.some((c) => c.layoutId)
|
|
586
|
+
) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
514
590
|
const iframeRect = iframe.getBoundingClientRect();
|
|
515
591
|
const adjustedEvent = new MouseEvent("contextmenu", {
|
|
516
592
|
bubbles: true,
|
|
@@ -42,13 +42,13 @@ export function CommentEditor({
|
|
|
42
42
|
|
|
43
43
|
React.useEffect(() => {
|
|
44
44
|
if (autoFocus && textareaRef.current) {
|
|
45
|
-
// Focus after a
|
|
45
|
+
// Focus after a longer delay to ensure the component is fully mounted, especially for dynamically created popovers
|
|
46
46
|
const timer = setTimeout(() => {
|
|
47
47
|
textareaRef.current?.focus();
|
|
48
48
|
// Move cursor to end
|
|
49
49
|
const len = textareaRef.current?.value.length || 0;
|
|
50
50
|
textareaRef.current?.setSelectionRange(len, len);
|
|
51
|
-
},
|
|
51
|
+
}, 200);
|
|
52
52
|
return () => clearTimeout(timer);
|
|
53
53
|
}
|
|
54
54
|
}, [autoFocus]);
|
|
@@ -169,6 +169,7 @@ export function Reviews() {
|
|
|
169
169
|
<SimpleIconButton
|
|
170
170
|
label="Add Reviewer"
|
|
171
171
|
icon="pi pi-plus"
|
|
172
|
+
data-testid="add-reviewer-button"
|
|
172
173
|
onClick={(x) => {
|
|
173
174
|
overlayPanelRef.current?.toggle(x);
|
|
174
175
|
setTimeout(() => {
|
|
@@ -230,6 +231,7 @@ export function Reviews() {
|
|
|
230
231
|
label="Add Reviewer"
|
|
231
232
|
disabled={waiting}
|
|
232
233
|
icon="pi pi-plus"
|
|
234
|
+
data-testid="add-reviewer-submit-button"
|
|
233
235
|
onClick={async () => {
|
|
234
236
|
if (!editContext?.contentEditorItem) return;
|
|
235
237
|
setShowErrors(true);
|
|
@@ -13,7 +13,7 @@ import { PerfectTree, TreeNode } from "../ui/PerfectTree";
|
|
|
13
13
|
|
|
14
14
|
import { isValidPlaceholder } from "../componentTreeHelper";
|
|
15
15
|
import { getAbsoluteIconUrl } from "../utils";
|
|
16
|
-
import { Plus,
|
|
16
|
+
import { Plus, LayoutTemplate, Pentagon } from "lucide-react";
|
|
17
17
|
import { SimpleIconButton } from "../ui/SimpleIconButton";
|
|
18
18
|
|
|
19
19
|
type CustomTreeNode = TreeNode<Component | Placeholder> & {
|
|
@@ -171,14 +171,15 @@ export function ComponentTree({}) {
|
|
|
171
171
|
tags: [],
|
|
172
172
|
};
|
|
173
173
|
|
|
174
|
-
if (editContext?.
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
174
|
+
if (!editContext?.showLayoutComponents) {
|
|
175
|
+
// Map components and filter out layout components when hidden
|
|
176
|
+
const mappedComponents = p.components.map((c) =>
|
|
177
|
+
mapComponentNode(c, node),
|
|
178
|
+
);
|
|
179
|
+
node.children = mappedComponents.filter((componentNode) => {
|
|
180
|
+
const component = componentNode.data as Component;
|
|
181
|
+
return !component.layoutId;
|
|
182
|
+
});
|
|
182
183
|
} else {
|
|
183
184
|
// Always map components, but filter based on editability and descendants
|
|
184
185
|
const mappedComponents = p.components.map((c) =>
|
|
@@ -201,36 +202,20 @@ export function ComponentTree({}) {
|
|
|
201
202
|
return [];
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
if (editContext?.
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
|
|
205
|
+
if (!editContext?.showLayoutComponents) {
|
|
206
|
+
// Show only non-layout components
|
|
207
|
+
const nodes: CustomTreeNode[] = [];
|
|
208
208
|
for (const placeholder of c.placeholders) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const placeholderNode = mapPlaceholderNode(placeholder, parent);
|
|
214
|
-
flattenedNodes.push(placeholderNode);
|
|
215
|
-
} else {
|
|
216
|
-
// Single placeholder - map components directly
|
|
217
|
-
for (const component of placeholder.components) {
|
|
218
|
-
const editableNodes = getEditableDescendants(component, parent);
|
|
219
|
-
flattenedNodes.push(...editableNodes);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
// Placeholder is not editable - collect editable descendants from its components
|
|
224
|
-
for (const component of placeholder.components) {
|
|
225
|
-
const editableNodes = getEditableDescendants(component, parent);
|
|
226
|
-
flattenedNodes.push(...editableNodes);
|
|
209
|
+
for (const component of placeholder.components) {
|
|
210
|
+
if (!component.layoutId) {
|
|
211
|
+
const componentNode = mapComponentNode(component, parent);
|
|
212
|
+
nodes.push(componentNode);
|
|
227
213
|
}
|
|
228
214
|
}
|
|
229
215
|
}
|
|
230
|
-
|
|
231
|
-
return flattenedNodes;
|
|
216
|
+
return nodes;
|
|
232
217
|
} else {
|
|
233
|
-
// Original logic for when
|
|
218
|
+
// Original logic for when showing layout components
|
|
234
219
|
// Filter placeholders based on editable descendants
|
|
235
220
|
let placeholdersToShow = c.placeholders.filter((p) => {
|
|
236
221
|
// Show placeholder if it's editable or has editable descendants
|
|
@@ -424,22 +409,72 @@ export function ComponentTree({}) {
|
|
|
424
409
|
|
|
425
410
|
setRootNodes(finalNodes);
|
|
426
411
|
setNodeDictionary(dict);
|
|
427
|
-
}, [page, editContext?.
|
|
412
|
+
}, [page, editContext?.showLayoutComponents]);
|
|
428
413
|
|
|
429
414
|
const handleTreeSelection = useCallback(
|
|
430
415
|
(key: string, event: React.MouseEvent) => {
|
|
431
|
-
let newKeys = [
|
|
432
|
-
|
|
416
|
+
let newKeys: string[] = [];
|
|
417
|
+
|
|
418
|
+
// Helper: gather visible component keys using current tree data and expanded state
|
|
419
|
+
const getVisibleComponentKeys = (): string[] => {
|
|
420
|
+
const keys: string[] = [];
|
|
421
|
+
const visit = (nodes: CustomTreeNode[]) => {
|
|
422
|
+
for (const n of nodes) {
|
|
423
|
+
if (n.type === "component") keys.push(n.key);
|
|
424
|
+
if (n.children && expandedKeys.includes(n.key)) {
|
|
425
|
+
visit(n.children as CustomTreeNode[]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
visit(rootNodes as CustomTreeNode[]);
|
|
430
|
+
return keys;
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
if (event.shiftKey) {
|
|
434
|
+
const anchorKey =
|
|
435
|
+
selectedKeys && selectedKeys.length > 0
|
|
436
|
+
? selectedKeys[selectedKeys.length - 1]!
|
|
437
|
+
: undefined;
|
|
438
|
+
const orderedKeys = getVisibleComponentKeys();
|
|
439
|
+
const aIdx = anchorKey ? orderedKeys.indexOf(anchorKey) : -1;
|
|
440
|
+
const bIdx = orderedKeys.indexOf(key);
|
|
441
|
+
|
|
442
|
+
if (aIdx !== -1 && bIdx !== -1) {
|
|
443
|
+
const start = Math.min(aIdx, bIdx);
|
|
444
|
+
const end = Math.max(aIdx, bIdx);
|
|
445
|
+
const rangeKeys = orderedKeys.slice(start, end + 1);
|
|
446
|
+
if (event.ctrlKey) {
|
|
447
|
+
const union = new Set<string>([
|
|
448
|
+
...selectedKeys.filter(
|
|
449
|
+
(k) => nodeDictionary[k]?.type === "component",
|
|
450
|
+
),
|
|
451
|
+
...rangeKeys,
|
|
452
|
+
]);
|
|
453
|
+
newKeys = Array.from(union);
|
|
454
|
+
} else {
|
|
455
|
+
newKeys = rangeKeys;
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
// Fallback to single selection when anchor or target not found
|
|
459
|
+
newKeys = [key];
|
|
460
|
+
}
|
|
461
|
+
} else if (event.ctrlKey) {
|
|
433
462
|
if (selectedKeys.includes(key)) {
|
|
434
463
|
newKeys = selectedKeys.filter((x) => x !== key);
|
|
435
464
|
} else {
|
|
436
465
|
newKeys = [...selectedKeys, key];
|
|
437
466
|
}
|
|
467
|
+
} else {
|
|
468
|
+
newKeys = [key];
|
|
438
469
|
}
|
|
439
470
|
|
|
440
|
-
const selectedComponentIds =
|
|
441
|
-
|
|
442
|
-
|
|
471
|
+
const selectedComponentIds = Array.from(
|
|
472
|
+
new Set(
|
|
473
|
+
newKeys
|
|
474
|
+
.filter((k) => nodeDictionary[k]?.type === "component")
|
|
475
|
+
.map((k) => nodeDictionary[k]!.componentId!),
|
|
476
|
+
),
|
|
477
|
+
);
|
|
443
478
|
|
|
444
479
|
editContextRef.current?.select(selectedComponentIds);
|
|
445
480
|
|
|
@@ -597,6 +632,102 @@ export function ComponentTree({}) {
|
|
|
597
632
|
return null;
|
|
598
633
|
};
|
|
599
634
|
|
|
635
|
+
// Resolve placeholder and correct index for drop zones even when placeholders are hidden/flattened
|
|
636
|
+
const getSiblingsForParent = (
|
|
637
|
+
parent: CustomTreeNode | null,
|
|
638
|
+
): CustomTreeNode[] => {
|
|
639
|
+
if (!parent) return (rootNodes as CustomTreeNode[]) || [];
|
|
640
|
+
const parentNode = nodeDictionary[parent.key];
|
|
641
|
+
return ((parentNode?.children as CustomTreeNode[]) ||
|
|
642
|
+
[]) as CustomTreeNode[];
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const findNearestComponentNeighbor = (
|
|
646
|
+
siblings: CustomTreeNode[],
|
|
647
|
+
index: number,
|
|
648
|
+
): CustomTreeNode | null => {
|
|
649
|
+
// Prefer right neighbor
|
|
650
|
+
for (let i = index; i < siblings.length; i++) {
|
|
651
|
+
if (siblings[i]?.type === "component") return siblings[i]!;
|
|
652
|
+
}
|
|
653
|
+
// Fallback to left neighbor
|
|
654
|
+
for (let j = index - 1; j >= 0; j--) {
|
|
655
|
+
if (siblings[j]?.type === "component") return siblings[j]!;
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const resolveDropContext = (
|
|
661
|
+
dragOverParent: CustomTreeNode | null,
|
|
662
|
+
index: number,
|
|
663
|
+
): { placeholder: Placeholder | null; indexInPlaceholder: number } => {
|
|
664
|
+
// Direct placeholder parent
|
|
665
|
+
if (dragOverParent && dragOverParent.type === "placeholder") {
|
|
666
|
+
const placeholder = dragOverParent.data as Placeholder;
|
|
667
|
+
return {
|
|
668
|
+
placeholder: placeholder.editable ? placeholder : null,
|
|
669
|
+
indexInPlaceholder: Math.max(0, index),
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Single editable placeholder on a component
|
|
674
|
+
if (dragOverParent && dragOverParent.type === "component") {
|
|
675
|
+
const component = dragOverParent.data as Component;
|
|
676
|
+
const editablePlaceholders = (component.placeholders || []).filter(
|
|
677
|
+
(p) => p.editable,
|
|
678
|
+
);
|
|
679
|
+
const siblings = getSiblingsForParent(dragOverParent);
|
|
680
|
+
if (editablePlaceholders.length === 1) {
|
|
681
|
+
const ph = editablePlaceholders[0]!;
|
|
682
|
+
// Count how many siblings in this placeholder appear before the zone
|
|
683
|
+
const indexInPlaceholder = siblings
|
|
684
|
+
.slice(0, Math.max(0, index))
|
|
685
|
+
.filter(
|
|
686
|
+
(n) =>
|
|
687
|
+
n.type === "component" &&
|
|
688
|
+
(n.data as Component)?.parentPlaceholder?.key === ph.key,
|
|
689
|
+
).length;
|
|
690
|
+
return { placeholder: ph, indexInPlaceholder };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Multiple placeholders: infer from nearest neighbor component
|
|
694
|
+
const neighbor = findNearestComponentNeighbor(
|
|
695
|
+
siblings,
|
|
696
|
+
Math.max(0, index),
|
|
697
|
+
);
|
|
698
|
+
const ph = (neighbor?.data as Component | undefined)?.parentPlaceholder;
|
|
699
|
+
if (ph && ph.editable) {
|
|
700
|
+
const indexInPlaceholder = siblings
|
|
701
|
+
.slice(0, Math.max(0, index))
|
|
702
|
+
.filter(
|
|
703
|
+
(n) =>
|
|
704
|
+
n.type === "component" &&
|
|
705
|
+
(n.data as Component)?.parentPlaceholder?.key === ph.key,
|
|
706
|
+
).length;
|
|
707
|
+
return { placeholder: ph, indexInPlaceholder };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return { placeholder: null, indexInPlaceholder: 0 };
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Root-level (no parent): infer from neighbors among root nodes
|
|
714
|
+
const siblings = getSiblingsForParent(null);
|
|
715
|
+
const neighbor = findNearestComponentNeighbor(siblings, Math.max(0, index));
|
|
716
|
+
const ph = (neighbor?.data as Component | undefined)?.parentPlaceholder;
|
|
717
|
+
if (ph && ph.editable) {
|
|
718
|
+
const indexInPlaceholder = siblings
|
|
719
|
+
.slice(0, Math.max(0, index))
|
|
720
|
+
.filter(
|
|
721
|
+
(n) =>
|
|
722
|
+
n.type === "component" &&
|
|
723
|
+
(n.data as Component)?.parentPlaceholder?.key === ph.key,
|
|
724
|
+
).length;
|
|
725
|
+
return { placeholder: ph, indexInPlaceholder };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return { placeholder: null, indexInPlaceholder: 0 };
|
|
729
|
+
};
|
|
730
|
+
|
|
600
731
|
// Handle drag over node
|
|
601
732
|
const handleDragOverZone = (
|
|
602
733
|
dragOverNode: CustomTreeNode | null,
|
|
@@ -604,9 +735,9 @@ export function ComponentTree({}) {
|
|
|
604
735
|
event: React.DragEvent,
|
|
605
736
|
): boolean => {
|
|
606
737
|
if (!editContext?.dragObject) return false;
|
|
607
|
-
const placeholder =
|
|
738
|
+
const { placeholder } = resolveDropContext(dragOverNode, index);
|
|
608
739
|
if (!placeholder) return false;
|
|
609
|
-
|
|
740
|
+
const isValid = isValidPlaceholder(placeholder, editContext.dragObject);
|
|
610
741
|
event.dataTransfer.dropEffect = isValid ? "move" : "none";
|
|
611
742
|
return isValid;
|
|
612
743
|
};
|
|
@@ -617,10 +748,20 @@ export function ComponentTree({}) {
|
|
|
617
748
|
index: number,
|
|
618
749
|
event: React.DragEvent,
|
|
619
750
|
): void => {
|
|
620
|
-
|
|
621
|
-
if (
|
|
751
|
+
// When dropping on a node (index < 0), use legacy behavior
|
|
752
|
+
if (index < 0) {
|
|
753
|
+
const placeholder = getPlaceholder(droppedOnNode);
|
|
754
|
+
if (!placeholder) return;
|
|
755
|
+
editContext!.droppedInPlaceholder(placeholder.key, index);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
622
758
|
|
|
623
|
-
|
|
759
|
+
const { placeholder, indexInPlaceholder } = resolveDropContext(
|
|
760
|
+
droppedOnNode,
|
|
761
|
+
index,
|
|
762
|
+
);
|
|
763
|
+
if (!placeholder) return;
|
|
764
|
+
editContext!.droppedInPlaceholder(placeholder.key, indexInPlaceholder);
|
|
624
765
|
};
|
|
625
766
|
|
|
626
767
|
if (!page)
|
|
@@ -630,35 +771,45 @@ export function ComponentTree({}) {
|
|
|
630
771
|
</div>
|
|
631
772
|
);
|
|
632
773
|
|
|
633
|
-
// Use the PerfectTree component
|
|
634
774
|
return (
|
|
635
775
|
<div className="flex h-full flex-col">
|
|
636
776
|
{/* Toolbar */}
|
|
637
777
|
<div className="flex items-center justify-between border-b border-gray-200 px-2 py-1">
|
|
638
|
-
<
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
778
|
+
<div className="flex items-center gap-1">
|
|
779
|
+
<SimpleIconButton
|
|
780
|
+
icon={<Plus strokeWidth={1} className="h-4 w-4" />}
|
|
781
|
+
label="Add component"
|
|
782
|
+
size="small"
|
|
783
|
+
data-testid="component-tree-add-component"
|
|
784
|
+
onClick={(event) => {
|
|
785
|
+
if (
|
|
786
|
+
!editContext ||
|
|
787
|
+
editContext.readonly ||
|
|
788
|
+
editContext.mode !== "edit"
|
|
789
|
+
)
|
|
790
|
+
return;
|
|
791
|
+
|
|
792
|
+
editContext.setInsertMode(true);
|
|
793
|
+
}}
|
|
794
|
+
/>
|
|
795
|
+
</div>
|
|
796
|
+
<div className="flex items-center gap-1">
|
|
797
|
+
<SimpleIconButton
|
|
798
|
+
icon={<LayoutTemplate strokeWidth={1} className="h-4 w-4" />}
|
|
799
|
+
label={
|
|
800
|
+
editContext?.showLayoutComponents ? "Hide layout" : "Show layout"
|
|
801
|
+
}
|
|
802
|
+
size="small"
|
|
803
|
+
selected={editContext?.showLayoutComponents}
|
|
804
|
+
onClick={() =>
|
|
805
|
+
editContext?.setShowLayoutComponents(
|
|
806
|
+
!editContext.showLayoutComponents,
|
|
807
|
+
)
|
|
808
|
+
}
|
|
809
|
+
/>
|
|
810
|
+
</div>
|
|
659
811
|
</div>
|
|
660
812
|
|
|
661
|
-
{/* Tree content */}
|
|
662
813
|
<div className="flex-1 p-2" ref={treeRef} data-testid="component-tree">
|
|
663
814
|
<PerfectTree
|
|
664
815
|
nodes={rootNodes}
|
|
@@ -673,7 +824,10 @@ export function ComponentTree({}) {
|
|
|
673
824
|
handleDragOverZone(parent as CustomTreeNode | null, index, event)
|
|
674
825
|
}
|
|
675
826
|
isValidDropZone={(parent, index) => {
|
|
676
|
-
const placeholder =
|
|
827
|
+
const { placeholder } = resolveDropContext(
|
|
828
|
+
parent as CustomTreeNode | null,
|
|
829
|
+
index,
|
|
830
|
+
);
|
|
677
831
|
if (!placeholder) return false;
|
|
678
832
|
if (!editContext?.dragObject) return false;
|
|
679
833
|
const result = isValidPlaceholder(
|
|
@@ -106,7 +106,7 @@ export function Debug({}: {}) {
|
|
|
106
106
|
tabs={tabs}
|
|
107
107
|
activeTab={activeTab}
|
|
108
108
|
setActiveTab={setActiveTab}
|
|
109
|
-
className="
|
|
109
|
+
className="border-gray-3 flex border-b px-2 pt-2 text-xs"
|
|
110
110
|
/>
|
|
111
111
|
|
|
112
112
|
<div className="absolute top-2 right-1 h-4 w-4">
|
package/src/revision.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = "1.0.
|
|
2
|
-
export const buildDate = "2025-08-25
|
|
1
|
+
export const version = "1.0.4065";
|
|
2
|
+
export const buildDate = "2025-08-25 11:33:32";
|
package/src/types.ts
CHANGED