@alpaca-editor/core 1.0.4172 → 1.0.4174
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/AgentsView.d.ts +5 -0
- package/dist/agents-view/AgentsView.js +213 -0
- package/dist/agents-view/AgentsView.js.map +1 -0
- package/dist/components/ui/context-menu.js +4 -4
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/config/config.js +56 -1
- package/dist/config/config.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +2 -1
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/ContentTree.d.ts +2 -1
- package/dist/editor/ContentTree.js +18 -3
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/ContextMenu.js +1 -1
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/FieldList.js +7 -3
- package/dist/editor/FieldList.js.map +1 -1
- package/dist/editor/FieldListField.d.ts +3 -2
- package/dist/editor/FieldListField.js +4 -4
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.d.ts +2 -1
- package/dist/editor/FieldListFieldWithFallbacks.js +5 -2
- package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
- package/dist/editor/MainLayout.js +1 -1
- package/dist/editor/QuickItemSwitcher.d.ts +9 -0
- package/dist/editor/QuickItemSwitcher.js +74 -0
- package/dist/editor/QuickItemSwitcher.js.map +1 -0
- package/dist/editor/ai/AgentCostDisplay.js +7 -11
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentProfilesOverview.d.ts +9 -0
- package/dist/editor/ai/AgentProfilesOverview.js +16 -0
- package/dist/editor/ai/AgentProfilesOverview.js.map +1 -0
- package/dist/editor/ai/AgentTerminal.js +285 -748
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/Agents.js +112 -54
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +2 -1
- package/dist/editor/ai/AiResponseMessage.js +4 -2
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContextInfoBar.js +17 -17
- package/dist/editor/ai/useAgentStatus.js +7 -0
- package/dist/editor/ai/useAgentStatus.js.map +1 -1
- package/dist/editor/client/EditorShell.js +230 -4
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +0 -12
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.js +1 -1
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/commands/itemCommands.js +1 -0
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/control-center/parhelia-setup/Overview.d.ts +1 -0
- package/dist/editor/control-center/parhelia-setup/Overview.js +91 -0
- package/dist/editor/control-center/parhelia-setup/Overview.js.map +1 -0
- package/dist/editor/field-types/AttachmentEditor.js +3 -6
- package/dist/editor/field-types/AttachmentEditor.js.map +1 -1
- package/dist/editor/field-types/DropLinkEditor.js +2 -2
- package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
- package/dist/editor/field-types/DropListEditor.js +2 -1
- package/dist/editor/field-types/DropListEditor.js.map +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js +3 -3
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.d.ts +2 -1
- package/dist/editor/field-types/MultiLineText.js +2 -2
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/RawEditor.d.ts +2 -1
- package/dist/editor/field-types/RawEditor.js +2 -2
- package/dist/editor/field-types/RawEditor.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.d.ts +2 -1
- package/dist/editor/field-types/SingleLineText.js +2 -2
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/field-types/TreeListEditor.js +9 -7
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +68 -7
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +1 -1
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/ActiveUsers.js +2 -2
- package/dist/editor/menubar/FavoritesControls.js +2 -2
- package/dist/editor/menubar/FavoritesControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +10 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +14 -0
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +21 -3
- package/dist/editor/services/agentService.js +24 -8
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +6 -1
- package/dist/editor/services/aiService.js +36 -5
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +1 -1
- package/dist/editor/services/contentService.js +4 -2
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/editService.d.ts +5 -1
- package/dist/editor/services/editService.js +1 -1
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/services/setupService.d.ts +21 -0
- package/dist/editor/services/setupService.js +10 -0
- package/dist/editor/services/setupService.js.map +1 -0
- package/dist/editor/sidebar/ComponentTree.js +15 -1
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/SidebarView.js +1 -1
- package/dist/editor/sidebar/SidebarView.js.map +1 -1
- package/dist/editor/ui/ItemSearch.d.ts +1 -0
- package/dist/editor/ui/ItemSearch.js +2 -2
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/editor/ui/PerfectTree.d.ts +5 -1
- package/dist/editor/ui/PerfectTree.js +308 -29
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/editor/utils/keyboardNavigation.d.ts +2 -0
- package/dist/editor/utils/keyboardNavigation.js +80 -2
- package/dist/editor/utils/keyboardNavigation.js.map +1 -1
- package/dist/editor/views/SingleEditView.js +6 -4
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/SplashScreen.js +78 -4
- package/dist/splash-screen/SplashScreen.js.map +1 -1
- package/dist/styles.css +157 -23
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
- package/src/agents-view/AgentsView.tsx +431 -0
- package/src/components/ui/context-menu.tsx +4 -4
- package/src/config/config.tsx +61 -0
- package/src/editor/ConfirmationDialog.tsx +42 -10
- package/src/editor/ContentTree.tsx +20 -1
- package/src/editor/ContextMenu.tsx +4 -1
- package/src/editor/FieldList.tsx +10 -4
- package/src/editor/FieldListField.tsx +7 -0
- package/src/editor/FieldListFieldWithFallbacks.tsx +10 -0
- package/src/editor/MainLayout.tsx +1 -1
- package/src/editor/QuickItemSwitcher.tsx +217 -0
- package/src/editor/ai/AgentCostDisplay.tsx +59 -60
- package/src/editor/ai/AgentProfilesOverview.tsx +81 -0
- package/src/editor/ai/AgentTerminal.tsx +321 -775
- package/src/editor/ai/Agents.tsx +157 -91
- package/src/editor/ai/AiResponseMessage.tsx +12 -1
- package/src/editor/ai/ContextInfoBar.tsx +17 -17
- package/src/editor/ai/useAgentStatus.ts +6 -0
- package/src/editor/client/EditorShell.tsx +288 -3
- package/src/editor/client/hooks/useSocketMessageHandler.ts +0 -15
- package/src/editor/client/ui/EditorChrome.tsx +1 -1
- package/src/editor/commands/itemCommands.tsx +1 -0
- package/src/editor/control-center/parhelia-setup/Overview.tsx +184 -0
- package/src/editor/field-types/AttachmentEditor.tsx +13 -50
- package/src/editor/field-types/DropLinkEditor.tsx +2 -2
- package/src/editor/field-types/DropListEditor.tsx +2 -1
- package/src/editor/field-types/InternalLinkFieldEditor.tsx +3 -3
- package/src/editor/field-types/MultiLineText.tsx +3 -0
- package/src/editor/field-types/RawEditor.tsx +3 -0
- package/src/editor/field-types/SingleLineText.tsx +3 -0
- package/src/editor/field-types/TreeListEditor.tsx +32 -24
- package/src/editor/media-selector/MediaFolderBrowser.tsx +93 -9
- package/src/editor/media-selector/TreeSelector.tsx +1 -0
- package/src/editor/menubar/ActiveUsers.tsx +2 -2
- package/src/editor/menubar/FavoritesControls.tsx +5 -5
- package/src/editor/page-editor-chrome/FrameMenu.tsx +10 -1
- package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +17 -0
- package/src/editor/services/agentService.ts +46 -11
- package/src/editor/services/aiService.ts +48 -7
- package/src/editor/services/contentService.ts +4 -1
- package/src/editor/services/editService.ts +8 -3
- package/src/editor/services/setupService.ts +35 -0
- package/src/editor/sidebar/ComponentTree.tsx +16 -1
- package/src/editor/sidebar/SidebarView.tsx +1 -1
- package/src/editor/ui/ItemSearch.tsx +3 -1
- package/src/editor/ui/PerfectTree.tsx +393 -42
- package/src/editor/utils/keyboardNavigation.ts +97 -1
- package/src/editor/views/SingleEditView.tsx +27 -13
- package/src/revision.ts +2 -2
- package/src/splash-screen/SplashScreen.tsx +134 -2
- package/src/types.ts +15 -0
|
@@ -88,6 +88,10 @@ export interface TreeProps<T = any> {
|
|
|
88
88
|
disableAutoSelectOnExpand?: boolean;
|
|
89
89
|
/** Noun used in multi-select drag preview (e.g., "items", "components"). Default: "items" */
|
|
90
90
|
multiDragNoun?: string;
|
|
91
|
+
/** Whether to enable keyboard navigation (default: true) */
|
|
92
|
+
enableKeyboardNavigation?: boolean;
|
|
93
|
+
/** Called when keyboard navigation focus changes */
|
|
94
|
+
onKeyboardNavigationChange?: (key: string) => void;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/**
|
|
@@ -346,6 +350,38 @@ const filterTreeNodes = (
|
|
|
346
350
|
.filter((node): node is TreeNode<any> => node !== null);
|
|
347
351
|
};
|
|
348
352
|
|
|
353
|
+
// Helper types for keyboard navigation
|
|
354
|
+
interface FlattenedNode<T = any> {
|
|
355
|
+
node: TreeNode<T>;
|
|
356
|
+
parent: TreeNode<T> | null;
|
|
357
|
+
depth: number;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Helper function to flatten tree nodes into a depth-first list of visible nodes
|
|
361
|
+
const flattenVisibleNodes = <T,>(
|
|
362
|
+
nodes: TreeNode<T>[],
|
|
363
|
+
expandedKeys: string[],
|
|
364
|
+
parent: TreeNode<T> | null = null,
|
|
365
|
+
depth: number = 0,
|
|
366
|
+
): FlattenedNode<T>[] => {
|
|
367
|
+
const result: FlattenedNode<T>[] = [];
|
|
368
|
+
|
|
369
|
+
for (const node of nodes) {
|
|
370
|
+
// Add current node
|
|
371
|
+
result.push({ node, parent, depth });
|
|
372
|
+
|
|
373
|
+
// If expanded and has children, recursively add children
|
|
374
|
+
const isExpanded = expandedKeys.includes(node.key);
|
|
375
|
+
if (isExpanded && node.children && Array.isArray(node.children)) {
|
|
376
|
+
result.push(
|
|
377
|
+
...flattenVisibleNodes(node.children, expandedKeys, node, depth + 1),
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return result;
|
|
383
|
+
};
|
|
384
|
+
|
|
349
385
|
// NodeContent component extracted and memoized
|
|
350
386
|
const NodeContent = memo(
|
|
351
387
|
({
|
|
@@ -580,9 +616,14 @@ export const PerfectTree = <T,>({
|
|
|
580
616
|
searchClearDelay = 1500,
|
|
581
617
|
disableAutoSelectOnExpand = false,
|
|
582
618
|
multiDragNoun = "items",
|
|
619
|
+
enableKeyboardNavigation = true,
|
|
620
|
+
onKeyboardNavigationChange,
|
|
583
621
|
}: TreeProps<T>) => {
|
|
584
622
|
const [searchTerm, setSearchTerm] = useState("");
|
|
585
623
|
const [isFocused, setIsFocused] = useState(false);
|
|
624
|
+
const [keyboardNavPosition, setKeyboardNavPosition] = useState<string | null>(
|
|
625
|
+
null,
|
|
626
|
+
);
|
|
586
627
|
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
587
628
|
|
|
588
629
|
// When toggling a node, notify parent and trigger external lazy load if needed.
|
|
@@ -686,47 +727,6 @@ export const PerfectTree = <T,>({
|
|
|
686
727
|
};
|
|
687
728
|
}, [onDragEnd]);
|
|
688
729
|
|
|
689
|
-
const handleDragEnd = useCallback(
|
|
690
|
-
(event: React.DragEvent<HTMLDivElement> | null) => {
|
|
691
|
-
isDraggingRef.current = false;
|
|
692
|
-
// Clear timeout when drag ends normally
|
|
693
|
-
if (dragTimeoutRef.current !== undefined) {
|
|
694
|
-
clearTimeout(dragTimeoutRef.current!);
|
|
695
|
-
dragTimeoutRef.current = undefined;
|
|
696
|
-
}
|
|
697
|
-
if (onDragEnd) {
|
|
698
|
-
onDragEnd(event);
|
|
699
|
-
}
|
|
700
|
-
},
|
|
701
|
-
[onDragEnd],
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
// Wrapper for onStartDrag that has access to the refs
|
|
705
|
-
const handleStartDragWithTimeout = useCallback(
|
|
706
|
-
(data: {
|
|
707
|
-
node: TreeNode<T>;
|
|
708
|
-
event: React.DragEvent<any>;
|
|
709
|
-
isMultiSelect: boolean;
|
|
710
|
-
}) => {
|
|
711
|
-
// Set the dragging ref to true when drag starts
|
|
712
|
-
isDraggingRef.current = true;
|
|
713
|
-
|
|
714
|
-
// Set up a timeout to auto-reset drag state if it gets stuck
|
|
715
|
-
dragTimeoutRef.current = setTimeout(() => {
|
|
716
|
-
if (isDraggingRef.current && onDragEnd) {
|
|
717
|
-
onDragEnd(null);
|
|
718
|
-
isDraggingRef.current = false;
|
|
719
|
-
}
|
|
720
|
-
}, 30000); // 30 second timeout
|
|
721
|
-
|
|
722
|
-
// Call the original onStartDrag
|
|
723
|
-
if (onStartDrag) {
|
|
724
|
-
onStartDrag(data);
|
|
725
|
-
}
|
|
726
|
-
},
|
|
727
|
-
[onStartDrag, onDragEnd],
|
|
728
|
-
);
|
|
729
|
-
|
|
730
730
|
// Scroll to selected node when scrollToSelected is enabled and selectedKeys change
|
|
731
731
|
const treeRef = useRef<HTMLDivElement>(null);
|
|
732
732
|
|
|
@@ -864,6 +864,46 @@ export const PerfectTree = <T,>({
|
|
|
864
864
|
return filterTreeNodes(nodes, searchTerm, expandedKeys);
|
|
865
865
|
}, [nodes, searchTerm, expandedKeys]);
|
|
866
866
|
|
|
867
|
+
// Wrapper for onStartDrag that has access to the refs
|
|
868
|
+
const handleStartDragWithTimeout = useCallback(
|
|
869
|
+
(data: {
|
|
870
|
+
node: TreeNode<T>;
|
|
871
|
+
event: React.DragEvent<any>;
|
|
872
|
+
isMultiSelect: boolean;
|
|
873
|
+
}) => {
|
|
874
|
+
// Mark dragging as active
|
|
875
|
+
isDraggingRef.current = true;
|
|
876
|
+
// Failsafe timeout to recover if the browser fails to emit dragend
|
|
877
|
+
dragTimeoutRef.current = setTimeout(() => {
|
|
878
|
+
if (isDraggingRef.current && onDragEnd) {
|
|
879
|
+
onDragEnd(null);
|
|
880
|
+
isDraggingRef.current = false;
|
|
881
|
+
}
|
|
882
|
+
}, 30000); // 30s safety timeout
|
|
883
|
+
|
|
884
|
+
// Delegate to consumer
|
|
885
|
+
if (onStartDrag) {
|
|
886
|
+
onStartDrag(data);
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
[onStartDrag, onDragEnd],
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
const handleDragEnd = useCallback(
|
|
893
|
+
(event: React.DragEvent<HTMLDivElement> | null) => {
|
|
894
|
+
isDraggingRef.current = false;
|
|
895
|
+
// Clear timeout when drag ends normally
|
|
896
|
+
if (dragTimeoutRef.current !== undefined) {
|
|
897
|
+
clearTimeout(dragTimeoutRef.current!);
|
|
898
|
+
dragTimeoutRef.current = undefined;
|
|
899
|
+
}
|
|
900
|
+
if (onDragEnd) {
|
|
901
|
+
onDragEnd(event);
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
[onDragEnd],
|
|
905
|
+
);
|
|
906
|
+
|
|
867
907
|
// Recursive function to render tree nodes along with drop zones.
|
|
868
908
|
const renderTreeList = useCallback(
|
|
869
909
|
(
|
|
@@ -964,6 +1004,300 @@ export const PerfectTree = <T,>({
|
|
|
964
1004
|
[filteredNodes, renderTreeList],
|
|
965
1005
|
);
|
|
966
1006
|
|
|
1007
|
+
// Initialize keyboard navigation position when tree gains focus
|
|
1008
|
+
useEffect(() => {
|
|
1009
|
+
if (isFocused && !keyboardNavPosition) {
|
|
1010
|
+
// Set initial position to first selected node, or first node
|
|
1011
|
+
if (selectedKeys.length > 0 && selectedKeys[0]) {
|
|
1012
|
+
setKeyboardNavPosition(selectedKeys[0]);
|
|
1013
|
+
} else if (filteredNodes.length > 0 && filteredNodes[0]) {
|
|
1014
|
+
setKeyboardNavPosition(filteredNodes[0].key);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}, [isFocused, keyboardNavPosition, selectedKeys, filteredNodes]);
|
|
1018
|
+
|
|
1019
|
+
// Update keyboard navigation position when selection changes externally (e.g., via click)
|
|
1020
|
+
useEffect(() => {
|
|
1021
|
+
if (isFocused && selectedKeys.length > 0 && selectedKeys[0]) {
|
|
1022
|
+
// Only update if the current nav position is not in the selected keys
|
|
1023
|
+
// This preserves nav position during shift-select
|
|
1024
|
+
if (!keyboardNavPosition || !selectedKeys.includes(keyboardNavPosition)) {
|
|
1025
|
+
setKeyboardNavPosition(selectedKeys[0]);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}, [selectedKeys, isFocused, keyboardNavPosition]);
|
|
1029
|
+
|
|
1030
|
+
// Helper function to scroll a node into view
|
|
1031
|
+
const scrollNodeIntoView = useCallback((nodeKey: string) => {
|
|
1032
|
+
if (!treeRef.current) return;
|
|
1033
|
+
|
|
1034
|
+
setTimeout(() => {
|
|
1035
|
+
const treeContainer = treeRef.current;
|
|
1036
|
+
if (!treeContainer) return;
|
|
1037
|
+
|
|
1038
|
+
const targetNode = treeContainer.querySelector(
|
|
1039
|
+
`[data-node-key="${CSS.escape(nodeKey)}"]`,
|
|
1040
|
+
) as HTMLElement;
|
|
1041
|
+
|
|
1042
|
+
if (targetNode) {
|
|
1043
|
+
let scrollContainer = targetNode.closest(
|
|
1044
|
+
".overflow-auto",
|
|
1045
|
+
) as HTMLElement;
|
|
1046
|
+
|
|
1047
|
+
if (!scrollContainer) {
|
|
1048
|
+
scrollContainer = targetNode.closest(
|
|
1049
|
+
'[style*="overflow"]',
|
|
1050
|
+
) as HTMLElement;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!scrollContainer) {
|
|
1054
|
+
scrollContainer = treeContainer.parentElement as HTMLElement;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (scrollContainer) {
|
|
1058
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
1059
|
+
const nodeRect = targetNode.getBoundingClientRect();
|
|
1060
|
+
|
|
1061
|
+
const isVisible =
|
|
1062
|
+
nodeRect.top >= containerRect.top &&
|
|
1063
|
+
nodeRect.bottom <= containerRect.bottom;
|
|
1064
|
+
|
|
1065
|
+
if (!isVisible) {
|
|
1066
|
+
const scrollTop = scrollContainer.scrollTop;
|
|
1067
|
+
const containerTop = containerRect.top;
|
|
1068
|
+
const nodeTop = nodeRect.top;
|
|
1069
|
+
const offset = nodeTop - containerTop;
|
|
1070
|
+
|
|
1071
|
+
const newScrollTop =
|
|
1072
|
+
scrollTop +
|
|
1073
|
+
offset -
|
|
1074
|
+
containerRect.height / 2 +
|
|
1075
|
+
nodeRect.height / 2;
|
|
1076
|
+
|
|
1077
|
+
scrollContainer.scrollTo({
|
|
1078
|
+
top: newScrollTop,
|
|
1079
|
+
behavior: "smooth",
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
targetNode.scrollIntoView({
|
|
1084
|
+
behavior: "smooth",
|
|
1085
|
+
block: "nearest",
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}, 0);
|
|
1090
|
+
}, []);
|
|
1091
|
+
|
|
1092
|
+
// Keyboard navigation functionality
|
|
1093
|
+
useEffect(() => {
|
|
1094
|
+
if (!enableKeyboardNavigation || !isFocused) return;
|
|
1095
|
+
|
|
1096
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
1097
|
+
// Ignore if user is typing in an input, textarea, or contenteditable element
|
|
1098
|
+
const target = event.target as HTMLElement;
|
|
1099
|
+
if (
|
|
1100
|
+
target.tagName === "INPUT" ||
|
|
1101
|
+
target.tagName === "TEXTAREA" ||
|
|
1102
|
+
target.contentEditable === "true" ||
|
|
1103
|
+
target.isContentEditable
|
|
1104
|
+
) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Get flattened list of visible nodes
|
|
1109
|
+
const visibleNodes = flattenVisibleNodes(filteredNodes, expandedKeys);
|
|
1110
|
+
if (visibleNodes.length === 0) return;
|
|
1111
|
+
|
|
1112
|
+
// Use keyboard navigation position as the current position
|
|
1113
|
+
const currentIndex = keyboardNavPosition
|
|
1114
|
+
? visibleNodes.findIndex((n) => n.node.key === keyboardNavPosition)
|
|
1115
|
+
: -1;
|
|
1116
|
+
|
|
1117
|
+
let newSelectedKey: string | null = null;
|
|
1118
|
+
let shouldUseShift = false;
|
|
1119
|
+
|
|
1120
|
+
switch (event.key) {
|
|
1121
|
+
case "ArrowDown": {
|
|
1122
|
+
event.preventDefault();
|
|
1123
|
+
shouldUseShift = event.shiftKey;
|
|
1124
|
+
if (currentIndex < visibleNodes.length - 1) {
|
|
1125
|
+
const nextNode = visibleNodes[currentIndex + 1];
|
|
1126
|
+
if (nextNode) {
|
|
1127
|
+
newSelectedKey = nextNode.node.key;
|
|
1128
|
+
}
|
|
1129
|
+
} else if (
|
|
1130
|
+
currentIndex === -1 &&
|
|
1131
|
+
visibleNodes.length > 0 &&
|
|
1132
|
+
visibleNodes[0]
|
|
1133
|
+
) {
|
|
1134
|
+
// No selection, select first node
|
|
1135
|
+
newSelectedKey = visibleNodes[0].node.key;
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
case "ArrowUp": {
|
|
1141
|
+
event.preventDefault();
|
|
1142
|
+
shouldUseShift = event.shiftKey;
|
|
1143
|
+
if (currentIndex > 0) {
|
|
1144
|
+
const prevNode = visibleNodes[currentIndex - 1];
|
|
1145
|
+
if (prevNode) {
|
|
1146
|
+
newSelectedKey = prevNode.node.key;
|
|
1147
|
+
}
|
|
1148
|
+
} else if (
|
|
1149
|
+
currentIndex === -1 &&
|
|
1150
|
+
visibleNodes.length > 0 &&
|
|
1151
|
+
visibleNodes[0]
|
|
1152
|
+
) {
|
|
1153
|
+
// No selection, select first node
|
|
1154
|
+
newSelectedKey = visibleNodes[0].node.key;
|
|
1155
|
+
}
|
|
1156
|
+
break;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
case "ArrowRight": {
|
|
1160
|
+
event.preventDefault();
|
|
1161
|
+
const currentNode =
|
|
1162
|
+
currentIndex >= 0 ? visibleNodes[currentIndex] : undefined;
|
|
1163
|
+
if (currentNode) {
|
|
1164
|
+
const node = currentNode.node;
|
|
1165
|
+
const isExpanded = expandedKeys.includes(node.key);
|
|
1166
|
+
|
|
1167
|
+
if (node.hasChildren || node.children?.length) {
|
|
1168
|
+
if (!isExpanded) {
|
|
1169
|
+
// Expand the node
|
|
1170
|
+
if (onToggleExpand) {
|
|
1171
|
+
onToggleExpand(node.key);
|
|
1172
|
+
}
|
|
1173
|
+
if (
|
|
1174
|
+
node.hasChildren &&
|
|
1175
|
+
node.children === undefined &&
|
|
1176
|
+
onLazyLoad
|
|
1177
|
+
) {
|
|
1178
|
+
onLazyLoad(node);
|
|
1179
|
+
}
|
|
1180
|
+
} else if (
|
|
1181
|
+
node.children &&
|
|
1182
|
+
node.children.length > 0 &&
|
|
1183
|
+
node.children[0]
|
|
1184
|
+
) {
|
|
1185
|
+
// Move to first child
|
|
1186
|
+
newSelectedKey = node.children[0].key;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
case "ArrowLeft": {
|
|
1194
|
+
event.preventDefault();
|
|
1195
|
+
const currentNode =
|
|
1196
|
+
currentIndex >= 0 ? visibleNodes[currentIndex] : undefined;
|
|
1197
|
+
if (currentNode) {
|
|
1198
|
+
const node = currentNode.node;
|
|
1199
|
+
const isExpanded = expandedKeys.includes(node.key);
|
|
1200
|
+
|
|
1201
|
+
if (isExpanded && (node.hasChildren || node.children?.length)) {
|
|
1202
|
+
// Collapse the node
|
|
1203
|
+
if (onToggleExpand) {
|
|
1204
|
+
onToggleExpand(node.key);
|
|
1205
|
+
}
|
|
1206
|
+
} else if (currentNode.parent) {
|
|
1207
|
+
// Move to parent node
|
|
1208
|
+
newSelectedKey = currentNode.parent.key;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
case "Enter": {
|
|
1215
|
+
event.preventDefault();
|
|
1216
|
+
if (keyboardNavPosition) {
|
|
1217
|
+
// Toggle expand/collapse if it has children
|
|
1218
|
+
const currentNode =
|
|
1219
|
+
currentIndex >= 0 ? visibleNodes[currentIndex] : undefined;
|
|
1220
|
+
if (currentNode) {
|
|
1221
|
+
const node = currentNode.node;
|
|
1222
|
+
if (node.hasChildren || node.children?.length) {
|
|
1223
|
+
if (onToggleExpand) {
|
|
1224
|
+
onToggleExpand(node.key);
|
|
1225
|
+
}
|
|
1226
|
+
if (
|
|
1227
|
+
node.hasChildren &&
|
|
1228
|
+
node.children === undefined &&
|
|
1229
|
+
onLazyLoad
|
|
1230
|
+
) {
|
|
1231
|
+
onLazyLoad(node);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
case "Home": {
|
|
1240
|
+
event.preventDefault();
|
|
1241
|
+
if (visibleNodes.length > 0) {
|
|
1242
|
+
const firstNode = visibleNodes[0];
|
|
1243
|
+
if (firstNode) {
|
|
1244
|
+
newSelectedKey = firstNode.node.key;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
case "End": {
|
|
1251
|
+
event.preventDefault();
|
|
1252
|
+
if (visibleNodes.length > 0) {
|
|
1253
|
+
const lastNode = visibleNodes[visibleNodes.length - 1];
|
|
1254
|
+
if (lastNode) {
|
|
1255
|
+
newSelectedKey = lastNode.node.key;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
if (newSelectedKey && newSelectedKey !== keyboardNavPosition) {
|
|
1263
|
+
// Update keyboard navigation position
|
|
1264
|
+
setKeyboardNavPosition(newSelectedKey);
|
|
1265
|
+
|
|
1266
|
+
if (onSelect) {
|
|
1267
|
+
// Create a synthetic mouse event with shiftKey set for range selection
|
|
1268
|
+
const syntheticEvent = {
|
|
1269
|
+
shiftKey: shouldUseShift,
|
|
1270
|
+
ctrlKey: false,
|
|
1271
|
+
metaKey: false,
|
|
1272
|
+
button: 0,
|
|
1273
|
+
} as React.MouseEvent;
|
|
1274
|
+
onSelect(newSelectedKey, syntheticEvent);
|
|
1275
|
+
}
|
|
1276
|
+
scrollNodeIntoView(newSelectedKey);
|
|
1277
|
+
if (onKeyboardNavigationChange) {
|
|
1278
|
+
onKeyboardNavigationChange(newSelectedKey);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
|
|
1283
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1284
|
+
|
|
1285
|
+
return () => {
|
|
1286
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
1287
|
+
};
|
|
1288
|
+
}, [
|
|
1289
|
+
enableKeyboardNavigation,
|
|
1290
|
+
isFocused,
|
|
1291
|
+
keyboardNavPosition,
|
|
1292
|
+
filteredNodes,
|
|
1293
|
+
expandedKeys,
|
|
1294
|
+
onToggleExpand,
|
|
1295
|
+
onSelect,
|
|
1296
|
+
onLazyLoad,
|
|
1297
|
+
scrollNodeIntoView,
|
|
1298
|
+
onKeyboardNavigationChange,
|
|
1299
|
+
]);
|
|
1300
|
+
|
|
967
1301
|
// Keyboard search functionality
|
|
968
1302
|
useEffect(() => {
|
|
969
1303
|
if (!enableKeyboardSearch || !isFocused) return;
|
|
@@ -980,6 +1314,21 @@ export const PerfectTree = <T,>({
|
|
|
980
1314
|
return;
|
|
981
1315
|
}
|
|
982
1316
|
|
|
1317
|
+
// Ignore navigation keys for search
|
|
1318
|
+
if (
|
|
1319
|
+
[
|
|
1320
|
+
"ArrowUp",
|
|
1321
|
+
"ArrowDown",
|
|
1322
|
+
"ArrowLeft",
|
|
1323
|
+
"ArrowRight",
|
|
1324
|
+
"Enter",
|
|
1325
|
+
"Home",
|
|
1326
|
+
"End",
|
|
1327
|
+
].includes(event.key)
|
|
1328
|
+
) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
983
1332
|
// Handle different key types
|
|
984
1333
|
if (event.key === "Escape") {
|
|
985
1334
|
// Clear search on Escape
|
|
@@ -1039,6 +1388,7 @@ export const PerfectTree = <T,>({
|
|
|
1039
1388
|
onBlur={() => {
|
|
1040
1389
|
setIsFocused(false);
|
|
1041
1390
|
setSearchTerm("");
|
|
1391
|
+
setKeyboardNavPosition(null);
|
|
1042
1392
|
if (searchTimeoutRef.current) {
|
|
1043
1393
|
clearTimeout(searchTimeoutRef.current);
|
|
1044
1394
|
searchTimeoutRef.current = null;
|
|
@@ -1073,7 +1423,8 @@ const arePropsEqual = <T,>(
|
|
|
1073
1423
|
prevProps.searchClearDelay !== nextProps.searchClearDelay ||
|
|
1074
1424
|
prevProps.disableAutoSelectOnExpand !==
|
|
1075
1425
|
nextProps.disableAutoSelectOnExpand ||
|
|
1076
|
-
prevProps.multiDragNoun !== nextProps.multiDragNoun
|
|
1426
|
+
prevProps.multiDragNoun !== nextProps.multiDragNoun ||
|
|
1427
|
+
prevProps.enableKeyboardNavigation !== nextProps.enableKeyboardNavigation
|
|
1077
1428
|
) {
|
|
1078
1429
|
return false;
|
|
1079
1430
|
}
|
|
@@ -23,6 +23,8 @@ export interface KeyboardNavigationDependencies {
|
|
|
23
23
|
data?: any;
|
|
24
24
|
event?: React.SyntheticEvent;
|
|
25
25
|
}) => Promise<any>;
|
|
26
|
+
showQuickSwitcher?: (show: boolean) => void;
|
|
27
|
+
cycleQuickSwitcher?: (direction: "next" | "prev" | "up" | "down") => void;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export function useKeyboardNavigation(deps: KeyboardNavigationDependencies) {
|
|
@@ -36,6 +38,8 @@ export function useKeyboardNavigation(deps: KeyboardNavigationDependencies) {
|
|
|
36
38
|
loadItem,
|
|
37
39
|
showInfoToast,
|
|
38
40
|
executeCommand,
|
|
41
|
+
showQuickSwitcher,
|
|
42
|
+
cycleQuickSwitcher,
|
|
39
43
|
} = deps;
|
|
40
44
|
|
|
41
45
|
const handleKeyDownDebounced = useDebouncedCallback(
|
|
@@ -169,6 +173,18 @@ export function useKeyboardNavigation(deps: KeyboardNavigationDependencies) {
|
|
|
169
173
|
);
|
|
170
174
|
|
|
171
175
|
if (command) {
|
|
176
|
+
// Check if user is typing in an input field
|
|
177
|
+
const target = event.target as HTMLElement;
|
|
178
|
+
const isTyping =
|
|
179
|
+
target instanceof HTMLInputElement ||
|
|
180
|
+
target instanceof HTMLTextAreaElement ||
|
|
181
|
+
target.isContentEditable;
|
|
182
|
+
|
|
183
|
+
// Don't execute commands when typing in text fields
|
|
184
|
+
// F2 works everywhere except when typing
|
|
185
|
+
// Delete also won't trigger when typing
|
|
186
|
+
if (isTyping) return;
|
|
187
|
+
|
|
172
188
|
event.preventDefault();
|
|
173
189
|
const contentEditorItem = editContextRef.current?.contentEditorItem;
|
|
174
190
|
if (!contentEditorItem) return;
|
|
@@ -211,6 +227,80 @@ export function useKeyboardNavigation(deps: KeyboardNavigationDependencies) {
|
|
|
211
227
|
return;
|
|
212
228
|
}
|
|
213
229
|
|
|
230
|
+
// Check if quick switcher is currently visible
|
|
231
|
+
const isQuickSwitcherVisible = editContextRef.current
|
|
232
|
+
? (editContextRef.current as any).isQuickSwitcherVisible
|
|
233
|
+
: false;
|
|
234
|
+
|
|
235
|
+
// Handle navigation keys when switcher is visible (check this FIRST)
|
|
236
|
+
if (isQuickSwitcherVisible && editContextRef.current) {
|
|
237
|
+
// Shift key acts as ArrowRight for easy one-handed navigation
|
|
238
|
+
if (event.key === "Shift" && event.ctrlKey) {
|
|
239
|
+
console.log(`[KeyNav] Shift pressed (Ctrl held) - moving right`);
|
|
240
|
+
event.preventDefault();
|
|
241
|
+
event.stopPropagation();
|
|
242
|
+
cycleQuickSwitcher?.("next");
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Arrow keys for navigation
|
|
247
|
+
if (
|
|
248
|
+
event.key === "ArrowLeft" ||
|
|
249
|
+
event.key === "ArrowRight" ||
|
|
250
|
+
event.key === "ArrowUp" ||
|
|
251
|
+
event.key === "ArrowDown"
|
|
252
|
+
) {
|
|
253
|
+
console.log(`[KeyNav] Arrow key navigation: ${event.key}`);
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
event.stopPropagation();
|
|
256
|
+
|
|
257
|
+
let direction: "next" | "prev" | "up" | "down";
|
|
258
|
+
switch (event.key) {
|
|
259
|
+
case "ArrowLeft":
|
|
260
|
+
direction = "prev";
|
|
261
|
+
break;
|
|
262
|
+
case "ArrowRight":
|
|
263
|
+
direction = "next";
|
|
264
|
+
break;
|
|
265
|
+
case "ArrowUp":
|
|
266
|
+
direction = "up";
|
|
267
|
+
break;
|
|
268
|
+
case "ArrowDown":
|
|
269
|
+
direction = "down";
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
cycleQuickSwitcher?.(direction);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Enter or Escape to close
|
|
277
|
+
if (event.key === "Enter" || event.key === "Escape") {
|
|
278
|
+
console.log(
|
|
279
|
+
`[KeyNav] ${event.key} pressed - closing switcher with selection: ${event.key === "Enter"}`,
|
|
280
|
+
);
|
|
281
|
+
event.preventDefault();
|
|
282
|
+
event.stopPropagation();
|
|
283
|
+
// showQuickSwitcher(false) will be handled by the Enter key handler in EditorShell
|
|
284
|
+
// For now, we just let the keyup handler in EditorShell handle it
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Handle quick switcher trigger (Ctrl+Shift pressed together) - only if NOT already visible
|
|
290
|
+
if (
|
|
291
|
+
!isQuickSwitcherVisible &&
|
|
292
|
+
((event.key === "Shift" && event.ctrlKey) ||
|
|
293
|
+
(event.key === "Control" && event.shiftKey)) &&
|
|
294
|
+
editContextRef.current &&
|
|
295
|
+
browseHistory.length > 1
|
|
296
|
+
) {
|
|
297
|
+
console.log("[KeyNav] Quick switcher trigger: Ctrl+Shift");
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
event.stopPropagation();
|
|
300
|
+
showQuickSwitcher?.(true);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
214
304
|
const target = event.target as HTMLElement;
|
|
215
305
|
const isTyping =
|
|
216
306
|
target instanceof HTMLInputElement ||
|
|
@@ -230,7 +320,13 @@ export function useKeyboardNavigation(deps: KeyboardNavigationDependencies) {
|
|
|
230
320
|
}
|
|
231
321
|
handleKeyDownDebounced(event);
|
|
232
322
|
},
|
|
233
|
-
[
|
|
323
|
+
[
|
|
324
|
+
handleKeyDownDebounced,
|
|
325
|
+
editContextRef,
|
|
326
|
+
showQuickSwitcher,
|
|
327
|
+
cycleQuickSwitcher,
|
|
328
|
+
browseHistory,
|
|
329
|
+
],
|
|
234
330
|
);
|
|
235
331
|
|
|
236
332
|
return { handleKeyDown };
|
|
@@ -38,23 +38,37 @@ export function SingleEditView({
|
|
|
38
38
|
item?.templateId?.toLowerCase() ===
|
|
39
39
|
"fe5dd826-48c6-436d-b87a-7c4210c7413b";
|
|
40
40
|
|
|
41
|
-
if (
|
|
42
|
-
|
|
41
|
+
// Check if this is the media library root item (typically has template ID eb22c1a2-8899-4d2d-b36c-4aeb209e66b7)
|
|
42
|
+
const isMediaLibraryRoot =
|
|
43
|
+
item?.id?.toLowerCase() === "3d6658d8-a0bf-4e75-b3e2-d050fabcf4e1";
|
|
44
|
+
|
|
45
|
+
if (isMediaFolder || isMediaLibraryRoot) {
|
|
46
|
+
return (
|
|
47
|
+
<div data-view="media-folder-edit-view" className="h-full w-full">
|
|
48
|
+
<MediaFolderEditView item={item} />
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
return
|
|
53
|
+
return (
|
|
54
|
+
<div data-view="item-editor-view" className="h-full w-full">
|
|
55
|
+
<ItemEditor item={item} />
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
return (
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
<div data-view="single-edit-view" className="h-full w-full">
|
|
62
|
+
<PageViewer
|
|
63
|
+
pageViewContext={pageViewContext}
|
|
64
|
+
showFormEditor={
|
|
65
|
+
editContext?.viewName == "page-editor" || !editContext?.isMobile
|
|
66
|
+
}
|
|
67
|
+
compareView={compareView}
|
|
68
|
+
name={name}
|
|
69
|
+
followEditsDefault={false}
|
|
70
|
+
className={className}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
59
73
|
);
|
|
60
74
|
}
|
package/src/revision.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = "1.0.
|
|
2
|
-
export const buildDate = "2025-10-
|
|
1
|
+
export const version = "1.0.4174";
|
|
2
|
+
export const buildDate = "2025-10-21 01:59:37";
|