Make pane expansion public

Relnote: Introduce pane expansion APIs to public
Test: existing test
Bug: 327637983
Change-Id: I301d6a9efc34f352707aaabe45dca05ed40a9f97
diff --git a/compose/material3/adaptive/adaptive-layout/api/current.txt b/compose/material3/adaptive/adaptive-layout/api/current.txt
index 1305c09..c69428b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/current.txt
@@ -36,7 +36,7 @@
 
   public final class ListDetailPaneScaffoldKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   public final class ListDetailPaneScaffoldRole {
@@ -60,6 +60,39 @@
     property public final String Hidden;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public abstract sealed class PaneExpansionAnchor {
+  }
+
+  public static final class PaneExpansionAnchor.Offset extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Offset(float offset);
+    method public float getOffset();
+    property public final float offset;
+  }
+
+  public static final class PaneExpansionAnchor.Proportion extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Proportion(@FloatRange(from=0.0, to=1.0) float proportion);
+    method public float getProportion();
+    property public final float proportion;
+  }
+
+  public final class PaneExpansionDragHandleKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void PaneExpansionDragHandle(androidx.compose.material3.adaptive.layout.PaneExpansionState state, long color, optional androidx.compose.ui.Modifier modifier);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class PaneExpansionState implements androidx.compose.foundation.gestures.DraggableState {
+    method public void clear();
+    method public void dispatchRawDelta(float delta);
+    method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public boolean isUnspecified();
+    method public void setFirstPaneProportion(@FloatRange(from=0.0, to=1.0) float firstPaneProportion);
+    method public void setFirstPaneWidth(int firstPaneWidth);
+    field public static final androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion Companion;
+    field public static final int Unspecified = -1; // 0xffffffff
+  }
+
+  public static final class PaneExpansionState.Companion {
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public sealed interface PaneExpansionStateKey {
     field public static final androidx.compose.material3.adaptive.layout.PaneExpansionStateKey.Companion Companion;
   }
@@ -74,6 +107,11 @@
     property public abstract androidx.compose.material3.adaptive.layout.PaneExpansionStateKey paneExpansionStateKey;
   }
 
+  public final class PaneExpansionStateKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(optional androidx.compose.material3.adaptive.layout.PaneExpansionStateKey key, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider keyProvider, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+  }
+
   public final class PaneKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
   }
@@ -117,7 +155,7 @@
 
   public final class SupportingPaneScaffoldKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
diff --git a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
index 1305c09..c69428b 100644
--- a/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-layout/api/restricted_current.txt
@@ -36,7 +36,7 @@
 
   public final class ListDetailPaneScaffoldKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void ListDetailPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   public final class ListDetailPaneScaffoldRole {
@@ -60,6 +60,39 @@
     property public final String Hidden;
   }
 
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public abstract sealed class PaneExpansionAnchor {
+  }
+
+  public static final class PaneExpansionAnchor.Offset extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Offset(float offset);
+    method public float getOffset();
+    property public final float offset;
+  }
+
+  public static final class PaneExpansionAnchor.Proportion extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor {
+    ctor public PaneExpansionAnchor.Proportion(@FloatRange(from=0.0, to=1.0) float proportion);
+    method public float getProportion();
+    property public final float proportion;
+  }
+
+  public final class PaneExpansionDragHandleKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void PaneExpansionDragHandle(androidx.compose.material3.adaptive.layout.PaneExpansionState state, long color, optional androidx.compose.ui.Modifier modifier);
+  }
+
+  @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Stable public final class PaneExpansionState implements androidx.compose.foundation.gestures.DraggableState {
+    method public void clear();
+    method public void dispatchRawDelta(float delta);
+    method public suspend Object? drag(androidx.compose.foundation.MutatePriority dragPriority, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.gestures.DragScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,? extends java.lang.Object?> block, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public boolean isUnspecified();
+    method public void setFirstPaneProportion(@FloatRange(from=0.0, to=1.0) float firstPaneProportion);
+    method public void setFirstPaneWidth(int firstPaneWidth);
+    field public static final androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion Companion;
+    field public static final int Unspecified = -1; // 0xffffffff
+  }
+
+  public static final class PaneExpansionState.Companion {
+  }
+
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Immutable public sealed interface PaneExpansionStateKey {
     field public static final androidx.compose.material3.adaptive.layout.PaneExpansionStateKey.Companion Companion;
   }
@@ -74,6 +107,11 @@
     property public abstract androidx.compose.material3.adaptive.layout.PaneExpansionStateKey paneExpansionStateKey;
   }
 
