@alpaca-editor/core 1.0.4147 → 1.0.4149
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/ContentTree.js +11 -7
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.js +11 -18
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.js +46 -12
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +55 -49
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/ui/PerfectTree.d.ts +1 -1
- package/dist/editor/ui/PerfectTree.js +34 -2
- package/dist/editor/ui/PerfectTree.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/package.json +1 -1
- package/src/editor/ContentTree.tsx +21 -8
- package/src/editor/ai/AgentTerminal.tsx +11 -17
- package/src/editor/ai/ToolCallDisplay.tsx +106 -69
- package/src/editor/sidebar/ComponentTree.tsx +116 -91
- package/src/editor/ui/PerfectTree.tsx +51 -2
- package/src/revision.ts +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
1
|
+
import React, { useState, useMemo, memo } from "react";
|
|
2
2
|
import { JsonView, defaultStyles } from "react-json-view-lite";
|
|
3
3
|
import "react-json-view-lite/dist/index.css";
|
|
4
4
|
|
|
@@ -159,20 +159,33 @@ const normalizeToolCall = (
|
|
|
159
159
|
return toolCall;
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
if (typeof json === "object" && json !== null) {
|
|
162
|
+
// Memoized JSON viewer component to prevent re-renders during streaming
|
|
163
|
+
const MemoizedJsonView = memo(
|
|
164
|
+
({ data }: { data: any }) => {
|
|
166
165
|
return (
|
|
167
166
|
<div className="font-mono text-xs" style={{ fontSize: "12px" }}>
|
|
168
167
|
<JsonLightThemeStyles />
|
|
169
168
|
<JsonView
|
|
170
|
-
data={
|
|
169
|
+
data={data}
|
|
171
170
|
shouldExpandNode={(level) => level < 2}
|
|
172
171
|
style={darkJsonStyles}
|
|
173
172
|
/>
|
|
174
173
|
</div>
|
|
175
174
|
);
|
|
175
|
+
},
|
|
176
|
+
(prevProps, nextProps) => {
|
|
177
|
+
// Only re-render if the data actually changed (deep comparison by stringifying)
|
|
178
|
+
return JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
MemoizedJsonView.displayName = "MemoizedJsonView";
|
|
183
|
+
|
|
184
|
+
// Helper function to parse JSON string to object
|
|
185
|
+
const parseJsonString = (json: string | object): any | null => {
|
|
186
|
+
// If it's already an object, return it directly
|
|
187
|
+
if (typeof json === "object" && json !== null) {
|
|
188
|
+
return json;
|
|
176
189
|
}
|
|
177
190
|
|
|
178
191
|
// Convert to string if not already
|
|
@@ -194,16 +207,7 @@ const renderJsonOrText = (json: string | object) => {
|
|
|
194
207
|
}
|
|
195
208
|
}
|
|
196
209
|
|
|
197
|
-
return
|
|
198
|
-
<div className="font-mono text-xs" style={{ fontSize: "12px" }}>
|
|
199
|
-
<JsonLightThemeStyles />
|
|
200
|
-
<JsonView
|
|
201
|
-
data={parsed}
|
|
202
|
-
shouldExpandNode={(level) => level < 2}
|
|
203
|
-
style={darkJsonStyles}
|
|
204
|
-
/>
|
|
205
|
-
</div>
|
|
206
|
-
);
|
|
210
|
+
return parsed;
|
|
207
211
|
} catch (e) {
|
|
208
212
|
console.log("JSON parse failed:", e, "Trying to handle as string...");
|
|
209
213
|
|
|
@@ -211,29 +215,32 @@ const renderJsonOrText = (json: string | object) => {
|
|
|
211
215
|
try {
|
|
212
216
|
const unescaped = jsonString.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
213
217
|
const parsed = JSON.parse(unescaped);
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<div className="font-mono text-xs" style={{ fontSize: "12px" }}>
|
|
217
|
-
<JsonLightThemeStyles />
|
|
218
|
-
<JsonView
|
|
219
|
-
data={parsed}
|
|
220
|
-
shouldExpandNode={(level) => level < 2}
|
|
221
|
-
style={darkJsonStyles}
|
|
222
|
-
/>
|
|
223
|
-
</div>
|
|
224
|
-
);
|
|
218
|
+
return parsed;
|
|
225
219
|
} catch (e2) {
|
|
226
220
|
console.log("Unescaping also failed:", e2);
|
|
227
|
-
// If all parsing fails, display as plain text
|
|
228
|
-
return
|
|
229
|
-
<div className="font-mono text-xs break-words whitespace-pre-wrap text-gray-700">
|
|
230
|
-
{jsonString}
|
|
231
|
-
</div>
|
|
232
|
-
);
|
|
221
|
+
// If all parsing fails, return null to indicate we should display as plain text
|
|
222
|
+
return null;
|
|
233
223
|
}
|
|
234
224
|
}
|
|
235
225
|
};
|
|
236
226
|
|
|
227
|
+
// Helper function to render JSON or text
|
|
228
|
+
const renderJsonOrText = (json: string | object) => {
|
|
229
|
+
const parsed = parseJsonString(json);
|
|
230
|
+
|
|
231
|
+
if (parsed !== null) {
|
|
232
|
+
return <MemoizedJsonView data={parsed} />;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// If parsing failed, display as plain text
|
|
236
|
+
const jsonString = typeof json === "string" ? json : String(json);
|
|
237
|
+
return (
|
|
238
|
+
<div className="font-mono text-xs break-words whitespace-pre-wrap text-gray-700">
|
|
239
|
+
{jsonString}
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
237
244
|
// Expandable panel component
|
|
238
245
|
const ExpandablePanel = ({
|
|
239
246
|
title,
|
|
@@ -277,44 +284,74 @@ const ExpandablePanel = ({
|
|
|
277
284
|
};
|
|
278
285
|
|
|
279
286
|
// Helper function to create expandable tool call details
|
|
280
|
-
const ToolCallDetails = (
|
|
281
|
-
toolCall,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
toolCall: BaseToolCall;
|
|
285
|
-
result?: string;
|
|
286
|
-
}) => {
|
|
287
|
-
const hasError = toolCall.function?.error;
|
|
288
|
-
const hasOutput = result || hasError;
|
|
287
|
+
const ToolCallDetails = memo(
|
|
288
|
+
({ toolCall, result }: { toolCall: BaseToolCall; result?: string }) => {
|
|
289
|
+
const hasError = toolCall.function?.error;
|
|
290
|
+
const hasOutput = result || hasError;
|
|
289
291
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
>
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
<
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)}
|
|
292
|
+
// Memoize parsed input data to prevent re-parsing on every render
|
|
293
|
+
const parsedInput = useMemo(() => {
|
|
294
|
+
return parseJsonString(toolCall.function?.arguments || "");
|
|
295
|
+
}, [toolCall.function?.arguments]);
|
|
296
|
+
|
|
297
|
+
// Memoize parsed output data to prevent re-parsing on every render
|
|
298
|
+
const parsedOutput = useMemo(() => {
|
|
299
|
+
return parseJsonString(result || "");
|
|
300
|
+
}, [result]);
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div className="mt-2 ml-4 overflow-hidden rounded-lg border border-gray-200/80 bg-gradient-to-br from-gray-50 to-gray-50/30 shadow-sm">
|
|
304
|
+
<ExpandablePanel title="Input" defaultExpanded={!hasOutput}>
|
|
305
|
+
<div className="rounded-md border border-gray-200 bg-white p-3 text-xs shadow-sm">
|
|
306
|
+
{parsedInput !== null ? (
|
|
307
|
+
<MemoizedJsonView data={parsedInput} />
|
|
308
|
+
) : (
|
|
309
|
+
<div className="font-mono text-xs break-words whitespace-pre-wrap text-gray-700">
|
|
310
|
+
{toolCall.function?.arguments || ""}
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
313
314
|
</ExpandablePanel>
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
315
|
+
|
|
316
|
+
{hasOutput && (
|
|
317
|
+
<ExpandablePanel
|
|
318
|
+
title={hasError ? "Error" : "Output"}
|
|
319
|
+
defaultExpanded={true}
|
|
320
|
+
>
|
|
321
|
+
{hasError ? (
|
|
322
|
+
<div className="rounded-md border-l-4 border-red-500 bg-red-50/80 p-3 text-xs text-red-700 shadow-sm">
|
|
323
|
+
<div className="mb-1.5 font-semibold">Tool Error:</div>
|
|
324
|
+
<div className="text-red-600">{toolCall.function?.error}</div>
|
|
325
|
+
</div>
|
|
326
|
+
) : (
|
|
327
|
+
<div className="rounded-md border border-gray-200 bg-white p-3 text-xs shadow-sm">
|
|
328
|
+
{parsedOutput !== null ? (
|
|
329
|
+
<MemoizedJsonView data={parsedOutput} />
|
|
330
|
+
) : (
|
|
331
|
+
<div className="font-mono text-xs break-words whitespace-pre-wrap text-gray-700">
|
|
332
|
+
{result || ""}
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
337
|
+
</ExpandablePanel>
|
|
338
|
+
)}
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
},
|
|
342
|
+
(prevProps, nextProps) => {
|
|
343
|
+
// Only re-render if the data actually changed
|
|
344
|
+
return (
|
|
345
|
+
prevProps.toolCall.id === nextProps.toolCall.id &&
|
|
346
|
+
prevProps.toolCall.function?.arguments ===
|
|
347
|
+
nextProps.toolCall.function?.arguments &&
|
|
348
|
+
prevProps.result === nextProps.result &&
|
|
349
|
+
prevProps.toolCall.function?.error === nextProps.toolCall.function?.error
|
|
350
|
+
);
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
ToolCallDetails.displayName = "ToolCallDetails";
|
|
318
355
|
|
|
319
356
|
export function ToolCallDisplay({
|
|
320
357
|
toolCalls,
|
|
@@ -728,41 +728,120 @@ export function ComponentTree({}) {
|
|
|
728
728
|
return { placeholder: null, indexInPlaceholder: 0 };
|
|
729
729
|
};
|
|
730
730
|
|
|
731
|
-
// Handle drag over node
|
|
732
|
-
const handleDragOverZone = (
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
731
|
+
// Handle drag over node (memoized)
|
|
732
|
+
const handleDragOverZone = useCallback(
|
|
733
|
+
(
|
|
734
|
+
dragOverNode: TreeNode | null,
|
|
735
|
+
index: number,
|
|
736
|
+
event: React.DragEvent,
|
|
737
|
+
): boolean => {
|
|
738
|
+
if (!editContext?.dragObject) return false;
|
|
739
|
+
const { placeholder } = resolveDropContext(
|
|
740
|
+
dragOverNode as CustomTreeNode | null,
|
|
741
|
+
index,
|
|
742
|
+
);
|
|
743
|
+
if (!placeholder) return false;
|
|
744
|
+
const isValid = isValidPlaceholder(placeholder, editContext.dragObject);
|
|
745
|
+
event.dataTransfer.dropEffect = isValid ? "move" : "none";
|
|
746
|
+
return isValid;
|
|
747
|
+
},
|
|
748
|
+
[editContext?.dragObject],
|
|
749
|
+
);
|
|
744
750
|
|
|
745
|
-
// Handle drop on node
|
|
746
|
-
const handleDropZone = (
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
751
|
+
// Handle drop on node (memoized)
|
|
752
|
+
const handleDropZone = useCallback(
|
|
753
|
+
(
|
|
754
|
+
droppedOnNode: TreeNode | null,
|
|
755
|
+
index: number,
|
|
756
|
+
event: React.DragEvent,
|
|
757
|
+
): void => {
|
|
758
|
+
// When dropping on a node (index < 0), use legacy behavior
|
|
759
|
+
if (index < 0) {
|
|
760
|
+
const placeholder = getPlaceholder(
|
|
761
|
+
droppedOnNode as CustomTreeNode | null,
|
|
762
|
+
);
|
|
763
|
+
if (!placeholder) return;
|
|
764
|
+
editContext!.droppedInPlaceholder(placeholder.key, index);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const { placeholder, indexInPlaceholder } = resolveDropContext(
|
|
769
|
+
droppedOnNode as CustomTreeNode | null,
|
|
770
|
+
index,
|
|
771
|
+
);
|
|
754
772
|
if (!placeholder) return;
|
|
755
|
-
editContext!.droppedInPlaceholder(placeholder.key,
|
|
756
|
-
|
|
757
|
-
|
|
773
|
+
editContext!.droppedInPlaceholder(placeholder.key, indexInPlaceholder);
|
|
774
|
+
},
|
|
775
|
+
[editContext],
|
|
776
|
+
);
|
|
758
777
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
778
|
+
// Memoize isValidDropZone callback
|
|
779
|
+
const handleIsValidDropZone = useCallback(
|
|
780
|
+
(parent: TreeNode | null, index: number): boolean => {
|
|
781
|
+
const { placeholder } = resolveDropContext(
|
|
782
|
+
parent as CustomTreeNode | null,
|
|
783
|
+
index,
|
|
784
|
+
);
|
|
785
|
+
if (!placeholder) return false;
|
|
786
|
+
if (!editContext?.dragObject) return false;
|
|
787
|
+
const result = isValidPlaceholder(placeholder, editContext.dragObject);
|
|
788
|
+
return result;
|
|
789
|
+
},
|
|
790
|
+
[editContext?.dragObject],
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
// Memoize onStartDrag callback
|
|
794
|
+
const handleStartDrag = useCallback(
|
|
795
|
+
(data: {
|
|
796
|
+
node: TreeNode;
|
|
797
|
+
event: React.DragEvent;
|
|
798
|
+
isMultiSelect: boolean;
|
|
799
|
+
}) => {
|
|
800
|
+
const component = data.node.data as Component;
|
|
801
|
+
|
|
802
|
+
// Initialize drag data to make sure dataTransfer is set
|
|
803
|
+
data.event.dataTransfer.setData("text/plain", data.node.key);
|
|
804
|
+
// Align with FrameMenu: include componentId and allowed effect
|
|
805
|
+
data.event.dataTransfer.setData("componentId", component.id);
|
|
806
|
+
data.event.dataTransfer.effectAllowed = "copyMove";
|
|
807
|
+
|
|
808
|
+
// Only create drag object for components with datasourceItem
|
|
809
|
+
if (!component?.datasourceItem) return;
|
|
810
|
+
setTimeout(() => {
|
|
811
|
+
const language = editContext!.page!.item.language;
|
|
812
|
+
const version = editContext!.page!.item.version;
|
|
813
|
+
const selectedIds =
|
|
814
|
+
(editContext?.selection?.length || 0) > 1 &&
|
|
815
|
+
editContext!.selection.includes(component.id)
|
|
816
|
+
? editContext!.selection
|
|
817
|
+
: [component.id];
|
|
818
|
+
const componentsToDrag = selectedIds.map((id) => ({
|
|
819
|
+
id,
|
|
820
|
+
language,
|
|
821
|
+
version,
|
|
822
|
+
}));
|
|
823
|
+
|
|
824
|
+
editContext!.dragStart({
|
|
825
|
+
type: "component",
|
|
826
|
+
typeId: component.typeId,
|
|
827
|
+
templateId: component.datasourceItem?.templateId,
|
|
828
|
+
name: component.name,
|
|
829
|
+
components: componentsToDrag,
|
|
830
|
+
});
|
|
831
|
+
}, 50);
|
|
832
|
+
// Prevent any upstream handlers from interfering
|
|
833
|
+
data.event.stopPropagation();
|
|
834
|
+
},
|
|
835
|
+
[editContext],
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
// Memoize onDragEnd callback
|
|
839
|
+
const handleDragEnd = useCallback(
|
|
840
|
+
(event: React.DragEvent | null) => {
|
|
841
|
+
editContext!.dragEnd();
|
|
842
|
+
},
|
|
843
|
+
[editContext],
|
|
844
|
+
);
|
|
766
845
|
|
|
767
846
|
if (!page) {
|
|
768
847
|
if (editContext?.contentEditorItem?.hasLayout) {
|
|
@@ -829,65 +908,11 @@ export function ComponentTree({}) {
|
|
|
829
908
|
onSelect={handleTreeSelection}
|
|
830
909
|
onContextMenu={handleTreeContextMenu}
|
|
831
910
|
enableDragAndDrop={true}
|
|
832
|
-
onDragOverZone={
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
parent as CustomTreeNode | null,
|
|
838
|
-
index,
|
|
839
|
-
);
|
|
840
|
-
if (!placeholder) return false;
|
|
841
|
-
if (!editContext?.dragObject) return false;
|
|
842
|
-
const result = isValidPlaceholder(
|
|
843
|
-
placeholder,
|
|
844
|
-
editContext.dragObject,
|
|
845
|
-
);
|
|
846
|
-
|
|
847
|
-
return result;
|
|
848
|
-
}}
|
|
849
|
-
onStartDrag={(data) => {
|
|
850
|
-
const component = data.node.data as Component;
|
|
851
|
-
|
|
852
|
-
// Initialize drag data to make sure dataTransfer is set
|
|
853
|
-
data.event.dataTransfer.setData("text/plain", data.node.key);
|
|
854
|
-
// Align with FrameMenu: include componentId and allowed effect
|
|
855
|
-
data.event.dataTransfer.setData("componentId", component.id);
|
|
856
|
-
data.event.dataTransfer.effectAllowed = "copyMove";
|
|
857
|
-
|
|
858
|
-
// Only create drag object for components with datasourceItem
|
|
859
|
-
if (!component?.datasourceItem) return;
|
|
860
|
-
setTimeout(() => {
|
|
861
|
-
const language = editContext!.page!.item.language;
|
|
862
|
-
const version = editContext!.page!.item.version;
|
|
863
|
-
const selectedIds =
|
|
864
|
-
(editContext?.selection?.length || 0) > 1 &&
|
|
865
|
-
editContext!.selection.includes(component.id)
|
|
866
|
-
? editContext!.selection
|
|
867
|
-
: [component.id];
|
|
868
|
-
const componentsToDrag = selectedIds.map((id) => ({
|
|
869
|
-
id,
|
|
870
|
-
language,
|
|
871
|
-
version,
|
|
872
|
-
}));
|
|
873
|
-
|
|
874
|
-
editContext!.dragStart({
|
|
875
|
-
type: "component",
|
|
876
|
-
typeId: component.typeId,
|
|
877
|
-
templateId: component.datasourceItem?.templateId,
|
|
878
|
-
name: component.name,
|
|
879
|
-
components: componentsToDrag,
|
|
880
|
-
});
|
|
881
|
-
}, 50);
|
|
882
|
-
// Prevent any upstream handlers from interfering
|
|
883
|
-
data.event.stopPropagation();
|
|
884
|
-
}}
|
|
885
|
-
onDragEnd={(event) => {
|
|
886
|
-
editContext!.dragEnd();
|
|
887
|
-
}}
|
|
888
|
-
onDrop={(parent, index, event) =>
|
|
889
|
-
handleDropZone(parent as CustomTreeNode | null, index, event)
|
|
890
|
-
}
|
|
911
|
+
onDragOverZone={handleDragOverZone}
|
|
912
|
+
isValidDropZone={handleIsValidDropZone}
|
|
913
|
+
onStartDrag={handleStartDrag}
|
|
914
|
+
onDragEnd={handleDragEnd}
|
|
915
|
+
onDrop={handleDropZone}
|
|
891
916
|
renderNode={(node) => renderNode(node as CustomTreeNode)}
|
|
892
917
|
/>
|
|
893
918
|
</div>
|
|
@@ -945,12 +945,16 @@ export const PerfectTree = <T,>({
|
|
|
945
945
|
onDragOverZone,
|
|
946
946
|
onDrop,
|
|
947
947
|
onDragEnd,
|
|
948
|
-
|
|
948
|
+
handleStartDragWithTimeout,
|
|
949
949
|
onDoubleClick,
|
|
950
|
+
onContextMenu,
|
|
950
951
|
handleSelect,
|
|
951
952
|
handleToggle,
|
|
952
953
|
enhancedRenderNode,
|
|
953
954
|
searchTerm,
|
|
955
|
+
enableDragAndDrop,
|
|
956
|
+
multiDragNoun,
|
|
957
|
+
isValidDropZone,
|
|
954
958
|
],
|
|
955
959
|
);
|
|
956
960
|
|
|
@@ -1053,4 +1057,49 @@ export const PerfectTree = <T,>({
|
|
|
1053
1057
|
);
|
|
1054
1058
|
};
|
|
1055
1059
|
|
|
1056
|
-
|
|
1060
|
+
// Custom comparison function for memo to prevent unnecessary re-renders
|
|
1061
|
+
// when callback props change but actual data hasn't
|
|
1062
|
+
const arePropsEqual = <T,>(
|
|
1063
|
+
prevProps: Readonly<TreeProps<T>>,
|
|
1064
|
+
nextProps: Readonly<TreeProps<T>>,
|
|
1065
|
+
): boolean => {
|
|
1066
|
+
// Compare primitive and array props
|
|
1067
|
+
if (
|
|
1068
|
+
prevProps.nodes !== nextProps.nodes ||
|
|
1069
|
+
prevProps.isDragging !== nextProps.isDragging ||
|
|
1070
|
+
prevProps.enableDragAndDrop !== nextProps.enableDragAndDrop ||
|
|
1071
|
+
prevProps.scrollToSelected !== nextProps.scrollToSelected ||
|
|
1072
|
+
prevProps.enableKeyboardSearch !== nextProps.enableKeyboardSearch ||
|
|
1073
|
+
prevProps.searchClearDelay !== nextProps.searchClearDelay ||
|
|
1074
|
+
prevProps.disableAutoSelectOnExpand !==
|
|
1075
|
+
nextProps.disableAutoSelectOnExpand ||
|
|
1076
|
+
prevProps.multiDragNoun !== nextProps.multiDragNoun
|
|
1077
|
+
) {
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// Compare arrays with shallow equality
|
|
1082
|
+
if (
|
|
1083
|
+
prevProps.selectedKeys?.length !== nextProps.selectedKeys?.length ||
|
|
1084
|
+
prevProps.selectedKeys?.some(
|
|
1085
|
+
(key, i) => key !== nextProps.selectedKeys?.[i],
|
|
1086
|
+
)
|
|
1087
|
+
) {
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
if (
|
|
1092
|
+
prevProps.expandedKeys?.length !== nextProps.expandedKeys?.length ||
|
|
1093
|
+
prevProps.expandedKeys?.some(
|
|
1094
|
+
(key, i) => key !== nextProps.expandedKeys?.[i],
|
|
1095
|
+
)
|
|
1096
|
+
) {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// For callback props, we assume they are stable if memoized in parent
|
|
1101
|
+
// If they change on every render, the parent should memoize them with useCallback
|
|
1102
|
+
return true;
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
export default memo(PerfectTree, arePropsEqual) as typeof PerfectTree;
|
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.4149";
|
|
2
|
+
export const buildDate = "2025-10-07 09:38:14";
|