Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions src/CP/Navigation/NavTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,30 @@ protected function getReorderedItems($originalList, $newList): bool|array
*/
protected function calculateMinimumItemsForReorder($originalList, $newList): int
{
// When the new list contains items not in the original (e.g., custom sections),
// we need to include enough items to establish where those new items are positioned,
// BUT only if they appear in the middle of the list. Items appended at the end
// don't need to be explicitly positioned.

$originalSet = collect($originalList);
$lastCustomPositionInMiddle = 0;

foreach ($newList as $index => $item) {
if (! $originalSet->contains($item)) {
// Check if there are any original items after this custom item
$hasOriginalItemsAfter = collect($newList)
->slice($index + 1)
->contains(fn ($futureItem) => $originalSet->contains($futureItem));

// Only track this position if there are original items after it
// (meaning it's in the middle, not at the end)
if ($hasOriginalItemsAfter) {
$lastCustomPositionInMiddle = $index + 1;
}
}
}

// Use the original algorithm for reordering existing items
$continueRejecting = true;

$minimumItemsCount = collect($originalList)
Expand All @@ -295,7 +319,11 @@ protected function calculateMinimumItemsForReorder($originalList, $newList): int
})
->count();

return max(1, $minimumItemsCount - 1);
$minimumFromReordering = max(1, $minimumItemsCount - 1);

// If we have custom items in the middle of the list, we need to include
// enough items to establish their position. Return the maximum of the two.
return max($minimumFromReordering, $lastCustomPositionInMiddle);
}

/**
Expand Down Expand Up @@ -329,7 +357,7 @@ protected function minify()
->values()
->all();

$this->config = $reorder
$this->config = ! empty($reorder)
? array_filter(compact('reorder', 'sections'))
: $sections;

Expand Down
54 changes: 54 additions & 0 deletions tests/CP/Navigation/NavTransformerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,60 @@ public function it_can_transform_complex_json_payload_copied_from_actual_vue_sub

$this->assertEquals($expected, $transformed);
}

#[Test]
public function it_preserves_reorder_array_when_moving_custom_section_to_first_position()
{
// This test reproduces a bug where moving a custom section to the first position
// (after "Top Level") results in the reorder array being dropped during the minifying
// process, even though it is necessary to maintain the custom section's position.

$transformed = $this->transform([
['display_original' => 'Top Level'],
[
'display' => '⭐ Favorites',
'action' => '@create',
'items' => [
[
'id' => 'favorites::edit_homepage',
'manipulations' => [
'action' => '@create',
'display' => 'Edit homepage',
'url' => '/cp',
'icon' => 'edit',
],
],
],
],
['display_original' => 'Content'],
['display_original' => 'Fields'],
['display_original' => 'Tools'],
['display_original' => 'Settings'],
['display_original' => 'Users'],
]);

$expected = [
'reorder' => [
'favorites',
],
'sections' => [
'favorites' => [
'display' => '⭐ Favorites',
'action' => '@create',
'items' => [
'favorites::edit_homepage' => [
'action' => '@create',
'display' => 'Edit homepage',
'url' => '/cp',
'icon' => 'edit',
],
],
],
],
];

$this->assertEquals($expected, $transformed);
}
}

class IncrementalIdHasher
Expand Down