+  public final class PaneExpansionStateKt {
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(optional androidx.compose.material3.adaptive.layout.PaneExpansionStateKey key, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static androidx.compose.material3.adaptive.layout.PaneExpansionState rememberPaneExpansionState(androidx.compose.material3.adaptive.layout.PaneExpansionStateKeyProvider keyProvider, optional java.util.List<? extends androidx.compose.material3.adaptive.layout.PaneExpansionAnchor> anchors);
+  }
+
   public final class PaneKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void AnimatedPane(androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.AnimatedPaneScope,kotlin.Unit> content);
   }
@@ -117,7 +155,7 @@
 
   public final class SupportingPaneScaffoldKt {
     method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldState scaffoldState, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void SupportingPaneScaffold(androidx.compose.material3.adaptive.layout.PaneScaffoldDirective directive, androidx.compose.material3.adaptive.layout.ThreePaneScaffoldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi public final class SupportingPaneScaffoldRole {
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
index d8bfa84..36723c0 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldScreenshotTest.kt
@@ -116,8 +116,9 @@
     fun threePaneScaffold_paneExpansion_fixedFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 412.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -134,7 +135,7 @@
     fun threePaneScaffold_paneExpansion_zeroFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth = 0
+            mockPaneExpansionState.setFirstPaneWidth(0)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -151,8 +152,9 @@
     fun threePaneScaffold_paneExpansion_overflowFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 1024.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -169,7 +171,7 @@
     fun threePaneScaffold_paneExpansion_fixedFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0.5f
+            mockPaneExpansionState.setFirstPaneProportion(0.5f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -186,7 +188,7 @@
     fun threePaneScaffold_paneExpansion_zeroFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0f
+            mockPaneExpansionState.setFirstPaneProportion(0f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -203,7 +205,7 @@
     fun threePaneScaffold_paneExpansion_smallFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0.05f
+            mockPaneExpansionState.setFirstPaneProportion(0.05f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -220,7 +222,7 @@
     fun threePaneScaffold_paneExpansion_largeFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 0.95f
+            mockPaneExpansionState.setFirstPaneProportion(0.95f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -237,7 +239,7 @@
     fun threePaneScaffold_paneExpansion_fullFirstPanePercentage() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPanePercentage = 1.0f
+            mockPaneExpansionState.setFirstPaneProportion(1f)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState)
         }
 
@@ -254,8 +256,9 @@
     fun threePaneScaffold_paneExpansionWithDragHandle_fixedFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 412.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) }
         }
 
@@ -272,7 +275,7 @@
     fun threePaneScaffold_paneExpansionWithDragHandle_zeroFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth = 0
+            mockPaneExpansionState.setFirstPaneWidth(0)
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) }
         }
 
@@ -289,8 +292,9 @@
     fun threePaneScaffold_paneExpansionWithDragHandle_overflowFirstPaneWidth() {
         rule.setContentWithSimulatedSize(simulatedWidth = 1024.dp, simulatedHeight = 800.dp) {
             val mockPaneExpansionState = PaneExpansionState()
-            mockPaneExpansionState.firstPaneWidth =
+            mockPaneExpansionState.setFirstPaneWidth(
                 with(LocalDensity.current) { 1024.dp.roundToPx() }
+            )
             SampleThreePaneScaffoldWithPaneExpansion(mockPaneExpansionState) { MockDragHandle(it) }
         }
 
diff --git a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
index 860b4dc..957275d 100644
--- a/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/androidInstrumentedTest/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffoldTest.kt
@@ -270,9 +270,9 @@
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 private val MockPaneExpansionAnchors =
     listOf(
-        PaneExpansionAnchor(percentage = 0),
-        PaneExpansionAnchor(startOffset = MockPaneExpansionMiddleAnchor),
-        PaneExpansionAnchor(percentage = 100),
+        PaneExpansionAnchor.Proportion(0f),
+        PaneExpansionAnchor.Offset(MockPaneExpansionMiddleAnchor),
+        PaneExpansionAnchor.Proportion(1f),
     )
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
index 2ad02fe..0403a04 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ListDetailPaneScaffold.kt
@@ -47,6 +47,13 @@
  * @param extraPane the extra pane of the scaffold, which is supposed to hold any supplementary info
  *   besides the list and the detail panes, for example, a task list or a mini-calendar view of a
  *   mail app. See [ListDetailPaneScaffoldRole.Extra].
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -57,6 +64,8 @@
     detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value),
 ) {
     ThreePaneScaffold(
         modifier = modifier.fillMaxSize(),
@@ -65,6 +74,8 @@
         paneOrder = ListDetailPaneScaffoldDefaults.PaneOrder,
         secondaryPane = listPane,
         tertiaryPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
         primaryPane = detailPane
     )
 }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
