| 
10 | 10 | use Illuminate\Http\JsonResponse;  | 
11 | 11 | use Illuminate\Http\Request;  | 
12 | 12 | use Illuminate\Support\Arr;  | 
 | 13 | +use Illuminate\Support\Collection;  | 
13 | 14 | use Illuminate\Support\Facades\App;  | 
14 | 15 | use Illuminate\Support\Facades\Response as ResponseFactory;  | 
15 | 16 | use Illuminate\Support\Str;  | 
@@ -188,6 +189,7 @@ public function toResponse($request)  | 
188 | 189 |             $this->resolveMergeProps($request),  | 
189 | 190 |             $this->resolveDeferredProps($request),  | 
190 | 191 |             $this->resolveCacheDirections($request),  | 
 | 192 | +            $this->resolveScrollProps($request),  | 
191 | 193 |         );  | 
192 | 194 | 
 
  | 
193 | 195 |         if ($request->header(Header::INERTIA)) {  | 
@@ -371,13 +373,18 @@ public function resolveAlways(array $props): array  | 
371 | 373 |     public function resolvePropertyInstances(array $props, Request $request, ?string $parentKey = null): array  | 
372 | 374 |     {  | 
373 | 375 |         foreach ($props as $key => $value) {  | 
 | 376 | +            if ($value instanceof ScrollProp) {  | 
 | 377 | +                $value->configureMergeIntent($request);  | 
 | 378 | +            }  | 
 | 379 | + | 
374 | 380 |             $resolveViaApp = collect([  | 
375 | 381 |                 Closure::class,  | 
376 | 382 |                 LazyProp::class,  | 
377 | 383 |                 OptionalProp::class,  | 
378 | 384 |                 DeferProp::class,  | 
379 | 385 |                 AlwaysProp::class,  | 
380 | 386 |                 MergeProp::class,  | 
 | 387 | +                ScrollProp::class,  | 
381 | 388 |             ])->first(fn ($class) => $value instanceof $class);  | 
382 | 389 | 
 
  | 
383 | 390 |             if ($resolveViaApp) {  | 
@@ -439,45 +446,112 @@ public function resolveCacheDirections(Request $request): array  | 
439 | 446 |     }  | 
440 | 447 | 
 
  | 
441 | 448 |     /**  | 
442 |  | -     * Resolve merge props configuration for client-side prop merging.  | 
 | 449 | +     * Get the props that should be considered for merging based on the request headers.  | 
443 | 450 |      *  | 
444 |  | -     * @return array<string, mixed>  | 
 | 451 | +     * @return \Illuminate\Support\Collection<string, \Inertia\Mergeable>  | 
445 | 452 |      */  | 
446 |  | -    public function resolveMergeProps(Request $request): array  | 
 | 453 | +    protected function getMergePropsForRequest(Request $request): Collection  | 
447 | 454 |     {  | 
448 | 455 |         $resetProps = array_filter(explode(',', $request->header(Header::RESET, '')));  | 
449 | 456 |         $onlyProps = array_filter(explode(',', $request->header(Header::PARTIAL_ONLY, '')));  | 
450 | 457 |         $exceptProps = array_filter(explode(',', $request->header(Header::PARTIAL_EXCEPT, '')));  | 
451 | 458 | 
 
  | 
452 |  | -        $mergeProps = collect($this->props)  | 
 | 459 | +        return collect($this->props)  | 
453 | 460 |             ->filter(fn ($prop) => $prop instanceof Mergeable)  | 
454 |  | -            ->filter(fn ($prop) => $prop->shouldMerge())  | 
455 |  | -            ->reject(fn ($_, $key) => in_array($key, $resetProps))  | 
456 |  | -            ->filter(fn ($_, $key) => count($onlyProps) === 0 || in_array($key, $onlyProps))  | 
457 |  | -            ->reject(fn ($_, $key) => in_array($key, $exceptProps));  | 
 | 461 | +            ->filter(fn (Mergeable $prop) => $prop->shouldMerge())  | 
 | 462 | +            ->reject(fn ($_, string $key) => in_array($key, $resetProps))  | 
 | 463 | +            ->filter(fn ($_, string $key) => count($onlyProps) === 0 || in_array($key, $onlyProps))  | 
 | 464 | +            ->reject(fn ($_, string $key) => in_array($key, $exceptProps));  | 
 | 465 | +    }  | 
 | 466 | + | 
 | 467 | +    /**  | 
 | 468 | +     * Resolve merge props configuration for client-side prop merging.  | 
 | 469 | +     *  | 
 | 470 | +     * @return array<string, mixed>  | 
 | 471 | +     */  | 
 | 472 | +    public function resolveMergeProps(Request $request): array  | 
 | 473 | +    {  | 
 | 474 | +        $mergeProps = $this->getMergePropsForRequest($request);  | 
458 | 475 | 
 
  | 
459 |  | -        $deepMergeProps = $mergeProps  | 
460 |  | -            ->filter(fn ($prop) => $prop->shouldDeepMerge())  | 
461 |  | -            ->keys();  | 
 | 476 | +        return array_filter([  | 
 | 477 | +            'mergeProps' => $this->resolveAppendMergeProps($mergeProps),  | 
 | 478 | +            'prependProps' => $this->resolvePrependMergeProps($mergeProps),  | 
 | 479 | +            'deepMergeProps' => $this->resolveDeepMergeProps($mergeProps),  | 
 | 480 | +            'matchPropsOn' => $this->resolveMergeMatchingKeys($mergeProps),  | 
 | 481 | +        ], fn ($prop) => count($prop) > 0);  | 
 | 482 | +    }  | 
462 | 483 | 
 
  | 
463 |  | -        $matchPropsOn = $mergeProps  | 
 | 484 | +    /**  | 
 | 485 | +     * Resolve props that should be appended during merging.  | 
 | 486 | +     *  | 
 | 487 | +     * @param  \Illuminate\Support\Collection<string, \Inertia\Mergeable>  $mergeProps  | 
 | 488 | +     * @return array<int, string>  | 
 | 489 | +     */  | 
 | 490 | +    protected function resolveAppendMergeProps(Collection $mergeProps): array  | 
 | 491 | +    {  | 
 | 492 | +        [$rootAppendProps, $nestedAppendProps] = $mergeProps  | 
 | 493 | +            ->reject(fn (Mergeable $prop) => $prop->shouldDeepMerge())  | 
 | 494 | +            ->partition(fn (Mergeable $prop) => $prop->appendsAtRoot());  | 
 | 495 | + | 
 | 496 | +        return $nestedAppendProps  | 
 | 497 | +            ->flatMap(fn (Mergeable $prop, string $key) => collect($prop->appendsAtPaths())->map(fn ($path) => $key.'.'.$path))  | 
 | 498 | +            ->merge($rootAppendProps->keys())  | 
 | 499 | +            ->unique()  | 
 | 500 | +            ->values()  | 
 | 501 | +            ->toArray();  | 
 | 502 | +    }  | 
 | 503 | + | 
 | 504 | +    /**  | 
 | 505 | +     * Resolve props that should be prepended during merging.  | 
 | 506 | +     *  | 
 | 507 | +     * @param  \Illuminate\Support\Collection<string, \Inertia\Mergeable>  $mergeProps  | 
 | 508 | +     * @return array<int, string>  | 
 | 509 | +     */  | 
 | 510 | +    protected function resolvePrependMergeProps(Collection $mergeProps): array  | 
 | 511 | +    {  | 
 | 512 | +        [$rootPrependProps, $nestedPrependProps] = $mergeProps  | 
 | 513 | +            ->reject(fn (Mergeable $prop) => $prop->shouldDeepMerge())  | 
 | 514 | +            ->partition(fn (Mergeable $prop) => $prop->prependsAtRoot());  | 
 | 515 | + | 
 | 516 | +        return $nestedPrependProps  | 
 | 517 | +            ->flatMap(fn (Mergeable $prop, string $key) => collect($prop->prependsAtPaths())->map(fn ($path) => $key.'.'.$path))  | 
 | 518 | +            ->merge($rootPrependProps->keys())  | 
 | 519 | +            ->unique()  | 
 | 520 | +            ->values()  | 
 | 521 | +            ->toArray();  | 
 | 522 | +    }  | 
 | 523 | + | 
 | 524 | +    /**  | 
 | 525 | +     * Resolve props that should be deep merged.  | 
 | 526 | +     *  | 
 | 527 | +     * @param  \Illuminate\Support\Collection<string, \Inertia\Mergeable>  $mergeProps  | 
 | 528 | +     * @return array<int, string>  | 
 | 529 | +     */  | 
 | 530 | +    protected function resolveDeepMergeProps(Collection $mergeProps): array  | 
 | 531 | +    {  | 
 | 532 | +        return $mergeProps  | 
 | 533 | +            ->filter(fn (Mergeable $prop) => $prop->shouldDeepMerge())  | 
 | 534 | +            ->keys()  | 
 | 535 | +            ->toArray();  | 
 | 536 | +    }  | 
 | 537 | + | 
 | 538 | +    /**  | 
 | 539 | +     * Resolve the matching keys for merge props.  | 
 | 540 | +     *  | 
 | 541 | +     * @param  \Illuminate\Support\Collection<string, \Inertia\Mergeable>  $mergeProps  | 
 | 542 | +     * @return array<int, string>  | 
 | 543 | +     */  | 
 | 544 | +    protected function resolveMergeMatchingKeys(Collection $mergeProps): array  | 
 | 545 | +    {  | 
 | 546 | +        return $mergeProps  | 
464 | 547 |             ->map(function (Mergeable $prop, $key) {  | 
465 | 548 |                 return collect($prop->matchesOn())  | 
466 | 549 |                     ->map(fn ($strategy) => $key.'.'.$strategy)  | 
467 | 550 |                     ->toArray();  | 
468 | 551 |             })  | 
469 | 552 |             ->flatten()  | 
470 |  | -            ->values();  | 
471 |  | - | 
472 |  | -        $mergeProps = $mergeProps  | 
473 |  | -            ->filter(fn ($prop) => ! $prop->shouldDeepMerge())  | 
474 |  | -            ->keys();  | 
475 |  | - | 
476 |  | -        return array_filter([  | 
477 |  | -            'mergeProps' => $mergeProps->toArray(),  | 
478 |  | -            'deepMergeProps' => $deepMergeProps->toArray(),  | 
479 |  | -            'matchPropsOn' => $matchPropsOn->toArray(),  | 
480 |  | -        ], fn ($prop) => count($prop) > 0);  | 
 | 553 | +            ->values()  | 
 | 554 | +            ->toArray();  | 
481 | 555 |     }  | 
482 | 556 | 
 
  | 
483 | 557 |     /**  | 
@@ -508,6 +582,20 @@ public function resolveDeferredProps(Request $request): array  | 
508 | 582 |         return $deferredProps->isNotEmpty() ? ['deferredProps' => $deferredProps->toArray()] : [];  | 
509 | 583 |     }  | 
510 | 584 | 
 
  | 
 | 585 | +    /**  | 
 | 586 | +     * Resolve scroll props configuration for client-side infinite scrolling.  | 
 | 587 | +     *  | 
 | 588 | +     * @return array<string, mixed>  | 
 | 589 | +     */  | 
 | 590 | +    public function resolveScrollProps(Request $request): array  | 
 | 591 | +    {  | 
 | 592 | +        $scrollProps = $this->getMergePropsForRequest($request)  | 
 | 593 | +            ->filter(fn (Mergeable $prop) => $prop instanceof ScrollProp)  | 
 | 594 | +            ->mapWithKeys(fn (ScrollProp $prop, string $key) => [$key => $prop->metadata()]);  | 
 | 595 | + | 
 | 596 | +        return $scrollProps->isNotEmpty() ? ['scrollProps' => $scrollProps->toArray()] : [];  | 
 | 597 | +    }  | 
 | 598 | + | 
511 | 599 |     /**  | 
512 | 600 |      * Determine if the request is a partial request.  | 
513 | 601 |      */  | 
 | 
0 commit comments