Fix leading overscroll for RenderShrinkWrappingViewport (#87143)

diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart
index 1be3990..b7a8d6c 100644
--- a/packages/flutter/lib/src/rendering/viewport.dart
+++ b/packages/flutter/lib/src/rendering/viewport.dart
@@ -1924,7 +1924,7 @@
       child: firstChild,
       scrollOffset: math.max(0.0, correctedOffset),
       overlap: math.min(0.0, correctedOffset),
-      layoutOffset: 0.0,
+      layoutOffset: math.max(0.0, -correctedOffset),
       remainingPaintExtent: mainAxisExtent,
       mainAxisExtent: mainAxisExtent,
       crossAxisExtent: crossAxisExtent,
diff --git a/packages/flutter/test/rendering/viewport_test.dart b/packages/flutter/test/rendering/viewport_test.dart
index 4520de3..91cb528 100644
--- a/packages/flutter/test/rendering/viewport_test.dart
+++ b/packages/flutter/test/rendering/viewport_test.dart
@@ -1843,6 +1843,188 @@
     });
   });
 
+  Widget _buildShrinkWrap({
+    ScrollController? controller,
+    Axis scrollDirection = Axis.vertical,
+    ScrollPhysics? physics,
+  }) {
+    return Directionality(
+      textDirection: TextDirection.ltr,
+      child: MediaQuery(
+        data: const MediaQueryData(),
+        child: ListView.builder(
+          controller: controller,
+          physics: physics,
+          scrollDirection: scrollDirection,
+          shrinkWrap: true,
+          itemBuilder: (BuildContext context, int index) => SizedBox(height: 50, width: 50, child: Text('Item $index')),
+          itemCount: 20,
+          itemExtent: 50,
+        ),
+      ),
+    );
+  }
+
+  testWidgets('Shrinkwrap allows overscrolling on default platforms - vertical', (WidgetTester tester) async {
+    // Regression test for https://github.com/flutter/flutter/issues/10949
+    // Scrollables should overscroll by default on iOS and macOS
+    final  ScrollController controller = ScrollController();
+    await tester.pumpWidget(
+      _buildShrinkWrap(controller: controller),
+    );
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+    // Check overscroll at both ends
+    // Start
+    TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(0, 25));
+    await tester.pump();
+    expect(controller.offset, -25.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dy, 25.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+
+    // End
+    final double maxExtent = controller.position.maxScrollExtent;
+    controller.jumpTo(controller.position.maxScrollExtent);
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+
+    overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(0, -25));
+    await tester.pump();
+    expect(controller.offset, greaterThan(maxExtent));
+    expect(tester.getBottomLeft(find.text('Item 19')).dy, 375.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
+
+  testWidgets('Shrinkwrap allows overscrolling on default platforms - horizontal', (WidgetTester tester) async {
+    // Regression test for https://github.com/flutter/flutter/issues/10949
+    // Scrollables should overscroll by default on iOS and macOS
+    final  ScrollController controller = ScrollController();
+    await tester.pumpWidget(
+      _buildShrinkWrap(controller: controller, scrollDirection: Axis.horizontal),
+    );
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+    // Check overscroll at both ends
+    // Start
+    TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(25, 0));
+    await tester.pump();
+    expect(controller.offset, -25.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dx, 25.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+
+    // End
+    final double maxExtent = controller.position.maxScrollExtent;
+    controller.jumpTo(controller.position.maxScrollExtent);
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+
+    overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(-25, 0));
+    await tester.pump();
+    expect(controller.offset, greaterThan(maxExtent));
+    expect(tester.getTopRight(find.text('Item 19')).dx, 375.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
+
+  testWidgets('Shrinkwrap allows overscrolling per physics - vertical', (WidgetTester tester) async {
+    // Regression test for https://github.com/flutter/flutter/issues/10949
+    // Scrollables should overscroll when the scroll physics allow
+    final  ScrollController controller = ScrollController();
+    await tester.pumpWidget(
+      _buildShrinkWrap(controller: controller, physics: const BouncingScrollPhysics()),
+    );
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+    // Check overscroll at both ends
+    // Start
+    TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(0, 25));
+    await tester.pump();
+    expect(controller.offset, -25.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dy, 25.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dy, 0.0);
+
+    // End
+    final double maxExtent = controller.position.maxScrollExtent;
+    controller.jumpTo(controller.position.maxScrollExtent);
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+
+    overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(0, -25));
+    await tester.pump();
+    expect(controller.offset, greaterThan(maxExtent));
+    expect(tester.getBottomLeft(find.text('Item 19')).dy, 375.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getBottomLeft(find.text('Item 19')).dy, 400.0);
+  });
+
+  testWidgets('Shrinkwrap allows overscrolling per physics - horizontal', (WidgetTester tester) async {
+    // Regression test for https://github.com/flutter/flutter/issues/10949
+    // Scrollables should overscroll when the scroll physics allow
+    final  ScrollController controller = ScrollController();
+    await tester.pumpWidget(
+      _buildShrinkWrap(
+        controller: controller,
+        scrollDirection: Axis.horizontal,
+        physics: const BouncingScrollPhysics(),
+      ),
+    );
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+    // Check overscroll at both ends
+    // Start
+    TestGesture overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(25, 0));
+    await tester.pump();
+    expect(controller.offset, -25.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dx, 25.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, 0.0);
+    expect(tester.getTopLeft(find.text('Item 0')).dx, 0.0);
+
+    // End
+    final double maxExtent = controller.position.maxScrollExtent;
+    controller.jumpTo(controller.position.maxScrollExtent);
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+
+    overscrollGesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
+    await overscrollGesture.moveBy(const Offset(-25, 0));
+    await tester.pump();
+    expect(controller.offset, greaterThan(maxExtent));
+    expect(tester.getTopRight(find.text('Item 19')).dx, 375.0);
+    await overscrollGesture.up();
+    await tester.pumpAndSettle();
+    expect(controller.offset, maxExtent);
+    expect(tester.getTopRight(find.text('Item 19')).dx, 400.0);
+  });
+
   testWidgets('Handles infinite constraints when TargetPlatform is iOS or macOS', (WidgetTester tester) async {
     // regression test for https://github.com/flutter/flutter/issues/45866
     await tester.pumpWidget(