index 0a83188..8398174 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionDragHandle.kt
@@ -30,14 +30,20 @@
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.unit.dp
 
+/**
+ * A default, basic non-customizable implementation of pane expansion drag handle. Note that this
+ * implementation will be deprecated in favor of the corresponding Material3 implementation when
+ * it's available.
+ */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
-// TODO(b/327637983): Implement this as a customizable component.
-internal fun PaneExpansionDragHandle(
+// TODO(b/327637983): Implement this as a customizable component as a Material3 component.
+fun PaneExpansionDragHandle(
     state: PaneExpansionState,
     color: Color,
     modifier: Modifier = Modifier,
 ) {
+    // TODO (conradchen): support drag handle motion during scaffold and expansion state change
     Box(
         modifier = modifier.paneExpansionDragHandle(state).size(24.dp, 48.dp),
         contentAlignment = Alignment.Center
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt
index 7a0d154..df95da6 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/PaneExpansionState.kt
@@ -16,18 +16,17 @@
 
 package androidx.compose.material3.adaptive.layout
 
-import androidx.annotation.IntRange
+import androidx.annotation.FloatRange
 import androidx.annotation.VisibleForTesting
 import androidx.collection.IntList
 import androidx.collection.MutableIntList
-import androidx.collection.emptyIntList
 import androidx.compose.animation.core.animate
 import androidx.compose.foundation.MutatePriority
 import androidx.compose.foundation.MutatorMutex
 import androidx.compose.foundation.gestures.DragScope
 import androidx.compose.foundation.gestures.DraggableState
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
-import androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion.UnspecifiedWidth
+import androidx.compose.material3.adaptive.layout.PaneExpansionState.Companion.Unspecified
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -40,8 +39,8 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.isSpecified
 import kotlin.math.abs
+import kotlin.math.roundToInt
 import kotlinx.coroutines.coroutineScope
 
 /**
@@ -96,7 +95,7 @@
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
-internal fun rememberPaneExpansionState(
+fun rememberPaneExpansionState(
     keyProvider: PaneExpansionStateKeyProvider,
     anchors: List<PaneExpansionAnchor> = emptyList()
 ): PaneExpansionState = rememberPaneExpansionState(keyProvider.paneExpansionStateKey, anchors)
@@ -112,7 +111,7 @@
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
-internal fun rememberPaneExpansionState(
+fun rememberPaneExpansionState(
     key: PaneExpansionStateKey = PaneExpansionStateKey.Default,
     anchors: List<PaneExpansionAnchor> = emptyList()
 ): PaneExpansionState {
@@ -129,32 +128,35 @@
     }
 }
 
+/**
+ * This class manages the pane expansion state for pane scaffolds. By providing and modifying an
+ * instance of this class, you can specify the expanded panes' expansion width or percentage when
+ * pane scaffold is displaying a dual-pane layout.
+ *
+ * This class also serves as the [DraggableState] of pane expansion handle. When a handle
+ * implementation is provided to the associated pane scaffold, the scaffold will use
+ * [PaneExpansionState] to store and manage dragging and anchoring of the handle, and thus the pane
+ * expansion state.
+ */
 @ExperimentalMaterial3AdaptiveApi
 @Stable
-internal class PaneExpansionState
+class PaneExpansionState
 internal constructor(
     // TODO(conradchen): Handle state change during dragging and settling
     data: PaneExpansionStateData = PaneExpansionStateData(),
-    internal var anchors: List<PaneExpansionAnchor> = emptyList()
+    anchors: List<PaneExpansionAnchor> = emptyList()
 ) : DraggableState {
 
-    var firstPaneWidth: Int
-        set(value) {
-            data.firstPanePercentageState = Float.NaN
-            data.currentDraggingOffsetState = UnspecifiedWidth
-            val coercedValue = value.coerceIn(0, maxExpansionWidth)
-            data.firstPaneWidthState = coercedValue
-        }
-        get() = data.firstPaneWidthState
+    internal val firstPaneWidth
+        get() =
+            if (maxExpansionWidth == Unspecified || data.firstPaneWidthState == Unspecified) {
+                Unspecified
+            } else {
+                data.firstPaneWidthState.coerceIn(0, maxExpansionWidth)
+            }
 
-    var firstPanePercentage: Float
-        set(value) {
-            require(value in 0f..1f) { "Percentage value needs to be in [0, 1]" }
-            data.firstPaneWidthState = UnspecifiedWidth
-            data.currentDraggingOffsetState = UnspecifiedWidth
-            data.firstPanePercentageState = value
-        }
-        get() = data.firstPanePercentageState
+    internal val firstPaneProportion: Float
+        get() = data.firstPaneProportionState
 
     internal var currentDraggingOffset
         get() = data.currentDraggingOffsetState
@@ -179,16 +181,18 @@
         get() = isDragging || isSettling
 
     @VisibleForTesting
-    internal var maxExpansionWidth = 0
+    internal var maxExpansionWidth by mutableIntStateOf(Unspecified)
         private set
 
     // Use this field to store the dragging offset decided by measuring instead of dragging to
     // prevent redundant re-composition.
     @VisibleForTesting
-    internal var currentMeasuredDraggingOffset = UnspecifiedWidth
+    internal var currentMeasuredDraggingOffset = Unspecified
         private set
 
-    private var anchorPositions: IntList = emptyIntList()
+    internal var anchors: List<PaneExpansionAnchor> by mutableStateOf(anchors)
+
+    private lateinit var measuredDensity: Density
 
     private val dragScope: DragScope =
         object : DragScope {
@@ -197,13 +201,14 @@
 
     private val dragMutex = MutatorMutex()
 
+    /** Returns `true` if none of [firstPaneWidth] or [firstPaneProportion] has been set. */
     fun isUnspecified(): Boolean =
-        firstPaneWidth == UnspecifiedWidth &&
-            firstPanePercentage.isNaN() &&
-            currentDraggingOffset == UnspecifiedWidth
+        firstPaneWidth == Unspecified &&
+            firstPaneProportion.isNaN() &&
+            currentDraggingOffset == Unspecified
 
     override fun dispatchRawDelta(delta: Float) {
-        if (currentMeasuredDraggingOffset == UnspecifiedWidth) {
+        if (currentMeasuredDraggingOffset == Unspecified) {
             return
         }
         currentDraggingOffset = (currentMeasuredDraggingOffset + delta).toInt()
@@ -216,11 +221,45 @@
             isDragging = false
         }
 
-    /** Clears any existing expansion state. */
+    /**
+     * Set the width of the first expanded pane in the layout. When the set value gets applied, it
+     * will be coerced within the range of `[0, the full displayable width of the layout]`.
+     *
+     * Note that setting this value will reset the first pane proportion previously set via
+     * [setFirstPaneProportion] or the current dragging result if there's any. Also if user drags
+     * the pane after setting the first pane width, the user dragging result will take the priority
+     * over this set value when rendering panes, but the set value will be saved.
+     */
+    fun setFirstPaneWidth(firstPaneWidth: Int) {
+        data.firstPaneProportionState = Float.NaN
+        data.currentDraggingOffsetState = Unspecified
+        data.firstPaneWidthState = firstPaneWidth
+    }
+
+    /**
+     * Set the proportion of the first expanded pane in the layout. The set value needs to be within
+     * the range of `[0f, 1f]`, otherwise the setter throws.
+     *
+     * Note that setting this value will reset the first pane width previously set via
+     * [setFirstPaneWidth] or the current dragging result if there's any. Also if user drags the
+     * pane after setting the first pane proportion, the user dragging result will take the priority
+     * over this set value when rendering panes, but the set value will be saved.
+     */
+    fun setFirstPaneProportion(@FloatRange(0.0, 1.0) firstPaneProportion: Float) {
+        require(firstPaneProportion in 0f..1f) { "Proportion value needs to be in [0f, 1f]" }
+        data.firstPaneWidthState = Unspecified
+        data.currentDraggingOffsetState = Unspecified
+        data.firstPaneProportionState = firstPaneProportion
+    }
+
+    /**
+     * Clears any previously set [firstPaneWidth] or [firstPaneProportion], as well as the user
+     * dragging result.
+     */
     fun clear() {
-        data.firstPaneWidthState = UnspecifiedWidth
-        data.firstPanePercentageState = Float.NaN
-        data.currentDraggingOffsetState = UnspecifiedWidth
+        data.firstPaneWidthState = Unspecified
+        data.firstPaneProportionState = Float.NaN
+        data.currentDraggingOffsetState = Unspecified
     }
 
     internal fun onMeasured(measuredWidth: Int, density: Density) {
@@ -228,10 +267,11 @@
             return
         }
         maxExpansionWidth = measuredWidth
-        if (firstPaneWidth != UnspecifiedWidth) {
-            firstPaneWidth = firstPaneWidth
+        measuredDensity = density
+        // To re-coerce the value
+        if (currentDraggingOffset != Unspecified) {
+            currentDraggingOffset = currentDraggingOffset
         }
-        anchorPositions = anchors.toPositions(measuredWidth, density)
     }
 
     internal fun onExpansionOffsetMeasured(measuredOffset: Int) {
@@ -239,7 +279,7 @@
     }
 
     internal suspend fun settleToAnchorIfNeeded(velocity: Float) {
-        val currentAnchorPositions = anchorPositions
+        val currentAnchorPositions = anchors.toPositions(maxExpansionWidth, measuredDensity)
         if (currentAnchorPositions.isEmpty()) {
             return
         }
@@ -283,41 +323,72 @@
         )
 
     companion object {
-        const val UnspecifiedWidth = -1
+        /** The constant value used to denote the pane expansion is not specified. */
+        const val Unspecified = -1
+
         private const val AnchoringVelocityThreshold = 200F
     }
 }
 
 @OptIn(ExperimentalMaterial3AdaptiveApi::class)
 internal class PaneExpansionStateData {
-    var firstPaneWidthState by mutableIntStateOf(UnspecifiedWidth)
-    var firstPanePercentageState by mutableFloatStateOf(Float.NaN)
-    var currentDraggingOffsetState by mutableIntStateOf(UnspecifiedWidth)
+    var firstPaneWidthState by mutableIntStateOf(Unspecified)
+    var firstPaneProportionState by mutableFloatStateOf(Float.NaN)
+    var currentDraggingOffsetState by mutableIntStateOf(Unspecified)
 }
 
+/**
+ * The implementations of this interface represent different types of anchors of pane expansion
+ * dragging. Setting up anchors when create [PaneExpansionState] will force user dragging to snap to
+ * the set anchors after user releases the drag.
+ */
 @ExperimentalMaterial3AdaptiveApi
-@Immutable
-internal class PaneExpansionAnchor
-private constructor(
-    val percentage: Int,
-    val startOffset: Dp // TODO(conradchen): confirm RTL support
-) {
-    constructor(@IntRange(0, 100) percentage: Int) : this(percentage, Dp.Unspecified)
+sealed class PaneExpansionAnchor private constructor() {
+    internal abstract fun positionIn(totalSizePx: Int, density: Density): Int
 
-    constructor(startOffset: Dp) : this(Int.MIN_VALUE, startOffset)
+    /**
+     * [PaneExpansionAnchor] implementation that specifies the anchor position in the proportion of
+     * the total size of the layout at the start side of the anchor.
+     *
+     * @property proportion the proportion of the layout at the start side of the anchor. layout.
+     */
+    class Proportion(@FloatRange(0.0, 1.0) val proportion: Float) : PaneExpansionAnchor() {
+        override fun positionIn(totalSizePx: Int, density: Density) =
+            (totalSizePx * proportion).roundToInt().coerceIn(0, totalSizePx)
 
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (other !is PaneExpansionAnchor) return false
-        if (percentage != other.percentage) return false
-        if (startOffset != other.startOffset) return false
-        return true
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Proportion) return false
+            return proportion == other.proportion
+        }
+
+        override fun hashCode(): Int {
+            return proportion.hashCode()
+        }
     }
 
-    override fun hashCode(): Int {
-        var result = percentage
-        result = 31 * result + startOffset.hashCode()
-        return result
+    /**
+     * [PaneExpansionAnchor] implementation that specifies the anchor position in the offset in
+     * [Dp]. If a positive value is provided, the offset will be treated as a start offset, on the
+     * other hand, if a negative value is provided, the absolute value of the provided offset will
+     * be used as an end offset. For example, if -150.dp is provided, the resulted anchor will be at
+     * the position that is 150dp away from the end side of the associated layout.
+     *
+     * @property offset the offset of the anchor in [Dp].
+     */
+    class Offset(val offset: Dp) : PaneExpansionAnchor() {
+        override fun positionIn(totalSizePx: Int, density: Density) =
+            with(density) { offset.toPx() }.toInt().let { if (it < 0) totalSizePx + it else it }
+
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (other !is Offset) return false
+            return offset == other.offset
+        }
+
+        override fun hashCode(): Int {
+            return offset.hashCode()
+        }
     }
 }
 
@@ -328,19 +399,7 @@
 ): IntList {
     val anchors = MutableIntList(size)
     @Suppress("ListIterator") // Not necessarily a random-accessible list
-    forEach { anchor ->
-        if (anchor.startOffset.isSpecified) {
-            val position =
-                with(density) { anchor.startOffset.toPx() }
-                    .toInt()
-                    .let { if (it < 0) maxExpansionWidth + it else it }
-            if (position in 0..maxExpansionWidth) {
-                anchors.add(position)
-            }
-        } else {
-            anchors.add(maxExpansionWidth * anchor.percentage / 100)
-        }
-    }
+    forEach { anchor -> anchors.add(anchor.positionIn(maxExpansionWidth, density)) }
     anchors.sort()
     return anchors
 }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
index 700e186..40d8c68 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/SupportingPaneScaffold.kt
@@ -41,6 +41,13 @@
  * @param extraPane the extra pane of the scaffold, which is supposed to hold any additional content
  *   besides the main and the supporting panes, for example, a styling panel in a doc app. See
  *   [SupportingPaneScaffoldRole.Extra].
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -51,6 +58,8 @@
     supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(value),
 ) {
     ThreePaneScaffold(
         modifier = modifier.fillMaxSize(),
@@ -59,6 +68,8 @@
         paneOrder = SupportingPaneScaffoldDefaults.PaneOrder,
         secondaryPane = supportingPane,
         tertiaryPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
         primaryPane = mainPane
     )
 }
diff --git a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
index b99b9f5..d85c569 100644
--- a/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
+++ b/compose/material3/adaptive/adaptive-layout/src/commonMain/kotlin/androidx/compose/material3/adaptive/layout/ThreePaneScaffold.kt
@@ -314,9 +314,7 @@
 
             if (!paneExpansionState.isUnspecified() && visiblePanes.size == 2) {
                 // Pane expansion should override everything
-                if (
-                    paneExpansionState.currentDraggingOffset != PaneExpansionState.UnspecifiedWidth
-                ) {
+                if (paneExpansionState.currentDraggingOffset != PaneExpansionState.Unspecified) {
                     // Respect the user dragging result if there's any
                     val halfSpacerSize = verticalSpacerSize / 2
                     if (paneExpansionState.currentDraggingOffset <= halfSpacerSize) {
@@ -366,7 +364,7 @@
                     val availableWidth = constraints.maxWidth
                     if (
                         paneExpansionState.firstPaneWidth == 0 ||
-                            paneExpansionState.firstPanePercentage == 0f
+                            paneExpansionState.firstPaneProportion == 0f
                     ) {
                         measureAndPlacePaneWithLocalBounds(
                             outerBounds,
@@ -375,7 +373,7 @@
                         )
                     } else if (
                         paneExpansionState.firstPaneWidth >= availableWidth - verticalSpacerSize ||
-                            paneExpansionState.firstPanePercentage >= 1f
+                            paneExpansionState.firstPaneProportion >= 1f
                     ) {
                         measureAndPlacePaneWithLocalBounds(
                             outerBounds,
@@ -385,12 +383,11 @@
                     } else {
                         val firstPaneWidth =
                             if (
-                                paneExpansionState.firstPaneWidth !=
-                                    PaneExpansionState.UnspecifiedWidth
+                                paneExpansionState.firstPaneWidth != PaneExpansionState.Unspecified
                             ) {
                                 paneExpansionState.firstPaneWidth
                             } else {
-                                (paneExpansionState.firstPanePercentage *
+                                (paneExpansionState.firstPaneProportion *
                                         (availableWidth - verticalSpacerSize))
                                     .toInt()
                             }
@@ -505,7 +502,7 @@
                     if (
                         !paneExpansionState.isDraggingOrSettling ||
                             paneExpansionState.currentDraggingOffset ==
-                                PaneExpansionState.UnspecifiedWidth
+                                PaneExpansionState.Unspecified
                     ) {
                         val spacerMiddleOffset =
                             getSpacerMiddleOffsetX(visiblePanes[0], visiblePanes[1])
@@ -524,7 +521,7 @@
                     handleOffsetX
                 )
             } else if (!isLookingAhead) {
-                paneExpansionState.onExpansionOffsetMeasured(PaneExpansionState.UnspecifiedWidth)
+                paneExpansionState.onExpansionOffsetMeasured(PaneExpansionState.Unspecified)
             }
 
             // Place the hidden panes to ensure a proper motion at the AnimatedVisibility,
@@ -703,7 +700,7 @@
         maxHandleWidth: Int,
         offsetX: Int
     ) {
-        if (offsetX == PaneExpansionState.UnspecifiedWidth) {
+        if (offsetX == PaneExpansionState.Unspecified) {
             return
         }
         val placeables =
@@ -724,7 +721,7 @@
                 (paneLeft.placedPositionX + paneLeft.measuredWidth + paneRight.placedPositionX) / 2
             paneLeft.measuredAndPlaced -> paneLeft.placedPositionX + paneLeft.measuredWidth
             paneRight.measuredAndPlaced -> 0
-            else -> PaneExpansionState.UnspecifiedWidth
+            else -> PaneExpansionState.Unspecified
         }
     }
 }
diff --git a/compose/material3/adaptive/adaptive-navigation/api/current.txt b/compose/material3/adaptive/adaptive-navigation/api/current.txt
index c1f74e3..962ebd8 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/current.txt
@@ -2,8 +2,8 @@
 package androidx.compose.material3.adaptive.navigation {
 
   public final class AndroidThreePaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
diff --git a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
index c1f74e3..962ebd8 100644
--- a/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
+++ b/compose/material3/adaptive/adaptive-navigation/api/restricted_current.txt
@@ -2,8 +2,8 @@
 package androidx.compose.material3.adaptive.navigation {
 
   public final class AndroidThreePaneScaffold_androidKt {
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
-    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableListDetailPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> listPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> detailPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
+    method @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @androidx.compose.runtime.Composable public static void NavigableSupportingPaneScaffold(androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator<java.lang.Object> navigator, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> mainPane, kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit> supportingPane, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope,kotlin.Unit>? extraPane, optional String defaultBackBehavior, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.adaptive.layout.PaneExpansionState,kotlin.Unit>? paneExpansionDragHandle, optional androidx.compose.material3.adaptive.layout.PaneExpansionState paneExpansionState);
   }
 
   @SuppressCompatibility @androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi @kotlin.jvm.JvmInline public final value class BackNavigationBehavior {
diff --git a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
index 1efa9f4..e55f244 100644
--- a/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
+++ b/compose/material3/adaptive/adaptive-navigation/src/androidMain/kotlin/androidx/compose/material3/adaptive/navigation/AndroidThreePaneScaffold.android.kt
@@ -19,8 +19,11 @@
 import androidx.activity.compose.BackHandler
 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold as BaseListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle
+import androidx.compose.material3.adaptive.layout.PaneExpansionState
 import androidx.compose.material3.adaptive.layout.SupportingPaneScaffold as BaseSupportingPaneScaffold
 import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope
+import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 
@@ -41,6 +44,13 @@
  *   mail app. See [ListDetailPaneScaffoldRole.Extra].
  * @param defaultBackBehavior the default back navigation behavior when the system back event
  *   happens. See [BackNavigationBehavior] for the use cases of each behavior.
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -50,7 +60,9 @@
     detailPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange
+    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
 ) {
     // TODO(b/330584029): support predictive back
     BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) {
@@ -62,7 +74,9 @@
         value = navigator.scaffoldValue,
         detailPane = detailPane,
         listPane = listPane,
-        extraPane = extraPane
+        extraPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
     )
 }
 
@@ -82,6 +96,13 @@
  *   [SupportingPaneScaffoldRole.Extra].
  * @param defaultBackBehavior the default back navigation behavior when the system back event
  *   happens. See [BackNavigationBehavior] for the use cases of each behavior.
+ * @param paneExpansionDragHandle the pane expansion drag handle to let users be able to drag to
+ *   change pane expansion state. Note that by default this argument will be `null`, and there won't
+ *   be a drag handle rendered and users won't be able to drag to change the pane split. You can
+ *   provide a [PaneExpansionDragHandle] here as our sample suggests. On the other hand, even if
+ *   there's no drag handle, you can still modify [paneExpansionState] directly to apply pane
+ *   expansion.
+ * @param paneExpansionState the state object of pane expansion.
  */
 @ExperimentalMaterial3AdaptiveApi
 @Composable
@@ -91,7 +112,9 @@
     supportingPane: @Composable ThreePaneScaffoldScope.() -> Unit,
     modifier: Modifier = Modifier,
     extraPane: (@Composable ThreePaneScaffoldScope.() -> Unit)? = null,
-    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange
+    defaultBackBehavior: BackNavigationBehavior = BackNavigationBehavior.PopUntilContentChange,
+    paneExpansionDragHandle: (@Composable (PaneExpansionState) -> Unit)? = null,
+    paneExpansionState: PaneExpansionState = rememberPaneExpansionState(navigator.scaffoldValue),
 ) {
     // TODO(b/330584029): support predictive back
     BackHandler(enabled = navigator.canNavigateBack(defaultBackBehavior)) {
@@ -103,6 +126,8 @@
         value = navigator.scaffoldValue,
         mainPane = mainPane,
         supportingPane = supportingPane,
-        extraPane = extraPane
+        extraPane = extraPane,
+        paneExpansionDragHandle = paneExpansionDragHandle,
+        paneExpansionState = paneExpansionState,
     )
 }
diff --git a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
index 3a44336..77e13de 100644
--- a/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
+++ b/compose/material3/adaptive/samples/src/main/java/androidx/compose/material3/adaptive/samples/ThreePaneScaffoldSample.kt
@@ -54,6 +54,9 @@
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
 import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
 import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
+import androidx.compose.material3.adaptive.layout.PaneExpansionAnchor
+import androidx.compose.material3.adaptive.layout.PaneExpansionDragHandle
+import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState
 import androidx.compose.material3.adaptive.navigation.BackNavigationBehavior
 import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
 import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
@@ -174,6 +177,14 @@
                     Text("Extra")
                 }
             }
+        },
+        paneExpansionState =
+            rememberPaneExpansionState(
+                keyProvider = scaffoldNavigator.scaffoldValue,
+                anchors = PaneExpansionAnchors
+            ),
+        paneExpansionDragHandle = { state ->
+            PaneExpansionDragHandle(state = state, color = MaterialTheme.colorScheme.outline)
         }
     )
 }
@@ -362,3 +373,11 @@
         }
     }
 }
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+private val PaneExpansionAnchors =
+    listOf(
+        PaneExpansionAnchor.Proportion(0f),
+        PaneExpansionAnchor.Proportion(0.5f),
+        PaneExpansionAnchor.Proportion(1f),
+    )