From f790dba99fa225ce7915951ded0e0b6599f75d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Thu, 21 May 2026 16:48:10 +0800 Subject: [PATCH 01/27] refactor(popup): reimplement `popup` Co-authored-by: Cursor --- tdesign-component/coverage/lcov.info | 2358 +++++++++-------- .../code/indexes._buildCustomIndexes.txt | 48 +- .../assets/code/indexes._buildOther.txt | 34 +- .../assets/code/indexes._buildSimple.txt | 32 +- .../code/popup._buildApiAutoCloseFalse.txt | 23 + .../code/popup._buildApiBackgroundColor.txt | 18 + .../popup._buildApiCancelBtnConfirmBtn.txt | 21 + .../popup._buildApiCancelConfirmBuilders.txt | 30 + .../popup._buildApiCancelConfirmWidgets.txt | 23 + .../code/popup._buildApiCloseBuilder.txt | 25 + .../popup._buildApiCloseOverlayClickFalse.txt | 17 + .../assets/code/popup._buildApiDuration.txt | 22 + .../code/popup._buildApiHeaderBuilder.txt | 26 + .../assets/code/popup._buildApiMarginTop.txt | 25 + .../code/popup._buildApiOnOverlayClick.txt | 23 + .../assets/code/popup._buildApiOverlay.txt | 18 + .../code/popup._buildApiShowOverlayFalse.txt | 26 + .../code/popup._buildApiTitleAlignLeft.txt | 20 + .../code/popup._buildApiTitleWidget.txt | 31 + .../code/popup._buildBottomActionBar.txt | 22 + .../code/popup._buildBottomTitleOnly.txt | 17 + .../assets/code/popup._buildCenterClose.txt | 27 + .../assets/code/popup._buildPopFromBottom.txt | 17 +- .../popup._buildPopFromBottomWithClose.txt | 24 - ...uildPopFromBottomWithCloseAndLeftTitle.txt | 26 - ...p._buildPopFromBottomWithCloseAndTitle.txt | 43 +- ...up._buildPopFromBottomWithCustomAction.txt | 37 + ...popup._buildPopFromBottomWithOperation.txt | 29 +- ...uildPopFromBottomWithOperationAndTitle.txt | 28 +- .../popup._buildPopFromBottomWithTitle.txt | 19 +- .../assets/code/popup._buildPopFromCenter.txt | 27 +- .../popup._buildPopFromCenterWithClose.txt | 27 +- ...opup._buildPopFromCenterWithUnderClose.txt | 34 +- .../assets/code/popup._buildPopFromLeft.txt | 16 +- .../assets/code/popup._buildPopFromRight.txt | 16 +- .../assets/code/popup._buildPopFromTop.txt | 25 +- .../lib/component_test/popup_test.dart | 57 +- .../example/lib/page/t_indexes_page.dart | 114 +- .../example/lib/page/t_picker_page.dart | 19 +- .../example/lib/page/t_popup_page.dart | 979 +++---- .../action_sheet/t_action_sheet.dart | 121 +- .../components/calendar/t_calendar_popup.dart | 48 +- .../lib/src/components/drawer/t_drawer.dart | 68 +- .../image_viewer/t_image_viewer.dart | 1 - .../lib/src/components/picker/t_picker.dart | 81 +- .../src/components/popup/_popup_header.dart | 243 ++ .../src/components/popup/_popup_layout.dart | 139 + .../src/components/popup/_popup_route.dart | 240 ++ .../src/components/popup/_popup_shell.dart | 111 + .../lib/src/components/popup/t_popup.dart | 357 +++ .../src/components/popup/t_popup_config.dart | 247 ++ .../src/components/popup/t_popup_panel.dart | 641 ----- .../src/components/popup/t_popup_route.dart | 300 --- .../src/components/popup/t_popup_tracker.dart | 29 + .../src/components/popup/t_popup_types.dart | 32 + .../lib/src/util/t_toolbar_pressable.dart | 96 + tdesign-component/lib/tdesign_flutter.dart | 4 +- .../test/helpers/popup_test_helpers.dart | 39 + .../test/helpers/popup_test_resource.dart | 190 ++ .../test/t_popup_config_test.dart | 125 + .../test/t_popup_coverage_test.dart | 374 +++ .../test/t_popup_layout_test.dart | 204 ++ tdesign-component/test/t_popup_test.dart | 793 ++++++ tdesign-site/src/popup/README.md | 2 +- 64 files changed, 5727 insertions(+), 3181 deletions(-) create mode 100644 tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiDuration.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiMarginTop.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiOverlay.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt create mode 100644 tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt create mode 100644 tdesign-component/example/assets/code/popup._buildBottomActionBar.txt create mode 100644 tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt create mode 100644 tdesign-component/example/assets/code/popup._buildCenterClose.txt delete mode 100644 tdesign-component/example/assets/code/popup._buildPopFromBottomWithClose.txt delete mode 100644 tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndLeftTitle.txt create mode 100644 tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt create mode 100644 tdesign-component/lib/src/components/popup/_popup_header.dart create mode 100644 tdesign-component/lib/src/components/popup/_popup_layout.dart create mode 100644 tdesign-component/lib/src/components/popup/_popup_route.dart create mode 100644 tdesign-component/lib/src/components/popup/_popup_shell.dart create mode 100644 tdesign-component/lib/src/components/popup/t_popup.dart create mode 100644 tdesign-component/lib/src/components/popup/t_popup_config.dart delete mode 100644 tdesign-component/lib/src/components/popup/t_popup_panel.dart delete mode 100644 tdesign-component/lib/src/components/popup/t_popup_route.dart create mode 100644 tdesign-component/lib/src/components/popup/t_popup_tracker.dart create mode 100644 tdesign-component/lib/src/components/popup/t_popup_types.dart create mode 100644 tdesign-component/lib/src/util/t_toolbar_pressable.dart create mode 100644 tdesign-component/test/helpers/popup_test_helpers.dart create mode 100644 tdesign-component/test/helpers/popup_test_resource.dart create mode 100644 tdesign-component/test/t_popup_config_test.dart create mode 100644 tdesign-component/test/t_popup_coverage_test.dart create mode 100644 tdesign-component/test/t_popup_layout_test.dart create mode 100644 tdesign-component/test/t_popup_test.dart diff --git a/tdesign-component/coverage/lcov.info b/tdesign-component/coverage/lcov.info index e24c20947..ca2c15ea0 100644 --- a/tdesign-component/coverage/lcov.info +++ b/tdesign-component/coverage/lcov.info @@ -1,36 +1,48 @@ -SF:lib/src/components/picker/t_picker_normalize.dart -DA:9,0 -DA:12,1 -DA:14,1 -DA:17,1 -DA:18,1 -DA:19,1 -DA:20,3 -DA:22,1 -DA:27,1 -DA:29,1 -DA:32,1 -DA:35,1 -DA:36,1 -DA:39,1 -DA:40,3 -DA:41,2 -DA:42,2 -DA:43,1 -DA:44,1 -DA:46,2 -DA:49,1 -DA:51,1 -DA:52,2 -DA:53,1 -DA:54,2 -DA:59,1 -DA:60,1 -DA:61,1 -DA:63,1 -DA:64,3 -LF:30 -LH:29 +SF:lib/src/components/popup/t_popup_config.dart +DA:7,3 +DA:50,3 +DA:91,3 +DA:92,3 +DA:97,3 +DA:185,3 +DA:186,6 +DA:187,6 +DA:190,3 +DA:191,6 +DA:192,6 +DA:195,3 +DA:196,6 +DA:197,3 +DA:198,5 +DA:200,4 +DA:202,2 +DA:203,4 +DA:204,2 +DA:205,2 +DA:206,4 +DA:207,1 +DA:209,3 +DA:210,6 +DA:211,3 +DA:212,3 +DA:213,2 +DA:214,2 +DA:215,2 +DA:216,2 +DA:220,2 +DA:221,6 +DA:222,4 +DA:223,4 +DA:227,2 +DA:228,2 +DA:229,2 +DA:230,2 +DA:231,2 +DA:236,9 +DA:237,0 +DA:242,3 +LF:42 +LH:41 end_of_record SF:lib/src/components/action_sheet/t_action_sheet.dart DA:20,0 @@ -66,140 +78,112 @@ DA:246,0 DA:250,0 DA:251,0 DA:254,0 -DA:257,0 -DA:262,0 -DA:287,0 +DA:256,0 +DA:260,0 +DA:281,0 +DA:285,0 DA:289,0 -DA:293,0 -DA:295,0 -DA:296,0 -DA:306,0 -DA:307,0 -DA:323,0 -DA:324,0 -DA:340,0 -DA:342,0 -LF:46 +DA:290,0 +DA:301,0 +DA:302,0 +DA:319,0 +DA:320,0 +DA:334,0 +LF:44 LH:0 end_of_record SF:lib/src/util/context_extension.dart -DA:7,0 +DA:7,6 LF:1 -LH:0 +LH:1 end_of_record -SF:lib/src/components/popup/t_popup_route.dart -DA:14,0 -DA:74,0 -DA:76,0 -DA:79,0 -DA:82,0 -DA:83,0 -DA:88,0 -DA:89,0 -DA:101,0 -DA:108,0 -DA:109,0 -DA:110,0 -DA:111,0 -DA:112,0 -DA:114,0 -DA:116,0 -DA:117,0 -DA:118,0 -DA:119,0 -DA:120,0 -DA:121,0 -DA:122,0 -DA:125,0 -DA:126,0 -DA:127,0 -DA:128,0 -DA:135,0 -DA:137,0 -DA:138,0 -DA:139,0 -DA:140,0 -DA:141,0 -DA:142,0 -DA:143,0 -DA:147,0 -DA:148,0 -DA:158,0 -DA:160,0 -DA:161,0 -DA:166,0 -DA:168,0 -DA:169,0 -DA:170,0 -DA:171,0 -DA:172,0 -DA:175,0 -DA:178,0 -DA:180,0 -DA:181,0 -DA:185,0 -DA:186,0 -DA:190,0 -DA:191,0 -DA:194,0 -DA:196,0 -DA:197,0 -DA:198,0 -DA:199,0 -DA:200,0 -DA:201,0 -DA:204,0 -DA:207,0 -DA:209,0 -DA:210,0 -DA:213,0 -DA:215,0 -DA:216,0 -DA:217,0 -DA:219,0 -DA:220,0 -DA:221,0 -DA:223,0 -DA:224,0 -DA:229,0 -DA:230,0 -DA:231,0 -DA:232,0 -DA:233,0 -DA:234,0 -DA:236,0 -DA:238,0 -DA:243,0 -DA:245,0 -DA:246,0 -DA:247,0 -DA:248,0 -DA:249,0 -DA:250,0 -DA:251,0 -DA:252,0 -DA:259,0 -DA:261,0 -DA:263,0 -DA:265,0 -DA:267,0 -DA:269,0 -DA:278,0 -DA:280,0 -DA:282,0 -DA:283,0 -DA:284,0 -DA:285,0 -DA:286,0 -DA:287,0 -DA:288,0 -DA:289,0 -DA:290,0 -DA:292,0 -DA:296,0 -DA:298,0 -LF:110 -LH:0 +SF:lib/src/components/popup/t_popup.dart +DA:12,1 +DA:112,2 +DA:156,2 +DA:197,2 +DA:200,2 +DA:208,2 +DA:209,2 +DA:212,2 +DA:213,2 +DA:214,2 +DA:217,2 +DA:222,2 +DA:228,2 +DA:230,6 +DA:231,2 +DA:232,2 +DA:233,2 +DA:240,2 +DA:241,2 +DA:242,2 +DA:243,4 +DA:244,2 +DA:247,1 +DA:250,1 +DA:251,1 +DA:257,1 +DA:259,1 +DA:260,2 +DA:261,4 +DA:265,1 +DA:267,2 +DA:268,1 +DA:271,1 +DA:272,2 +DA:275,2 +DA:276,1 +DA:277,3 +DA:278,2 +DA:279,2 +DA:280,2 +DA:281,2 +DA:282,2 +DA:283,2 +DA:284,2 +DA:285,2 +DA:286,2 +DA:287,2 +DA:288,2 +DA:289,2 +DA:290,2 +DA:291,2 +DA:292,2 +DA:293,2 +DA:294,2 +DA:295,2 +DA:296,2 +DA:297,2 +DA:298,2 +DA:299,2 +DA:300,2 +DA:301,2 +DA:302,2 +DA:303,2 +DA:304,2 +DA:305,2 +DA:306,2 +DA:307,2 +DA:308,2 +DA:309,2 +DA:310,2 +DA:311,2 +DA:312,2 +DA:313,2 +DA:314,2 +DA:315,2 +DA:316,2 +DA:317,2 +DA:321,1 +DA:323,2 +DA:329,2 +DA:342,6 +DA:344,2 +DA:345,2 +DA:348,4 +LF:84 +LH:84 end_of_record SF:lib/src/components/action_sheet/t_action_sheet_grid.dart DA:30,0 @@ -483,7 +467,7 @@ DA:48,0 DA:51,0 DA:54,0 DA:57,0 -DA:60,3 +DA:60,6 DA:63,0 DA:68,0 DA:71,0 @@ -533,11 +517,11 @@ DA:204,0 DA:207,0 DA:210,0 DA:213,0 -DA:218,3 -DA:221,3 +DA:218,0 +DA:221,0 DA:224,0 DA:227,0 -DA:230,0 +DA:230,6 DA:233,0 DA:236,0 DA:239,0 @@ -557,11 +541,11 @@ DA:280,0 DA:283,0 DA:286,0 DA:290,0 -DA:292,0 +DA:292,6 DA:294,0 DA:296,0 DA:298,0 -DA:300,3 +DA:300,0 DA:302,0 DA:303,0 DA:305,0 @@ -572,15 +556,15 @@ DA:312,0 DA:314,0 DA:316,0 DA:318,0 -DA:322,3 -DA:324,0 -DA:326,3 -DA:328,3 +DA:322,6 +DA:324,6 +DA:326,0 +DA:328,0 DA:330,0 DA:332,0 DA:334,0 LF:110 -LH:7 +LH:5 end_of_record SF:lib/src/theme/t_fonts.dart DA:8,0 @@ -589,11 +573,11 @@ DA:14,0 DA:17,0 DA:20,0 DA:23,0 -DA:26,0 -DA:29,3 +DA:26,6 +DA:29,6 DA:32,0 DA:35,0 -DA:38,3 +DA:38,6 DA:41,0 DA:44,0 DA:47,0 @@ -605,13 +589,13 @@ DA:62,0 DA:65,0 DA:68,0 LF:21 -LH:2 +LH:3 end_of_record SF:lib/src/theme/t_radius.dart DA:8,0 -DA:9,3 +DA:9,0 DA:10,0 -DA:11,0 +DA:11,6 DA:14,0 DA:17,0 LF:6 @@ -619,45 +603,45 @@ LH:1 end_of_record SF:lib/src/theme/t_spacers.dart DA:5,0 -DA:7,3 -DA:9,3 +DA:7,6 +DA:9,6 DA:11,3 DA:13,0 -DA:15,3 +DA:15,0 DA:17,0 DA:19,0 DA:21,0 DA:23,0 DA:25,0 LF:11 -LH:4 +LH:3 end_of_record SF:lib/src/theme/t_theme.dart -DA:13,0 -DA:18,0 -DA:35,0 -DA:38,0 -DA:40,0 -DA:41,0 -DA:42,0 -DA:43,0 -DA:44,0 +DA:13,2 +DA:18,2 +DA:35,2 +DA:38,2 +DA:40,4 +DA:41,2 +DA:42,2 +DA:43,2 +DA:44,2 DA:48,0 -DA:55,0 -DA:59,0 +DA:55,2 +DA:59,4 DA:63,0 DA:64,0 -DA:69,1 -DA:72,1 +DA:69,2 +DA:72,0 DA:76,0 DA:77,0 DA:79,0 DA:80,0 DA:86,0 DA:89,0 -DA:136,1 -DA:149,1 -DA:152,1 +DA:136,2 +DA:149,2 +DA:152,2 DA:158,0 DA:159,0 DA:167,0 @@ -695,64 +679,64 @@ DA:245,0 DA:247,0 DA:248,0 DA:251,0 -DA:257,1 -DA:261,1 -DA:262,1 -DA:264,1 -DA:265,1 -DA:266,1 -DA:268,1 -DA:269,1 -DA:270,4 -DA:284,1 -DA:291,1 +DA:257,2 +DA:261,2 +DA:262,2 +DA:264,2 +DA:265,2 +DA:266,2 +DA:268,2 +DA:269,2 +DA:270,8 +DA:284,2 +DA:291,2 DA:292,0 -DA:297,1 -DA:298,1 -DA:299,1 -DA:300,1 +DA:297,2 +DA:298,2 +DA:299,2 +DA:300,2 DA:301,0 -DA:302,1 -DA:304,1 -DA:305,1 -DA:306,1 -DA:308,3 -DA:309,2 +DA:302,2 +DA:304,2 +DA:305,2 +DA:306,2 +DA:308,6 +DA:309,4 DA:332,0 DA:333,0 DA:337,0 -DA:342,1 -DA:343,1 -DA:344,2 -DA:345,1 -DA:350,1 -DA:351,2 -DA:352,1 -DA:354,2 -DA:359,1 -DA:360,2 -DA:361,2 -DA:365,1 -DA:366,2 -DA:367,3 -DA:371,1 -DA:372,2 -DA:373,3 -DA:377,1 -DA:378,2 -DA:379,3 -DA:383,1 -DA:384,2 -DA:385,1 -DA:386,2 -DA:387,2 -DA:388,2 -DA:389,2 -DA:390,2 -DA:391,4 -DA:392,3 -DA:396,2 -DA:400,1 +DA:342,2 +DA:343,2 +DA:344,4 +DA:345,2 +DA:350,2 +DA:351,4 +DA:352,2 +DA:354,4 +DA:359,2 +DA:360,4 +DA:361,4 +DA:365,2 +DA:366,4 +DA:367,6 +DA:371,2 +DA:372,4 +DA:373,6 +DA:377,2 +DA:378,4 +DA:379,6 +DA:383,2 +DA:384,4 +DA:385,2 +DA:386,4 +DA:387,4 +DA:388,4 +DA:389,4 +DA:390,4 +DA:391,8 +DA:392,6 +DA:396,4 +DA:400,2 DA:401,0 DA:402,0 DA:406,0 @@ -781,17 +765,17 @@ DA:456,0 DA:457,0 DA:458,0 DA:459,0 -DA:474,1 -DA:477,2 -DA:481,1 -DA:484,2 -DA:485,1 -DA:489,3 -DA:490,1 -DA:496,1 -DA:497,1 +DA:474,2 +DA:477,4 +DA:481,2 +DA:484,4 +DA:485,2 +DA:489,6 +DA:490,2 +DA:496,2 +DA:497,2 LF:157 -LH:67 +LH:77 end_of_record SF:lib/src/util/iterable_ext.dart DA:8,0 @@ -842,7 +826,7 @@ LF:14 LH:0 end_of_record SF:lib/src/components/badge/t_badge.dart -DA:43,1 +DA:43,4 DA:57,0 DA:95,0 DA:96,0 @@ -966,15 +950,15 @@ LF:120 LH:1 end_of_record SF:lib/src/components/text/t_text.dart -DA:41,1 -DA:68,1 +DA:41,2 +DA:68,2 DA:71,0 DA:98,0 -DA:163,1 -DA:165,1 +DA:163,2 +DA:165,2 DA:167,0 DA:169,0 -DA:172,1 +DA:172,2 DA:173,0 DA:174,0 DA:176,0 @@ -992,35 +976,35 @@ DA:192,0 DA:193,0 DA:194,0 DA:196,0 -DA:199,3 -DA:201,1 +DA:199,4 +DA:201,2 DA:203,0 DA:205,0 -DA:210,1 -DA:211,1 -DA:214,1 -DA:216,1 -DA:217,2 +DA:210,2 +DA:211,2 +DA:214,2 +DA:216,2 +DA:217,0 DA:218,0 -DA:220,2 -DA:221,3 -DA:223,1 +DA:220,4 +DA:221,4 +DA:223,2 DA:224,0 DA:225,0 -DA:227,2 -DA:229,1 +DA:227,4 +DA:229,2 DA:230,0 DA:232,0 -DA:237,2 -DA:238,1 +DA:237,4 +DA:238,2 DA:239,2 -DA:244,2 -DA:245,2 +DA:244,4 +DA:245,6 DA:246,2 DA:247,2 DA:248,2 DA:249,2 -DA:250,3 +DA:250,4 DA:251,2 DA:252,2 DA:253,2 @@ -1028,32 +1012,32 @@ DA:254,2 DA:255,2 DA:256,2 DA:257,2 -DA:258,1 -DA:259,3 +DA:258,2 +DA:259,4 DA:260,2 DA:261,2 DA:262,2 DA:265,2 -DA:266,1 +DA:266,2 DA:272,0 DA:273,0 -DA:276,1 -DA:280,1 -DA:281,1 -DA:282,1 -DA:283,1 -DA:285,1 -DA:286,1 -DA:287,1 -DA:288,1 -DA:289,1 -DA:290,1 -DA:291,1 -DA:292,1 -DA:293,1 -DA:294,1 -DA:295,1 -DA:296,1 +DA:276,2 +DA:280,2 +DA:281,2 +DA:282,2 +DA:283,2 +DA:285,2 +DA:286,2 +DA:287,2 +DA:288,2 +DA:289,2 +DA:290,2 +DA:291,2 +DA:292,2 +DA:293,2 +DA:294,2 +DA:295,2 +DA:296,2 DA:298,0 DA:299,0 DA:301,0 @@ -1141,7 +1125,7 @@ DA:497,0 DA:502,0 DA:505,0 LF:174 -LH:58 +LH:57 end_of_record SF:lib/src/components/action_sheet/t_action_sheet_item_widget.dart DA:12,0 @@ -1944,96 +1928,92 @@ LF:90 LH:0 end_of_record SF:lib/src/components/picker/no_wave_behavior.dart -DA:11,1 -DA:14,2 -DA:17,1 -DA:21,1 -DA:23,1 -DA:24,1 -DA:25,1 -DA:26,1 -DA:27,1 -DA:28,1 +DA:11,0 +DA:14,0 +DA:17,0 +DA:21,0 +DA:23,0 +DA:24,0 +DA:25,0 +DA:26,0 +DA:27,0 +DA:28,0 LF:10 -LH:10 +LH:0 end_of_record SF:lib/src/components/picker/t_item_widget.dart -DA:31,1 -DA:67,1 -DA:68,1 -DA:70,1 -DA:72,1 -DA:74,1 -DA:75,1 -DA:76,1 -DA:78,1 -DA:79,1 -DA:82,1 -DA:84,1 -DA:85,2 -DA:92,1 -DA:94,1 -DA:95,1 -DA:96,1 -DA:98,7 -DA:99,1 -DA:100,1 -DA:101,1 -DA:102,2 -DA:104,1 -DA:105,1 -DA:106,1 -DA:110,1 -DA:111,1 -DA:114,1 -DA:115,1 -DA:116,1 -DA:117,1 -DA:133,1 -DA:150,3 -DA:153,1 -DA:154,1 -DA:155,1 -DA:156,1 -DA:157,2 -DA:158,1 -DA:161,1 -DA:164,1 -DA:168,1 -DA:169,2 -DA:172,1 -DA:173,3 -DA:174,3 -DA:178,1 -DA:179,2 +DA:31,0 +DA:67,0 +DA:68,0 +DA:70,0 +DA:72,0 +DA:74,0 +DA:75,0 +DA:76,0 +DA:78,0 +DA:79,0 +DA:82,0 +DA:84,0 +DA:85,0 +DA:92,0 +DA:94,0 +DA:95,0 +DA:96,0 +DA:98,0 +DA:99,0 +DA:100,0 +DA:101,0 +DA:102,0 +DA:104,0 +DA:105,0 +DA:106,0 +DA:110,0 +DA:111,0 +DA:114,0 +DA:115,0 +DA:116,0 +DA:117,0 +DA:133,4 +DA:150,0 +DA:153,0 +DA:154,0 +DA:155,0 +DA:156,0 +DA:157,0 +DA:158,0 +DA:161,0 +DA:164,0 +DA:168,0 +DA:169,0 +DA:172,0 +DA:173,0 +DA:174,0 +DA:178,0 +DA:179,0 LF:48 -LH:48 +LH:1 end_of_record SF:lib/src/components/picker/t_picker_option.dart -DA:14,2 -DA:29,1 -DA:32,1 -DA:33,3 -DA:34,3 -DA:35,3 -DA:36,3 -DA:38,1 -DA:39,4 -DA:41,1 -DA:42,3 +DA:14,0 +DA:29,0 +DA:32,0 +DA:33,0 +DA:34,0 +DA:35,0 +DA:36,0 +DA:38,0 +DA:39,0 +DA:41,0 +DA:42,0 LF:11 -LH:11 +LH:0 end_of_record SF:lib/src/components/picker/t_picker_value.dart -DA:13,1 -DA:28,1 -DA:29,5 -DA:35,1 -DA:36,5 -DA:38,1 -DA:40,4 -LF:7 -LH:7 +DA:13,0 +DA:38,0 +DA:40,0 +LF:3 +LH:0 end_of_record SF:lib/src/components/calendar/t_calendar.dart DA:26,0 @@ -2211,68 +2191,68 @@ LF:171 LH:0 end_of_record SF:lib/src/components/calendar/t_date_picker.dart -DA:21,0 -DA:34,0 +DA:22,0 DA:35,0 -DA:41,0 -DA:43,0 +DA:36,0 +DA:42,0 DA:44,0 DA:45,0 -DA:48,0 -DA:50,0 -DA:52,0 +DA:46,0 +DA:49,0 +DA:51,0 DA:53,0 DA:54,0 DA:55,0 DA:56,0 -DA:58,0 +DA:57,0 DA:59,0 DA:60,0 DA:61,0 -DA:63,0 +DA:62,0 DA:64,0 DA:65,0 DA:66,0 DA:67,0 -DA:69,0 -DA:74,0 +DA:68,0 +DA:70,0 DA:75,0 -DA:77,0 -DA:79,0 +DA:76,0 +DA:78,0 DA:80,0 DA:81,0 -DA:84,0 -DA:86,0 +DA:82,0 +DA:85,0 DA:87,0 DA:88,0 -DA:92,0 -DA:94,0 +DA:89,0 +DA:93,0 DA:95,0 DA:96,0 DA:97,0 -DA:108,0 +DA:98,0 DA:109,0 DA:110,0 -DA:112,0 +DA:111,0 DA:113,0 -DA:115,0 +DA:114,0 DA:116,0 DA:117,0 DA:118,0 -DA:120,0 -DA:122,0 -DA:124,0 +DA:119,0 +DA:121,0 +DA:123,0 DA:125,0 -DA:127,0 -DA:129,0 +DA:126,0 +DA:128,0 DA:130,0 DA:131,0 DA:132,0 DA:133,0 -DA:135,0 -DA:137,0 -DA:139,0 -DA:142,0 +DA:134,0 +DA:136,0 +DA:138,0 +DA:140,0 +DA:143,0 LF:62 LH:0 end_of_record @@ -2637,36 +2617,36 @@ DA:25,0 DA:61,0 DA:64,0 DA:67,0 +DA:68,0 DA:71,0 -DA:74,0 -DA:75,0 -DA:76,0 +DA:72,0 +DA:73,0 DA:77,0 +DA:79,0 DA:80,0 DA:81,0 -DA:82,0 -DA:83,0 +DA:84,0 DA:85,0 DA:86,0 -DA:87,0 -DA:92,0 -DA:93,0 +DA:88,0 +DA:89,0 +DA:90,0 +DA:96,0 DA:97,0 DA:98,0 -DA:99,0 +DA:102,0 DA:103,0 DA:104,0 DA:105,0 -DA:106,0 +DA:110,0 DA:111,0 -DA:113,0 -DA:118,0 -DA:120,0 -DA:125,0 -DA:133,0 -DA:141,0 -DA:146,0 -DA:147,0 +DA:114,0 +DA:116,0 +DA:121,0 +DA:129,0 +DA:137,0 +DA:142,0 +DA:143,0 LF:38 LH:0 end_of_record @@ -3972,7 +3952,7 @@ LF:10 LH:0 end_of_record SF:lib/src/components/collapse/t_inset_divider.dart -DA:9,1 +DA:9,4 DA:11,0 DA:13,0 DA:15,0 @@ -4441,7 +4421,7 @@ LF:47 LH:0 end_of_record SF:lib/src/components/divider/t_divider.dart -DA:12,6 +DA:12,24 DA:26,0 DA:64,0 DA:67,0 @@ -4498,10 +4478,9 @@ DA:108,0 DA:109,0 DA:111,0 DA:112,0 +DA:113,0 +DA:116,0 DA:117,0 -DA:118,0 -DA:119,0 -DA:120,0 DA:121,0 DA:122,0 DA:123,0 @@ -4513,22 +4492,21 @@ DA:128,0 DA:129,0 DA:130,0 DA:131,0 -DA:136,0 -DA:138,0 -DA:142,0 +DA:132,0 +DA:133,0 +DA:134,0 +DA:139,0 +DA:140,0 DA:143,0 -DA:146,0 +DA:145,0 DA:148,0 DA:149,0 DA:150,0 -DA:154,0 -DA:155,0 -DA:156,0 -LF:35 +LF:33 LH:0 end_of_record SF:lib/src/components/icon/t_icons.dart -DA:8,1 +DA:8,4 DA:9,0 DA:22,0 LF:3 @@ -6446,11 +6424,11 @@ LF:518 LH:0 end_of_record SF:lib/src/components/image_viewer/t_image_viewer.dart -DA:12,0 +DA:11,0 +DA:40,0 DA:41,0 -DA:42,0 +DA:46,0 DA:47,0 -DA:48,0 LF:5 LH:0 end_of_record @@ -6916,7 +6894,7 @@ DA:94,0 DA:98,0 DA:102,0 DA:104,0 -DA:111,1 +DA:111,4 DA:120,0 DA:125,0 DA:129,0 @@ -7977,7 +7955,7 @@ LF:47 LH:0 end_of_record SF:lib/src/components/loading/t_loading.dart -DA:38,3 +DA:38,12 DA:49,0 DA:78,0 DA:80,0 @@ -8481,12 +8459,12 @@ LF:21 LH:0 end_of_record SF:lib/src/util/platform_util.dart -DA:6,1 -DA:7,1 -DA:10,1 -DA:11,1 -DA:14,1 -DA:15,1 +DA:6,0 +DA:7,0 +DA:10,2 +DA:11,2 +DA:14,0 +DA:15,0 DA:18,0 DA:19,0 DA:22,0 @@ -8497,409 +8475,23 @@ DA:30,0 DA:31,0 DA:34,0 LF:15 -LH:6 +LH:2 end_of_record SF:lib/src/components/picker/t_picker.dart -DA:57,2 -DA:229,1 -DA:230,1 -DA:250,6 -DA:252,1 -DA:254,1 -DA:255,1 -DA:258,1 -DA:260,1 -DA:261,4 -DA:263,4 -DA:265,1 -DA:266,1 -DA:270,1 -DA:272,1 -DA:273,1 -DA:277,1 -DA:278,2 -DA:279,1 -DA:283,1 -DA:284,2 -DA:285,2 -DA:286,2 -DA:287,2 -DA:288,2 -DA:290,2 -DA:291,2 -DA:292,1 -DA:293,1 -DA:294,2 -DA:295,1 -DA:296,1 -DA:300,1 -DA:301,1 -DA:303,2 -DA:304,5 -DA:306,2 -DA:308,7 -DA:309,1 -DA:313,1 -DA:317,1 -DA:318,2 -DA:319,1 -DA:323,2 -DA:324,1 -DA:327,1 -DA:328,2 -DA:331,5 -DA:332,1 -DA:337,1 -DA:339,1 -DA:340,1 -DA:342,1 -DA:343,1 -DA:344,1 -DA:350,1 -DA:351,1 -DA:352,2 -DA:353,1 -DA:354,2 -DA:355,1 -DA:356,2 -DA:358,1 -DA:360,1 -DA:362,1 -DA:363,5 -DA:364,1 -DA:365,1 -DA:366,1 -DA:367,1 -DA:368,1 -DA:369,1 -DA:370,2 -DA:375,1 -DA:376,2 -DA:377,1 -DA:378,1 -DA:379,4 -DA:380,2 -DA:392,1 -DA:393,1 -DA:395,1 -DA:396,2 -DA:397,1 -DA:398,1 -DA:399,1 -DA:401,1 -DA:402,4 -DA:403,4 -DA:404,1 -DA:405,2 -DA:407,1 -DA:408,2 -DA:410,1 -DA:412,1 -DA:413,4 -DA:414,5 -DA:415,1 -DA:416,2 -DA:425,1 -DA:426,2 -DA:427,2 -DA:429,1 -DA:430,2 -DA:431,1 -DA:432,2 -DA:434,1 -DA:446,1 -DA:454,1 -DA:455,1 -DA:456,2 -DA:459,1 -DA:460,1 -DA:465,1 -DA:467,2 -DA:468,2 -DA:469,0 -DA:471,1 -DA:474,1 -DA:475,1 -DA:476,2 -DA:483,1 -DA:484,2 -DA:485,1 -DA:489,1 -DA:490,1 -DA:492,1 -DA:493,1 -DA:494,1 -DA:495,1 -DA:496,1 -DA:497,1 -DA:498,1 -DA:500,2 -DA:501,2 -DA:504,2 -DA:506,2 -DA:507,1 -DA:508,1 -DA:509,2 -DA:510,2 -DA:511,2 -DA:514,1 -DA:515,2 -DA:516,2 -DA:517,2 -DA:526,1 -DA:529,1 -DA:533,2 -DA:534,1 -DA:537,1 -DA:538,2 -DA:539,2 -DA:543,1 -DA:545,2 -DA:550,2 -DA:554,1 -DA:559,1 -DA:561,3 -DA:562,1 -DA:565,2 -DA:568,1 -DA:569,5 -DA:570,0 -DA:574,2 -DA:575,1 -DA:579,1 -DA:581,1 -DA:584,2 -DA:585,1 -DA:586,2 -DA:587,1 -DA:589,1 -DA:590,0 -DA:591,0 -DA:596,1 -DA:597,2 -DA:601,1 -DA:602,1 -DA:603,3 -DA:604,1 -DA:605,1 -DA:606,2 -DA:610,1 -DA:611,2 -DA:618,1 -DA:619,2 -DA:620,1 -DA:621,4 -DA:624,0 -DA:625,0 -DA:629,0 -DA:632,1 -DA:633,2 -DA:634,3 -DA:635,3 -DA:638,1 -DA:642,2 -DA:646,2 -DA:648,1 -DA:651,1 -DA:658,1 -DA:659,4 -DA:662,5 -DA:663,3 -DA:665,5 -DA:666,5 -DA:667,5 -DA:668,5 -DA:677,1 -DA:686,2 -DA:687,1 -DA:688,3 -DA:691,2 -DA:692,3 -DA:693,2 -DA:694,4 -DA:702,3 -DA:713,1 -DA:714,1 -DA:715,1 -DA:718,2 -DA:720,1 -DA:721,1 -DA:730,1 -DA:731,1 -DA:732,1 -DA:734,4 -DA:735,2 -DA:736,1 -DA:739,6 -DA:741,2 -DA:742,0 -DA:743,0 -DA:747,1 -DA:748,2 -DA:751,1 -DA:754,1 -DA:755,4 -DA:762,1 -DA:763,2 -DA:767,2 -DA:768,1 -DA:771,2 -DA:773,4 -LF:245 -LH:236 -end_of_record -SF:lib/src/components/picker/t_picker_items.dart -DA:14,2 -DA:29,2 -DA:39,1 -DA:43,1 -DA:44,1 -DA:50,1 -DA:53,1 -DA:54,3 -DA:55,3 -DA:58,1 -DA:63,3 -DA:66,3 -DA:67,3 -DA:74,1 -DA:75,2 -DA:93,1 -DA:103,1 -DA:107,1 -DA:108,1 -DA:118,1 -DA:121,1 -DA:122,3 -DA:123,3 -DA:130,1 -DA:135,3 -DA:138,2 -DA:139,2 -DA:140,2 -DA:141,5 -DA:142,5 -DA:149,1 -DA:153,0 -DA:154,0 -DA:156,0 -DA:157,0 -DA:159,0 -DA:162,1 -DA:163,2 -DA:164,2 -DA:165,5 -LF:40 -LH:35 -end_of_record -SF:lib/src/components/picker/t_picker_keys.dart -DA:13,2 -DA:35,1 -DA:38,1 -DA:39,3 -DA:40,3 -DA:41,3 -DA:42,3 -DA:43,3 -DA:45,1 -DA:46,5 -DA:48,1 -DA:50,5 -LF:12 -LH:12 -end_of_record -SF:lib/src/components/picker/t_picker_load_event.dart -DA:8,2 -DA:29,1 -DA:30,1 -DA:31,2 -DA:32,2 -LF:5 -LH:5 -end_of_record -SF:lib/src/components/popover/t_popover.dart -DA:6,0 -DA:24,0 -DA:29,0 -LF:3 -LH:0 -end_of_record -SF:lib/src/components/popover/t_popover_widget.dart -DA:66,0 -DA:126,0 -DA:127,0 -DA:135,0 -DA:137,0 -DA:138,0 -DA:139,0 -DA:140,0 -DA:141,0 -DA:144,0 -DA:145,0 -DA:152,0 -DA:153,0 -DA:154,0 -DA:155,0 -DA:159,0 -DA:160,0 -DA:161,0 -DA:164,0 -DA:165,0 -DA:169,0 -DA:170,0 -DA:171,0 -DA:172,0 -DA:173,0 -DA:174,0 -DA:175,0 -DA:178,0 -DA:179,0 -DA:183,0 -DA:184,0 -DA:188,0 -DA:189,0 -DA:190,0 -DA:191,0 -DA:192,0 -DA:193,0 -DA:197,0 -DA:198,0 -DA:202,0 -DA:203,0 -DA:204,0 -DA:207,0 -DA:208,0 -DA:209,0 -DA:210,0 -DA:211,0 -DA:212,0 -DA:216,0 -DA:217,0 -DA:221,0 -DA:222,0 -DA:223,0 -DA:227,0 -DA:230,0 -DA:235,0 -DA:236,0 -DA:237,0 -DA:238,0 -DA:239,0 -DA:241,0 -DA:242,0 -DA:243,0 -DA:245,0 +DA:53,0 +DA:225,0 +DA:226,0 +DA:244,0 DA:246,0 -DA:247,0 +DA:248,0 DA:249,0 -DA:250,0 -DA:251,0 -DA:253,0 +DA:252,0 DA:254,0 DA:255,0 -DA:258,0 +DA:257,0 DA:259,0 -DA:265,0 +DA:260,0 +DA:264,0 DA:266,0 DA:267,0 DA:271,0 @@ -8911,7 +8503,6 @@ DA:279,0 DA:280,0 DA:281,0 DA:282,0 -DA:283,0 DA:284,0 DA:285,0 DA:286,0 @@ -8919,243 +8510,434 @@ DA:287,0 DA:288,0 DA:289,0 DA:290,0 -DA:291,0 -DA:293,0 DA:294,0 DA:295,0 -DA:296,0 DA:297,0 DA:298,0 DA:300,0 -DA:305,0 -DA:306,0 +DA:302,0 +DA:303,0 DA:307,0 -DA:308,0 -DA:309,0 -DA:310,0 DA:311,0 DA:312,0 DA:313,0 -DA:314,0 -DA:315,0 DA:317,0 DA:318,0 -DA:319,0 -DA:320,0 DA:321,0 DA:322,0 -DA:323,0 -DA:324,0 DA:325,0 DA:326,0 -DA:327,0 -DA:329,0 -DA:335,0 +DA:331,0 +DA:333,0 +DA:334,0 DA:336,0 DA:337,0 DA:338,0 -DA:340,0 -DA:342,0 -DA:343,0 DA:344,0 +DA:345,0 DA:346,0 DA:347,0 DA:348,0 +DA:349,0 DA:350,0 -DA:351,0 -DA:353,0 +DA:352,0 DA:354,0 -DA:355,0 +DA:356,0 DA:357,0 +DA:358,0 DA:359,0 +DA:360,0 DA:361,0 DA:362,0 +DA:363,0 DA:364,0 -DA:365,0 -DA:366,0 -DA:368,0 +DA:369,0 DA:370,0 +DA:371,0 DA:372,0 DA:373,0 -DA:375,0 -DA:376,0 -DA:377,0 -DA:380,0 -DA:382,0 -DA:384,0 +DA:374,0 +DA:386,0 +DA:387,0 +DA:388,0 DA:389,0 -DA:390,0 DA:391,0 +DA:392,0 DA:393,0 +DA:394,0 +DA:395,0 +DA:396,0 DA:397,0 DA:398,0 DA:399,0 -DA:400,0 DA:401,0 DA:402,0 +DA:407,0 DA:408,0 DA:410,0 DA:411,0 DA:412,0 DA:413,0 +DA:414,0 DA:416,0 DA:417,0 -DA:418,0 -DA:419,0 -DA:420,0 -DA:421,0 -DA:422,0 -DA:423,0 -DA:424,0 -DA:442,0 -DA:443,0 -DA:444,0 +DA:429,0 +DA:430,0 +DA:431,0 +DA:433,0 +DA:434,0 +DA:435,0 +DA:436,0 +DA:438,0 DA:445,0 DA:446,0 +DA:447,0 +DA:451,0 +DA:452,0 +DA:454,0 DA:455,0 DA:456,0 DA:457,0 DA:458,0 DA:459,0 DA:460,0 -DA:467,0 +DA:462,0 +DA:463,0 +DA:466,0 DA:468,0 DA:469,0 DA:470,0 +DA:471,0 +DA:472,0 DA:473,0 -DA:474,0 -DA:475,0 +DA:476,0 +DA:477,0 DA:478,0 DA:479,0 -DA:480,0 -DA:481,0 -DA:483,0 -DA:492,0 -DA:493,0 -DA:494,0 -DA:497,0 -DA:498,0 +DA:488,0 +DA:491,0 +DA:495,0 +DA:496,0 +DA:499,0 +DA:500,0 DA:501,0 -DA:502,0 DA:505,0 -DA:506,0 -DA:514,0 -DA:515,0 +DA:507,0 +DA:512,0 DA:516,0 -DA:517,0 -DA:518,0 -DA:519,0 -DA:520,0 +DA:521,0 +DA:523,0 +DA:524,0 DA:527,0 -DA:534,0 +DA:530,0 +DA:531,0 +DA:532,0 DA:536,0 DA:537,0 -DA:538,0 -DA:539,0 -DA:540,0 DA:541,0 -DA:542,0 -DA:545,0 -LF:231 +DA:543,0 +DA:546,0 +DA:547,0 +DA:548,0 +DA:549,0 +DA:551,0 +DA:552,0 +DA:553,0 +DA:558,0 +DA:559,0 +DA:563,0 +DA:564,0 +DA:565,0 +DA:566,0 +DA:567,0 +DA:568,0 +DA:572,0 +DA:573,0 +DA:580,0 +DA:581,0 +DA:582,0 +DA:583,0 +DA:586,0 +DA:587,0 +DA:591,0 +DA:594,0 +DA:595,0 +DA:596,0 +DA:597,0 +DA:600,0 +DA:604,0 +DA:608,0 +DA:610,0 +DA:613,0 +DA:620,0 +DA:621,0 +DA:624,0 +DA:625,0 +DA:627,0 +DA:628,0 +DA:629,0 +DA:630,0 +DA:639,0 +DA:648,0 +DA:649,0 +DA:650,0 +DA:653,0 +DA:654,0 +DA:655,0 +DA:656,0 +DA:664,0 +DA:675,0 +DA:676,0 +DA:677,0 +DA:680,0 +DA:682,0 +DA:683,0 +DA:692,0 +DA:693,0 +DA:694,0 +DA:696,0 +DA:697,0 +DA:698,0 +DA:701,0 +DA:703,0 +DA:704,0 +DA:705,0 +DA:709,0 +DA:710,0 +DA:713,0 +DA:716,0 +DA:717,0 +DA:724,0 +DA:725,0 +DA:729,0 +DA:730,0 +DA:733,0 +DA:735,0 +LF:235 LH:0 end_of_record -SF:lib/src/components/popup/t_popup_panel.dart -DA:11,0 -DA:21,0 -DA:70,0 -DA:72,0 -DA:73,0 -DA:77,0 -DA:78,0 -DA:80,0 -DA:88,0 -DA:90,0 -DA:94,0 -DA:95,0 -DA:99,0 -DA:100,0 +SF:lib/src/util/t_toolbar_pressable.dart +DA:10,2 +DA:41,2 +DA:42,2 +DA:48,2 +DA:49,8 +DA:52,4 +DA:55,6 +DA:58,2 +DA:60,2 +DA:61,4 +DA:62,2 +DA:63,2 +DA:64,2 +DA:67,4 +DA:68,4 +DA:69,1 +DA:70,2 +DA:74,4 +DA:75,1 +DA:76,2 +DA:81,8 +DA:83,2 +DA:85,4 +DA:86,4 +DA:87,0 +DA:88,4 +DA:89,2 +DA:90,4 +DA:91,2 +DA:92,2 +LF:30 +LH:29 +end_of_record +SF:lib/src/components/picker/t_picker_items.dart +DA:14,0 +DA:29,0 +DA:39,0 +DA:43,0 +DA:44,0 +DA:50,0 +DA:53,0 +DA:54,0 +DA:55,0 +DA:58,0 +DA:63,0 +DA:66,0 +DA:67,0 +DA:74,0 +DA:75,0 +DA:93,0 DA:103,0 -DA:104,0 -DA:105,0 -DA:106,0 DA:107,0 -DA:110,0 -DA:111,0 -DA:112,0 -DA:113,0 -DA:114,0 -DA:116,0 -DA:117,0 -DA:120,0 +DA:108,0 +DA:118,0 DA:121,0 -DA:124,0 -DA:125,0 +DA:122,0 +DA:123,0 +DA:130,0 +DA:135,0 +DA:138,0 +DA:139,0 +DA:140,0 +DA:141,0 +DA:142,0 +DA:149,0 +DA:153,0 +DA:154,0 +DA:156,0 +DA:157,0 +DA:159,0 +DA:162,0 +DA:163,0 +DA:164,0 +DA:165,0 +LF:40 +LH:0 +end_of_record +SF:lib/src/components/picker/t_picker_keys.dart +DA:13,4 +DA:35,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:45,0 +DA:46,0 +DA:48,0 +DA:50,0 +LF:12 +LH:1 +end_of_record +SF:lib/src/components/picker/t_picker_normalize.dart +DA:9,0 +DA:12,0 +DA:14,0 +DA:17,0 +DA:18,0 +DA:19,0 +DA:20,0 +DA:22,0 +DA:27,0 +DA:29,0 +DA:32,0 +DA:35,0 +DA:36,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:44,0 +DA:46,0 +DA:49,0 +DA:51,0 +DA:52,0 +DA:53,0 +DA:54,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:63,0 +DA:64,0 +LF:30 +LH:0 +end_of_record +SF:lib/src/components/picker/t_picker_load_event.dart +DA:8,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +LF:5 +LH:0 +end_of_record +SF:lib/src/components/popover/t_popover.dart +DA:6,0 +DA:24,0 +DA:29,0 +LF:3 +LH:0 +end_of_record +SF:lib/src/components/popover/t_popover_widget.dart +DA:66,0 DA:126,0 DA:127,0 -DA:131,0 +DA:135,0 +DA:137,0 +DA:138,0 +DA:139,0 DA:140,0 DA:141,0 DA:144,0 DA:145,0 -DA:146,0 -DA:149,0 -DA:150,0 +DA:152,0 +DA:153,0 DA:154,0 DA:155,0 -DA:156,0 -DA:157,0 -DA:158,0 +DA:159,0 +DA:160,0 DA:161,0 -DA:168,0 +DA:164,0 +DA:165,0 DA:169,0 +DA:170,0 +DA:171,0 DA:172,0 +DA:173,0 DA:174,0 DA:175,0 -DA:176,0 -DA:177,0 -DA:181,0 +DA:178,0 +DA:179,0 +DA:183,0 DA:184,0 -DA:185,0 +DA:188,0 DA:189,0 +DA:190,0 DA:191,0 DA:192,0 DA:193,0 -DA:194,0 DA:197,0 -DA:200,0 -DA:201,0 +DA:198,0 DA:202,0 +DA:203,0 +DA:204,0 +DA:207,0 +DA:208,0 DA:209,0 +DA:210,0 DA:211,0 -DA:213,0 -DA:214,0 -DA:218,0 -DA:219,0 -DA:220,0 +DA:212,0 +DA:216,0 +DA:217,0 DA:221,0 DA:222,0 DA:223,0 -DA:225,0 -DA:226,0 -DA:228,0 -DA:229,0 +DA:227,0 DA:230,0 -DA:232,0 -DA:233,0 -DA:234,0 DA:235,0 DA:236,0 -DA:244,0 +DA:237,0 +DA:238,0 +DA:239,0 +DA:241,0 +DA:242,0 +DA:243,0 DA:245,0 DA:246,0 DA:247,0 -DA:248,0 DA:249,0 DA:250,0 DA:251,0 -DA:256,0 -DA:257,0 +DA:253,0 +DA:254,0 +DA:255,0 DA:258,0 +DA:259,0 +DA:265,0 +DA:266,0 +DA:267,0 DA:271,0 DA:272,0 DA:273,0 @@ -9163,130 +8945,494 @@ DA:277,0 DA:278,0 DA:279,0 DA:280,0 +DA:281,0 +DA:282,0 DA:283,0 DA:284,0 DA:285,0 +DA:286,0 DA:287,0 DA:288,0 DA:289,0 DA:290,0 -DA:292,0 +DA:291,0 +DA:293,0 +DA:294,0 +DA:295,0 +DA:296,0 +DA:297,0 DA:298,0 -DA:334,0 +DA:300,0 +DA:305,0 +DA:306,0 +DA:307,0 +DA:308,0 +DA:309,0 +DA:310,0 +DA:311,0 +DA:312,0 +DA:313,0 +DA:314,0 +DA:315,0 +DA:317,0 +DA:318,0 +DA:319,0 +DA:320,0 +DA:321,0 +DA:322,0 +DA:323,0 +DA:324,0 +DA:325,0 +DA:326,0 +DA:327,0 +DA:329,0 DA:335,0 +DA:336,0 +DA:337,0 +DA:338,0 DA:340,0 DA:342,0 DA:343,0 -DA:345,0 +DA:344,0 DA:346,0 DA:347,0 DA:348,0 -DA:349,0 DA:350,0 +DA:351,0 +DA:353,0 +DA:354,0 +DA:355,0 DA:357,0 -DA:358,0 -DA:360,0 +DA:359,0 DA:361,0 -DA:363,0 +DA:362,0 +DA:364,0 +DA:365,0 DA:366,0 DA:368,0 -DA:369,0 -DA:371,0 +DA:370,0 DA:372,0 -DA:374,0 -DA:381,0 +DA:373,0 +DA:375,0 +DA:376,0 +DA:377,0 +DA:380,0 DA:382,0 -DA:383,0 +DA:384,0 +DA:389,0 DA:390,0 -DA:392,0 -DA:394,0 -DA:395,0 -DA:396,0 +DA:391,0 +DA:393,0 DA:397,0 DA:398,0 +DA:399,0 +DA:400,0 +DA:401,0 DA:402,0 -DA:404,0 -DA:409,0 +DA:408,0 +DA:410,0 +DA:411,0 +DA:412,0 +DA:413,0 +DA:416,0 +DA:417,0 +DA:418,0 +DA:419,0 +DA:420,0 +DA:421,0 +DA:422,0 +DA:423,0 +DA:424,0 +DA:442,0 +DA:443,0 +DA:444,0 +DA:445,0 +DA:446,0 +DA:455,0 +DA:456,0 DA:457,0 DA:458,0 -DA:463,0 -DA:465,0 -DA:466,0 +DA:459,0 +DA:460,0 DA:467,0 +DA:468,0 +DA:469,0 DA:470,0 -DA:472,0 DA:473,0 DA:474,0 -DA:476,0 -DA:477,0 +DA:475,0 +DA:478,0 +DA:479,0 DA:480,0 DA:481,0 -DA:482,0 DA:483,0 -DA:485,0 -DA:486,0 -DA:487,0 -DA:488,0 -DA:495,0 -DA:496,0 +DA:492,0 +DA:493,0 +DA:494,0 +DA:497,0 DA:498,0 -DA:499,0 -DA:507,0 -DA:513,0 +DA:501,0 +DA:502,0 +DA:505,0 +DA:506,0 +DA:514,0 DA:515,0 DA:516,0 +DA:517,0 +DA:518,0 +DA:519,0 DA:520,0 -DA:524,0 -DA:525,0 -DA:526,0 DA:527,0 -DA:528,0 -DA:529,0 -DA:530,0 +DA:534,0 DA:536,0 +DA:537,0 DA:538,0 +DA:539,0 +DA:540,0 DA:541,0 DA:542,0 -DA:543,0 -DA:544,0 DA:545,0 -DA:549,0 -DA:551,0 -DA:556,0 -DA:588,0 -DA:590,0 -DA:591,0 -DA:593,0 -DA:595,0 -DA:597,0 -DA:598,0 -DA:599,0 -DA:600,0 -DA:602,0 -DA:604,0 -DA:605,0 -DA:607,0 -DA:608,0 -DA:610,0 -DA:616,0 -DA:617,0 -DA:618,0 -DA:619,0 -DA:620,0 -DA:622,0 -DA:623,0 -DA:624,0 -DA:625,0 -DA:626,0 -DA:627,0 -DA:628,0 -DA:629,0 -DA:631,0 -DA:632,0 -DA:634,0 -LF:224 +LF:231 LH:0 end_of_record +SF:lib/src/components/popup/_popup_header.dart +DA:12,2 +DA:23,2 +DA:25,6 +DA:29,4 +DA:30,3 +DA:33,4 +DA:34,2 +DA:36,2 +DA:37,2 +DA:38,2 +DA:43,2 +DA:44,5 +DA:45,1 +DA:46,2 +DA:47,2 +DA:48,2 +DA:59,1 +DA:61,1 +DA:62,2 +DA:73,2 +DA:81,2 +DA:83,2 +DA:84,4 +DA:85,2 +DA:86,4 +DA:87,2 +DA:88,2 +DA:94,2 +DA:95,2 +DA:96,4 +DA:97,2 +DA:98,4 +DA:99,2 +DA:100,1 +DA:101,3 +DA:102,2 +DA:103,2 +DA:106,2 +DA:110,2 +DA:111,4 +DA:112,4 +DA:113,2 +DA:114,4 +DA:115,2 +DA:116,1 +DA:117,3 +DA:118,2 +DA:119,2 +DA:122,2 +DA:126,2 +DA:131,2 +DA:132,4 +DA:133,3 +DA:135,6 +DA:136,2 +DA:137,8 +DA:138,2 +DA:139,2 +DA:142,2 +DA:145,2 +DA:146,4 +DA:147,3 +DA:149,6 +DA:150,2 +DA:151,8 +DA:152,2 +DA:153,2 +DA:157,2 +DA:163,2 +DA:174,2 +DA:176,2 +DA:177,2 +DA:178,4 +DA:179,2 +DA:180,4 +DA:181,4 +DA:182,2 +DA:187,4 +DA:188,3 +DA:189,4 +DA:190,2 +DA:192,2 +DA:193,2 +DA:195,2 +DA:198,2 +DA:199,5 +DA:200,4 +DA:205,2 +DA:208,2 +LF:89 +LH:89 +end_of_record +SF:lib/src/components/popup/t_popup_types.dart +DA:11,4 +DA:13,1 +LF:2 +LH:2 +end_of_record +SF:lib/src/components/popup/_popup_layout.dart +DA:7,3 +DA:26,3 +DA:27,6 +DA:30,3 +DA:33,3 +DA:34,3 +DA:35,3 +DA:36,3 +DA:37,3 +DA:38,3 +DA:39,3 +DA:40,3 +DA:41,3 +DA:44,3 +DA:45,3 +DA:46,6 +DA:47,2 +DA:48,2 +DA:49,2 +DA:50,2 +DA:56,2 +DA:57,2 +DA:58,2 +DA:59,2 +DA:64,1 +DA:65,2 +DA:66,1 +DA:67,1 +DA:68,1 +DA:71,3 +DA:72,3 +DA:73,3 +DA:74,3 +DA:75,3 +DA:76,3 +DA:79,3 +DA:80,2 +DA:81,2 +DA:82,2 +DA:83,2 +DA:84,2 +DA:87,3 +DA:88,3 +DA:89,3 +DA:90,3 +DA:91,6 +DA:92,6 +DA:100,3 +DA:101,3 +DA:102,3 +DA:104,6 +DA:105,12 +DA:110,2 +DA:111,2 +DA:112,2 +DA:114,2 +DA:116,2 +DA:118,2 +DA:120,1 +DA:125,3 +DA:126,3 +DA:127,3 +DA:128,6 +DA:129,3 +DA:130,6 +DA:131,2 +DA:132,4 +DA:133,2 +DA:134,4 +DA:135,1 +LF:70 +LH:70 +end_of_record +SF:lib/src/components/popup/_popup_route.dart +DA:12,2 +DA:15,2 +DA:16,2 +DA:18,2 +DA:19,2 +DA:20,2 +DA:33,2 +DA:34,4 +DA:37,4 +DA:38,4 +DA:39,3 +DA:40,3 +DA:45,2 +DA:46,4 +DA:48,2 +DA:51,2 +DA:54,2 +DA:57,2 +DA:63,2 +DA:66,2 +DA:67,4 +DA:70,2 +DA:71,2 +DA:74,2 +DA:75,5 +DA:76,5 +DA:79,0 +DA:88,2 +DA:95,2 +DA:101,2 +DA:102,4 +DA:103,4 +DA:104,2 +DA:105,4 +DA:106,4 +DA:107,4 +DA:109,10 +DA:112,2 +DA:113,2 +DA:114,2 +DA:115,2 +DA:119,6 +DA:120,2 +DA:126,2 +DA:127,4 +DA:132,4 +DA:134,2 +DA:136,2 +DA:138,2 +DA:139,6 +DA:140,6 +DA:141,1 +DA:142,2 +DA:147,2 +DA:148,2 +DA:150,2 +DA:151,2 +DA:152,4 +DA:153,6 +DA:157,4 +DA:158,2 +DA:163,2 +DA:164,2 +DA:165,0 +DA:170,1 +DA:171,3 +DA:172,2 +DA:173,2 +DA:177,2 +DA:178,4 +DA:179,2 +DA:180,5 +DA:182,4 +DA:183,2 +DA:184,5 +DA:188,2 +DA:189,2 +DA:192,2 +DA:196,2 +DA:197,4 +DA:198,4 +DA:199,2 +DA:203,2 +DA:205,5 +DA:206,5 +DA:207,2 +DA:208,4 +DA:212,2 +DA:214,2 +DA:215,2 +DA:218,2 +DA:220,2 +DA:221,6 +DA:223,2 +LF:94 +LH:92 +end_of_record +SF:lib/src/components/popup/_popup_shell.dart +DA:12,2 +DA:21,2 +DA:23,2 +DA:24,6 +DA:26,6 +DA:27,6 +DA:29,4 +DA:31,6 +DA:32,4 +DA:33,2 +DA:34,2 +DA:35,2 +DA:36,2 +DA:38,2 +DA:43,2 +DA:46,2 +DA:47,2 +DA:48,4 +DA:49,4 +DA:50,2 +DA:51,2 +DA:53,2 +DA:62,6 +DA:63,6 +DA:64,4 +DA:66,2 +DA:67,2 +DA:72,6 +DA:73,2 +DA:77,2 +DA:78,2 +DA:79,2 +DA:80,2 +DA:82,4 +DA:86,1 +DA:89,2 +DA:97,2 +DA:99,2 +DA:100,4 +DA:101,2 +DA:102,4 +DA:103,2 +DA:104,2 +DA:105,2 +DA:106,2 +DA:107,2 +DA:108,2 +LF:47 +LH:47 +end_of_record +SF:lib/src/components/popup/t_popup_tracker.dart +DA:7,0 +DA:9,6 +DA:11,2 +DA:12,10 +DA:15,2 +DA:16,6 +DA:17,6 +DA:18,4 +DA:22,2 +DA:23,4 +DA:24,2 +DA:27,2 +LF:12 +LH:11 +end_of_record SF:lib/src/components/progress/t_progress.dart DA:13,0 DA:17,0 @@ -10653,7 +10799,7 @@ LF:90 LH:0 end_of_record SF:lib/src/components/skeleton/t_skeleton_rowcol.dart -DA:7,1 +DA:7,4 DA:15,0 DA:16,0 DA:21,0 @@ -10667,11 +10813,11 @@ DA:41,0 DA:42,0 DA:43,0 DA:44,0 -DA:50,1 -DA:56,1 -DA:60,1 -DA:64,1 -DA:68,1 +DA:50,4 +DA:56,4 +DA:60,4 +DA:64,4 +DA:68,4 DA:79,0 DA:80,0 DA:83,0 @@ -10682,10 +10828,10 @@ DA:92,0 DA:95,0 DA:96,0 DA:101,0 -DA:110,1 -DA:119,1 -DA:128,1 -DA:137,1 +DA:110,4 +DA:119,4 +DA:128,4 +DA:137,4 DA:160,0 LF:34 LH:10 @@ -11044,7 +11190,7 @@ DA:523,0 DA:524,0 DA:525,0 DA:526,0 -DA:532,1 +DA:532,4 DA:534,0 DA:539,0 DA:561,0 @@ -12313,17 +12459,17 @@ LF:40 LH:0 end_of_record SF:lib/src/theme/basic.dart -DA:9,1 -DA:10,2 -DA:11,3 -DA:14,1 -DA:15,4 -DA:17,1 -DA:18,1 -DA:19,2 -DA:28,1 -DA:30,1 -DA:31,3 +DA:9,2 +DA:10,4 +DA:11,6 +DA:14,2 +DA:15,8 +DA:17,2 +DA:18,2 +DA:19,4 +DA:28,2 +DA:30,2 +DA:31,6 DA:36,0 DA:38,0 DA:39,0 @@ -12361,7 +12507,7 @@ DA:50,0 DA:52,0 DA:53,0 DA:54,0 -DA:88,1 +DA:88,4 DA:99,0 DA:101,0 DA:102,0 @@ -12402,7 +12548,7 @@ DA:158,0 DA:163,0 DA:164,0 DA:166,0 -DA:199,1 +DA:199,4 DA:211,0 DA:213,0 DA:214,0 @@ -12416,7 +12562,7 @@ DA:223,0 DA:225,0 DA:226,0 DA:227,0 -DA:251,1 +DA:251,4 DA:259,0 DA:261,0 DA:262,0 @@ -15477,19 +15623,19 @@ LF:176 LH:0 end_of_record SF:lib/src/theme/resource_delegate.dart -DA:20,0 -DA:21,0 +DA:20,2 +DA:21,2 DA:22,0 -DA:24,0 -DA:26,0 +DA:24,2 +DA:26,4 DA:31,0 DA:32,0 -DA:38,0 -DA:39,0 +DA:38,2 +DA:39,2 DA:44,0 -DA:47,0 -DA:48,0 -DA:49,0 +DA:47,2 +DA:48,2 +DA:49,2 DA:212,0 DA:215,0 DA:218,0 @@ -15542,7 +15688,7 @@ DA:356,0 DA:359,0 DA:362,0 LF:64 -LH:0 +LH:9 end_of_record SF:lib/src/theme/t_shadows.dart DA:7,0 @@ -15564,17 +15710,17 @@ LF:8 LH:0 end_of_record SF:lib/src/util/string_util.dart -DA:5,1 -DA:7,2 -DA:9,2 +DA:5,2 +DA:7,4 +DA:9,4 DA:14,0 -DA:17,2 -DA:18,1 -DA:20,1 -DA:23,2 -DA:24,1 -DA:27,1 -DA:30,2 +DA:17,4 +DA:18,2 +DA:20,2 +DA:23,4 +DA:24,2 +DA:27,2 +DA:30,4 LF:11 LH:10 end_of_record diff --git a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt index afd087518..88e3c0b48 100644 --- a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt +++ b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt @@ -9,33 +9,27 @@ Widget _buildCustomIndexes(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - modalTop: renderBox?.size.height, - builder: (context) { - return TIndexes( - indexList: indexList, - builderIndex: (context, index, isActive) { - return TText( - '自定义 ${index}', - textColor: isActive - ? TTheme.of(context).brandNormalColor - : TTheme.of(context).textColorPrimary, - ); - }, - builderContent: (context, index) { - final list = _list.firstWhere( - (element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list - .map((e) => TCell( - title: e, - )) - .toList(), - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderIndex: (context, index, isActive) { + return TText( + '自定义 $index', + textColor: isActive + ? TTheme.of(context).brandNormalColor + : TTheme.of(context).textColorPrimary, + ); + }, + builderContent: (context, index) { + final list = _list + .firstWhere((element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), ); }, ), diff --git a/tdesign-component/example/assets/code/indexes._buildOther.txt b/tdesign-component/example/assets/code/indexes._buildOther.txt index 64b0446c9..f871cd50d 100644 --- a/tdesign-component/example/assets/code/indexes._buildOther.txt +++ b/tdesign-component/example/assets/code/indexes._buildOther.txt @@ -9,26 +9,20 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - modalTop: renderBox?.size.height, - builder: (context) { - return TIndexes( - indexList: indexList, - capsuleTheme: true, - builderContent: (context, index) { - final list = _list.firstWhere( - (element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list - .map((e) => TCell( - title: e, - )) - .toList(), - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + capsuleTheme: true, + builderContent: (context, index) { + final list = _list + .firstWhere((element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), ); }, ), diff --git a/tdesign-component/example/assets/code/indexes._buildSimple.txt b/tdesign-component/example/assets/code/indexes._buildSimple.txt index 5cec08001..36e19e5a2 100644 --- a/tdesign-component/example/assets/code/indexes._buildSimple.txt +++ b/tdesign-component/example/assets/code/indexes._buildSimple.txt @@ -9,25 +9,19 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - modalTop: renderBox?.size.height, - builder: (context) { - return TIndexes( - indexList: indexList, - builderContent: (context, index) { - final list = _list.firstWhere( - (element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list - .map((e) => TCell( - title: e, - )) - .toList(), - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderContent: (context, index) { + final list = _list + .firstWhere((element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), ); }, ), diff --git a/tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt b/tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt new file mode 100644 index 000000000..a4dc51338 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt @@ -0,0 +1,23 @@ + + Widget _buildApiAutoCloseFalse(BuildContext context) { + return TButton( + text: 'autoCloseOnCancel/Confirm=false', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '不自动关闭', + autoCloseOnCancel: false, + autoCloseOnConfirm: false, + onCancel: () => + TToast.showText('已点取消', context: context), + onConfirm: () => + TToast.showText('已点确定', context: context), + child: _body(context, height: 140), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt b/tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt new file mode 100644 index 000000000..b4c4ef6fc --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt @@ -0,0 +1,18 @@ + + Widget _buildApiBackgroundColor(BuildContext context) { + return TButton( + text: 'backgroundColor + radius', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 260, + radius: 16, + backgroundColor: const Color(0xFFFFF8E7), + child: _body(context, height: 220), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt b/tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt new file mode 100644 index 000000000..63f3b0906 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt @@ -0,0 +1,21 @@ + + Widget _buildApiCancelBtnConfirmBtn(BuildContext context) { + return TButton( + text: 'cancelBtn / confirmBtn', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '自定义按钮文案', + cancelBtn: '暂不', + confirmBtn: '好的', + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: _body(context), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt b/tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt new file mode 100644 index 000000000..7a330e0a7 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt @@ -0,0 +1,30 @@ + + Widget _buildApiCancelConfirmBuilders(BuildContext context) { + return TButton( + text: 'cancelBuilder / confirmBuilder', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: 'Builder 插槽', + cancelBuilder: (ctx) => TText( + '自定义取消', + textColor: TTheme.of(ctx).textColorSecondary, + font: TTheme.of(ctx).fontBodyLarge, + ), + confirmBuilder: (ctx) => TText( + '自定义确定', + textColor: TTheme.of(ctx).brandNormalColor, + font: TTheme.of(ctx).fontTitleMedium, + fontWeight: FontWeight.w600, + ), + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: _body(context), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt b/tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt new file mode 100644 index 000000000..b13320e05 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt @@ -0,0 +1,23 @@ + + Widget _buildApiCancelConfirmWidgets(BuildContext context) { + return TButton( + text: 'cancel / confirm Widget', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: 'Widget 插槽', + cancel: Icon(Icons.undo, + color: TTheme.of(context).textColorSecondary), + confirm: + Icon(Icons.check, color: TTheme.of(context).brandNormalColor), + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: _body(context), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt b/tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt new file mode 100644 index 000000000..444de7c0b --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt @@ -0,0 +1,25 @@ + + Widget _buildApiCloseBuilder(BuildContext context) { + return TButton( + text: 'closeBuilder', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.center, + width: 260, + height: 160, + closeBuilder: (ctx) => TextButton( + onPressed: () => TPopup.close(ctx), + child: TText( + '关闭', + textColor: TTheme.of(ctx).fontWhColor1, + font: TTheme.of(ctx).fontBodyLarge, + ), + ), + child: _body(context, height: 120), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt b/tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt new file mode 100644 index 000000000..733170092 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt @@ -0,0 +1,17 @@ + + Widget _buildApiCloseOverlayClickFalse(BuildContext context) { + return TButton( + text: 'closeOnOverlayClick: false', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 260, + closeOnOverlayClick: false, + child: _body(context), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiDuration.txt b/tdesign-component/example/assets/code/popup._buildApiDuration.txt new file mode 100644 index 000000000..2a190439a --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiDuration.txt @@ -0,0 +1,22 @@ + + Widget _buildApiDuration(BuildContext context) { + return TButton( + text: 'duration: 600ms', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 240, + duration: const Duration(milliseconds: 600), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + ), + ); + }, + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt b/tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt new file mode 100644 index 000000000..4b1887c92 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt @@ -0,0 +1,26 @@ + + Widget _buildApiHeaderBuilder(BuildContext context) { + return TButton( + text: 'headerBuilder(非 closeBtn)', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + headerBuilder: (ctx) => Container( + height: _headerHeight, + alignment: Alignment.center, + color: TTheme.of(ctx).brandColor1, + child: TText( + '自定义头部', + textColor: TTheme.of(ctx).brandNormalColor, + font: TTheme.of(ctx).fontTitleMedium, + ), + ), + child: _body(context, height: 200), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt new file mode 100644 index 000000000..a7dbd5cd4 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt @@ -0,0 +1,25 @@ + + Widget _buildApiMarginTop(BuildContext context) { + return TButton( + text: 'bottom margin.top', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 320, + margin: const EdgeInsets.only(top: 120, left: 16, right: 16), + title: '日历式留白', + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: Container( + height: 240, + color: TTheme.of(context).bgColorContainer, + ), + ); + }, + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt new file mode 100644 index 000000000..a1f80b865 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt @@ -0,0 +1,23 @@ + + Widget _buildApiOnOverlayClick(BuildContext context) { + return TButton( + text: 'onOverlayClick', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 260, + onOverlayClick: () => + TToast.showText('点击蒙层', context: context), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + ), + ); + }, + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiOverlay.txt b/tdesign-component/example/assets/code/popup._buildApiOverlay.txt new file mode 100644 index 000000000..fa638be58 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiOverlay.txt @@ -0,0 +1,18 @@ + + Widget _buildApiOverlay(BuildContext context) { + return TButton( + text: 'overlayColor + overlayOpacity', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 260, + overlayColor: TTheme.of(context).brandNormalColor, + overlayOpacity: 0.35, + child: _body(context), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt new file mode 100644 index 000000000..d6968601d --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt @@ -0,0 +1,26 @@ + + Widget _buildApiShowOverlayFalse(BuildContext context) { + return TButton( + text: 'showOverlay: false(无蒙层)', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + showOverlay: false, + // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) + title: '无蒙层', + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + ), + ); + }, + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt b/tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt new file mode 100644 index 000000000..27a65a384 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt @@ -0,0 +1,20 @@ + + Widget _buildApiTitleAlignLeft(BuildContext context) { + return TButton( + text: 'titleAlignLeft', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '左对齐标题', + titleAlignLeft: true, + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: _body(context), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt b/tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt new file mode 100644 index 000000000..bdbc8078b --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt @@ -0,0 +1,31 @@ + + Widget _buildApiTitleWidget(BuildContext context) { + return TButton( + text: 'titleWidget', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + titleWidget: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(TIcons.info_circle, + color: TTheme.of(context).brandNormalColor), + const SizedBox(width: 4), + TText( + '自定义标题', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + ), + ], + ), + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: _body(context), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildBottomActionBar.txt b/tdesign-component/example/assets/code/popup._buildBottomActionBar.txt new file mode 100644 index 000000000..11eda7be2 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildBottomActionBar.txt @@ -0,0 +1,22 @@ + + Widget _buildBottomActionBar(BuildContext context) { + return TButton( + text: 'bottom 操作栏(title + onCancel/onConfirm)', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + onCancel: () => TPopup.close(context), + onConfirm: () { + TToast.showText('确定', context: context); + TPopup.close(context); + }, + child: _body(context, height: 200), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt b/tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt new file mode 100644 index 000000000..e48a77791 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt @@ -0,0 +1,17 @@ + + Widget _buildBottomTitleOnly(BuildContext context) { + return TButton( + text: 'bottom 仅标题(无操作按钮)', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + child: _body(context, height: 200), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildCenterClose.txt b/tdesign-component/example/assets/code/popup._buildCenterClose.txt new file mode 100644 index 000000000..c9045eef5 --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildCenterClose.txt @@ -0,0 +1,27 @@ + + Widget _buildCenterClose(BuildContext context) { + return TButton( + text: 'center close + onCloseBtn', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => TPopup.show( + context: context, + placement: TPopupPlacement.center, + width: 240, + height: 180, + close: IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).fontWhColor1, + size: 32, + ), + onPressed: () => TPopup.close(context), + ), + onCloseBtn: () => + TToast.showText('onCloseBtn', context: context), + child: _body(context, height: 160), + ), + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt index 39ec6d336..850617fca 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt @@ -7,15 +7,14 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 240, + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + ), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithClose.txt deleted file mode 100644 index 35edc505c..000000000 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithClose.txt +++ /dev/null @@ -1,24 +0,0 @@ - - Widget _buildPopFromBottomWithClose(BuildContext context) { - return TButton( - text: '底部弹出层-带关闭', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - closeClick: () { - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), - ); - }, - ); - } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndLeftTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndLeftTitle.txt deleted file mode 100644 index bdc0fdf7c..000000000 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndLeftTitle.txt +++ /dev/null @@ -1,26 +0,0 @@ - - Widget _buildPopFromBottomWithCloseAndLeftTitle(BuildContext context) { - return TButton( - text: '底部弹出层-带左边标题及关闭', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字', - titleLeft: true, - closeClick: () { - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), - ); - }, - ); - } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt index a8eba6f9c..a79e31363 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt @@ -7,18 +7,37 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字', - closeClick: () { - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + cancel: TText( + '关闭', + textColor: TTheme.of(context).textColorSecondary, + font: TTheme.of(context).fontBodyLarge, + ), + titleWidget: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(TIcons.info_circle, + color: TTheme.of(context).brandNormalColor, size: 18), + const SizedBox(width: 4), + TText( + '自定义标题', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + ), + ], + ), + confirm: TText( + '完成', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + fontWeight: FontWeight.w600, + ), + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: Container(height: 200), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt new file mode 100644 index 000000000..bc044696a --- /dev/null +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt @@ -0,0 +1,37 @@ + + Widget _buildPopFromBottomWithCustomAction(BuildContext context) { + return TButton( + text: '底部弹出层-操作栏全自定义', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + titleWidget: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(TIcons.info_circle, + color: TTheme.of(context).brandNormalColor, size: 18), + const SizedBox(width: 4), + TText( + '自定义标题', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + ), + ], + ), + cancel: Icon(Icons.undo, + color: TTheme.of(context).textColorSecondary), + confirm: Icon(Icons.check, + color: TTheme.of(context).brandNormalColor), + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: Container(height: 200), + ); + }, + ); + } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt index 9d37be37f..68c8cf39c 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt @@ -7,23 +7,18 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push(TSlidePopupRoute( - modalBarrierColor: TTheme.of(context).fontGyColor2, - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomConfirmPanel( - leftClick: () { - Navigator.maybePop(context); - }, - rightClick: () { - TToast.showText('确定', context: context); - Navigator.maybePop(context); - }, - child: Container( - height: 200, - ), - ); - })); + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + overlayColor: TTheme.of(context).fontGyColor2, + onCancel: () => TPopup.close(context), + onConfirm: () { + TToast.showText('确定', context: context); + TPopup.close(context); + }, + child: Container(height: 200), + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt index dee0cb931..e98e490d3 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt @@ -7,23 +7,17 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomConfirmPanel( - title: '标题文字', - leftClick: () { - Navigator.maybePop(context); - }, - rightClick: () { - TToast.showText('确定', context: context); - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }, - ), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + onCancel: () => TPopup.close(context), + onConfirm: () { + TToast.showText('确定', context: context); + TPopup.close(context); + }, + child: Container(height: 200), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt index e88aae7e1..bce3f8e95 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt @@ -7,19 +7,12 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字', - hideClose: true, - // closeClick: () { - // Navigator.maybePop(context); - // }, - child: Container(height: 200), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + child: Container(height: 200), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt index 2262272ba..2f03be28c 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt @@ -7,20 +7,19 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return Container( - decoration: BoxDecoration( - color: TTheme.of(context).bgColorContainer, - borderRadius: - BorderRadius.circular(TTheme.of(context).radiusLarge), - ), - width: 240, - height: 240, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.center, + closeBtn: false, + child: Container( + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: + BorderRadius.circular(TTheme.of(context).radiusLarge), + ), + width: 240, + height: 240, + ), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt index 08f3aa831..3e6bd6907 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt @@ -7,18 +7,21 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - isDismissible: false, - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return TPopupCenterPanel( - closeClick: () { - Navigator.maybePop(context); - }, - child: const SizedBox(width: 240, height: 240), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.center, + closeOnOverlayClick: false, + width: 240, + height: 240, + close: IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).fontWhColor1, + size: 32, + ), + onPressed: () => TPopup.close(context), + ), + child: const SizedBox(width: 240, height: 240), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt index e22ac50fe..255768cba 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt @@ -1,25 +1,31 @@ Widget _buildPopFromCenterWithUnderClose(BuildContext context) { return TButton( - text: '居中弹出层-关闭在下方', + text: '居中弹出层-自定义下方按钮', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - isDismissible: false, - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return TPopupCenterPanel( - closeUnderBottom: true, - closeClick: () { - Navigator.maybePop(context); - }, - child: const SizedBox(width: 240, height: 240), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.center, + closeOnOverlayClick: true, + width: 240, + height: 200, + close: IconButton( + icon: Icon( + TIcons.poweroff, + color: TTheme.of(context).fontWhColor1, + size: 36, + ), + onPressed: () => TPopup.close(context), + ), + child: Container( + width: 240, + height: 200, + color: TTheme.of(context).bgColorContainer, + ), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt index fba388fa5..bc5825c30 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt @@ -7,15 +7,13 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.left, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - width: 280, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.left, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + ), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt index 650e05195..ab28e32fd 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt @@ -7,15 +7,13 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - width: 280, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + ), ); }, ); diff --git a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt index f0675a65a..9ea230450 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt @@ -7,21 +7,16 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.top, - open: () { - print('open'); - }, - opened: () { - print('opened'); - }, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.top, + height: 240, + onOpen: () => print('open'), + onOpened: () => print('opened'), + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + ), ); }, ); diff --git a/tdesign-component/example/lib/component_test/popup_test.dart b/tdesign-component/example/lib/component_test/popup_test.dart index 7aeb93e9d..c36ea65b9 100644 --- a/tdesign-component/example/lib/component_test/popup_test.dart +++ b/tdesign-component/example/lib/component_test/popup_test.dart @@ -9,7 +9,7 @@ class ConfirmDialogTestApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'confirmDialog 测试示例', + title: 'TPopup 测试示例', theme: ThemeData(primarySwatch: Colors.blue), home: const TestPage(), ); @@ -24,35 +24,23 @@ class TestPage extends StatefulWidget { } class _TestPageState extends State { - final TextEditingController _searchNameController = TextEditingController(); - final TextEditingController _searchRemarkController = TextEditingController(); - void _showProblemDialog() { - - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: 'title', - radius: 20, - backgroundColor: const Color(0xFFFAFFFC), - closeClick: () { - Navigator.maybePop(context); - }, - child: Container( - padding: const EdgeInsets.only(left: 20, right: 20, bottom: 33), - decoration: const BoxDecoration(color: Colors.white), - child: const Column( - children: [ - Center( - child: Text("立即拨打"), - ), - ], - ), - ), - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + title: 'title', + radius: 20, + backgroundColor: const Color(0xFFFAFFFC), + closeBtn: true, + onCloseBtn: () => TPopup.close(context), + child: Container( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 33), + decoration: const BoxDecoration(color: Colors.white), + child: const Column( + children: [ + Center(child: Text('立即拨打')), + ], + ), ), ); } @@ -60,7 +48,7 @@ class _TestPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('TConfirmDialog测试')), + appBar: AppBar(title: const Text('TPopup测试')), body: Center( child: TButton( child: const Text('显示问题弹窗'), @@ -69,11 +57,4 @@ class _TestPageState extends State { ), ); } - - @override - void dispose() { - _searchNameController.dispose(); - _searchRemarkController.dispose(); - super.dispose(); - } -} \ No newline at end of file +} diff --git a/tdesign-component/example/lib/page/t_indexes_page.dart b/tdesign-component/example/lib/page/t_indexes_page.dart index caf9b55e9..b889d5603 100644 --- a/tdesign-component/example/lib/page/t_indexes_page.dart +++ b/tdesign-component/example/lib/page/t_indexes_page.dart @@ -157,25 +157,19 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - modalTop: renderBox?.size.height, - builder: (context) { - return TIndexes( - indexList: indexList, - builderContent: (context, index) { - final list = _list.firstWhere( - (element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list - .map((e) => TCell( - title: e, - )) - .toList(), - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderContent: (context, index) { + final list = _list + .firstWhere((element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), ); }, ), @@ -195,26 +189,20 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - modalTop: renderBox?.size.height, - builder: (context) { - return TIndexes( - indexList: indexList, - capsuleTheme: true, - builderContent: (context, index) { - final list = _list.firstWhere( - (element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list - .map((e) => TCell( - title: e, - )) - .toList(), - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + capsuleTheme: true, + builderContent: (context, index) { + final list = _list + .firstWhere((element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), ); }, ), @@ -234,33 +222,27 @@ Widget _buildCustomIndexes(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - modalTop: renderBox?.size.height, - builder: (context) { - return TIndexes( - indexList: indexList, - builderIndex: (context, index, isActive) { - return TText( - '自定义 ${index}', - textColor: isActive - ? TTheme.of(context).brandNormalColor - : TTheme.of(context).textColorPrimary, - ); - }, - builderContent: (context, index) { - final list = _list.firstWhere( - (element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list - .map((e) => TCell( - title: e, - )) - .toList(), - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderIndex: (context, index, isActive) { + return TText( + '自定义 $index', + textColor: isActive + ? TTheme.of(context).brandNormalColor + : TTheme.of(context).textColorPrimary, + ); + }, + builderContent: (context, index) { + final list = _list + .firstWhere((element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), ); }, ), diff --git a/tdesign-component/example/lib/page/t_picker_page.dart b/tdesign-component/example/lib/page/t_picker_page.dart index 744b06aa0..3eba316c8 100644 --- a/tdesign-component/example/lib/page/t_picker_page.dart +++ b/tdesign-component/example/lib/page/t_picker_page.dart @@ -184,15 +184,16 @@ class _TPickerPageState extends State { /// TPicker 自带「取消 / 标题 / 确认」工具栏,业务方在 onCancel/onConfirm /// 中自行决定是否调用 Navigator.pop 关闭弹窗。 void _showPickerPopup(BuildContext context, {required Widget picker}) { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (ctx) => Material( - color: TTheme.of(ctx).bgColorContainer, - child: SafeArea( - top: false, - child: picker, - ), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + child: Material( + color: TTheme.of(context).bgColorContainer, + child: SafeArea( + top: false, + child: picker, ), ), ); diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart index 892855a8c..2aa2328da 100644 --- a/tdesign-component/example/lib/page/t_popup_page.dart +++ b/tdesign-component/example/lib/page/t_popup_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; + import '../annotation/demo.dart'; import '../base/example_widget.dart'; @@ -9,6 +10,41 @@ import '../base/example_widget.dart'; class TPopupPage extends StatelessWidget { const TPopupPage({super.key}); + static const double _headerHeight = 58; + + /// 底部标题 + 关闭(headerBuilder,非 closeBtn)。 + static WidgetBuilder _bottomTitleCloseHeader({required String title}) { + return (BuildContext ctx) { + final theme = TTheme.of(ctx); + return SizedBox( + height: _headerHeight, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + children: [ + Expanded( + child: Center( + child: TText( + title, + textColor: theme.textColorPrimary, + font: theme.fontTitleLarge, + fontWeight: FontWeight.w700, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + IconButton( + icon: Icon(TIcons.close, color: theme.textColorSecondary), + onPressed: () => TPopup.close(ctx), + ), + ], + ), + ), + ); + }; + } + @override Widget build(BuildContext context) { return ExamplePage( @@ -32,438 +68,223 @@ class TPopupPage extends StatelessWidget { title: '组件示例', children: [ ExampleItem(builder: _buildPopFromBottomWithOperationAndTitle), - ExampleItem(builder: _buildPopFromBottomWithOperation), ExampleItem(builder: _buildPopFromBottomWithCloseAndTitle), - ExampleItem(builder: _buildPopFromBottomWithCloseAndLeftTitle), - ExampleItem(builder: _buildPopFromBottomWithClose), - ExampleItem(builder: _buildPopFromBottomWithTitle), ExampleItem(builder: _buildPopFromCenterWithClose), ExampleItem(builder: _buildPopFromCenterWithUnderClose), ], ), + ExampleModule( + title: '更多 API', + children: [ + ExampleItem(builder: _buildApiMarginTop), + ExampleItem(builder: _buildApiShowOverlayFalse), + ExampleItem(builder: _buildApiOnOverlayClick), + ExampleItem(builder: _buildApiDuration), + ], + ), ], test: [ ExampleItem( - desc: '操作栏超长文本,指定颜色', - builder: (_) { - return TButton( - text: '底部弹出层-带标题及操作', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomConfirmPanel( - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - leftText: '点这里确认!', - leftTextColor: TTheme.of(context).brandNormalColor, - leftClick: () { - TToast.showText('确认', context: context); - Navigator.maybePop(context); - }, - rightText: '关闭', - rightTextColor: TTheme.of(context).errorNormalColor, - rightClick: () => Navigator.maybePop(context), - child: Container(height: 200), - ); - }, - ), - ); - }, - ); - }), - ExampleItem( - desc: '带关闭超长文本', - builder: (_) { - return TButton( - text: '底部弹出层-带标题及操作', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - closeColor: TTheme.of(context).errorNormalColor, - closeClick: () => Navigator.maybePop(context), - child: Container(height: 200), - ); - }), - ); - }, - ); - }), - ExampleItem( - desc: '修改圆角', - builder: (_) { - return Column( - // spacing: 16, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - TButton( - text: '底部弹出层-修改圆角', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - closeColor: - TTheme.of(context).errorNormalColor, - closeClick: () => Navigator.maybePop(context), - child: Container(height: 200), - radius: 6, - ); - }), - ); - }, - ), - const SizedBox(height: 16), - TButton( - text: '底部弹出层-修改圆角', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomConfirmPanel( - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - leftText: '点这里确认!', - leftTextColor: - TTheme.of(context).brandNormalColor, - leftClick: () { - TToast.showText('确认', context: context); - Navigator.maybePop(context); - }, - rightText: '关闭', - rightTextColor: - TTheme.of(context).errorNormalColor, - rightClick: () => Navigator.maybePop(context), - child: Container(height: 200), - radius: 6, - ); - }), - ); - }, - ), - const SizedBox(height: 16), - TButton( - text: '居中弹出层-修改圆角', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return TPopupCenterPanel( - closeColor: - TTheme.of(context).errorNormalColor, - closeClick: () { - Navigator.maybePop(context); - }, - child: const SizedBox(height: 240, width: 240), - radius: 6, - ); - }), - ); - }, + desc: '操作栏超长文本,指定颜色', + builder: (_) { + return TButton( + text: '底部弹出层-带标题及操作', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', + cancel: TText( + '点这里确认!', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontBodyLarge, ), - const SizedBox(height: 16), - TButton( - text: '居中弹出层-底部关闭-修改圆角', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return TPopupCenterPanel( - closeUnderBottom: true, - closeClick: () { - Navigator.maybePop(context); - }, - child: const SizedBox(height: 240, width: 240), - radius: 6, - ); - }), - ); - }, + confirm: TText( + '关闭', + textColor: TTheme.of(context).errorNormalColor, + font: TTheme.of(context).fontBodyLarge, ), - ], - ); - }), + onCancel: () { + TToast.showText('确认', context: context); + TPopup.close(context); + }, + onConfirm: () => TPopup.close(context), + child: Container(height: 200), + ); + }, + ); + }, + ), ExampleItem( - desc: '自定义位置', + desc: '带关闭超长文本', builder: (_) { return TButton( - text: '自定义位置', + text: '底部弹出层-带标题及关闭', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - var renderBox = - navBarkey.currentContext!.findRenderObject() as RenderBox; - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - modalTop: renderBox.size.height, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - width: 280, - ); - }, + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + headerBuilder: _bottomTitleCloseHeader( + title: + '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', ), + child: Container(height: 200), ); }, ); }, ), ExampleItem( - desc: '弹出层包含输入框且不会被键盘遮挡', - builder: (_) { - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceAround, - // spacing: 16, - children: [ - TButton( - text: '底部弹出层-键盘弹出默认遮挡', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - closeColor: - TTheme.of(context).errorNormalColor, - closeClick: () => Navigator.maybePop(context), - child: Material( - child: SizedBox( - height: 100, - child: TInput( - type: TInputType.normal, - leftLabel: '标签文字', - hintText: '请输入文字', - maxLength: 10, - additionInfo: '最大输入10个字符', - ), - ), - ), - radius: 6, - ); - }), - ); - }, - ), - const SizedBox(height: 16), - TButton( - text: '底部弹出层-键盘弹出不遮挡', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - focusMove: true, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - closeColor: - TTheme.of(context).errorNormalColor, - closeClick: () { - Navigator.maybePop(context); - }, - child: Material( - child: SizedBox( - height: 100, - child: TInput( - type: TInputType.normal, - leftLabel: '标签文字', - hintText: '请输入文字', - maxLength: 10, - additionInfo: '最大输入10个字符', - ), - ), - ), - radius: 6, - ); - }), - ); - }, - ), - const SizedBox(height: 16), - TButton( - text: '居中弹出层-键盘弹出不遮挡', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.center, - focusMove: true, - builder: (context) { - return TPopupCenterPanel( - closeColor: - TTheme.of(context).errorNormalColor, - closeClick: () { - Navigator.maybePop(context); - }, - child: SizedBox( - height: 348, - child: Column( - children: [ - TInput( - type: TInputType.normal, - leftLabel: '标签文字1', - hintText: '请输入文字1', - maxLength: 10, - ), - TInput( - type: TInputType.normal, - leftLabel: '标签文字2', - hintText: '请输入文字2', - maxLength: 10, - ), - TInput( - type: TInputType.normal, - leftLabel: '标签文字3', - hintText: '请输入文字3', - maxLength: 10, - ), - TInput( - type: TInputType.normal, - leftLabel: '标签文字4', - hintText: '请输入文字4', - maxLength: 10, - ), - TInput( - type: TInputType.normal, - leftLabel: '会被键盘遮挡的输入框1', - hintText: '会被键盘遮挡小部分', - maxLength: 10, - ), - TInput( - type: TInputType.normal, - leftLabel: '会被键盘遮挡的输入框2', - hintText: '会被键盘遮挡全遮挡', - maxLength: 10, - ) - ], - ), - ), - radius: 6, - ); - }), - ); - }, - ) - ], - ); - }), - ExampleItem( - /// todo fix 动画闪烁 - desc: '可拖动全屏', + desc: '修改圆角', builder: (_) { return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceAround, - // spacing: 16, - children: [ - TButton( - text: '可拖动全屏', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字', - draggable: true, - closeColor: - TTheme.of(context).errorNormalColor, - closeClick: () { - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), - ); - }, - ), - const SizedBox(height: 16), - TButton( - text: '可拖动全屏-带标题及操作', - isBlock: true, - theme: TButtonTheme.primary, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomConfirmPanel( - title: '标题文字', - draggable: true, - leftClick: () { - Navigator.maybePop(context); - }, - rightClick: () { - TToast.showText('确定', context: context); - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), - ); - }, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TButton( + text: '底部弹出层-修改圆角', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + radius: 6, + headerBuilder: _bottomTitleCloseHeader( + title: + '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', + ), + child: Container(height: 200), + ); + }, + ), + const SizedBox(height: 16), + TButton( + text: '底部弹出层-修改圆角', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + radius: 6, + title: + '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', + cancel: TText( + '点这里确认!', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), + confirm: TText( + '关闭', + textColor: TTheme.of(context).errorNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), + onCancel: () { + TToast.showText('确认', context: context); + TPopup.close(context); + }, + onConfirm: () => TPopup.close(context), + child: Container(height: 200), + ); + }, + ), + const SizedBox(height: 16), + TButton( + text: '居中弹出层-修改圆角', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.center, + width: 240, + height: 240, + radius: 6, + close: IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).errorNormalColor, + size: 32, + ), + onPressed: () => TPopup.close(context), + ), + child: const SizedBox(height: 240, width: 240), + ); + }, + ), + const SizedBox(height: 16), + TButton( + text: '居中弹出层-底部关闭-修改圆角', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + TPopup.show( + context: context, + placement: TPopupPlacement.center, + width: 240, + height: 240, + radius: 6, + onCloseBtn: () => TPopup.close(context), + child: const SizedBox(height: 240, width: 240), + ); + }, + ), + ], + ); + }, + ), + ExampleItem( + desc: '自定义位置', + builder: (_) { + return TButton( + text: '自定义位置', + isBlock: true, + theme: TButtonTheme.primary, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () { + final renderBox = + navBarkey.currentContext!.findRenderObject() as RenderBox; + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox.size.height), + child: Container( + color: TTheme.of(context).bgColorContainer, ), - ]); + ); + }, + ); }, ), ], ); } + // --- 01 组件类型(保持原 Demo 文案与交互)--- + @Demo(group: 'popup') Widget _buildPopFromTop(BuildContext context) { return TButton( @@ -473,21 +294,16 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.top, - open: () { - print('open'); - }, - opened: () { - print('opened'); - }, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.top, + height: 240, + onOpen: () => print('open'), + onOpened: () => print('opened'), + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + ), ); }, ); @@ -502,15 +318,13 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.left, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - width: 280, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.left, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + ), ); }, ); @@ -525,20 +339,19 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return Container( - decoration: BoxDecoration( - color: TTheme.of(context).bgColorContainer, - borderRadius: - BorderRadius.circular(TTheme.of(context).radiusLarge), - ), - width: 240, - height: 240, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.center, + closeBtn: false, + child: Container( + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: + BorderRadius.circular(TTheme.of(context).radiusLarge), + ), + width: 240, + height: 240, + ), ); }, ); @@ -553,15 +366,14 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 240, + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + ), ); }, ); @@ -576,20 +388,20 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.right, - builder: (context) { - return Container( - color: TTheme.of(context).bgColorContainer, - width: 280, - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.right, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + ), ); }, ); } + // --- 02 组件示例 --- + @Demo(group: 'popup') Widget _buildPopFromBottomWithOperationAndTitle(BuildContext context) { return TButton( @@ -599,211 +411,226 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomConfirmPanel( - title: '标题文字', - leftClick: () { - Navigator.maybePop(context); - }, - rightClick: () { - TToast.showText('确定', context: context); - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }, - ), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + onCancel: () => TPopup.close(context), + onConfirm: () { + TToast.showText('确定', context: context); + TPopup.close(context); + }, + child: Container(height: 200), ); }, ); } @Demo(group: 'popup') - Widget _buildPopFromBottomWithOperation(BuildContext context) { + Widget _buildPopFromBottomWithCloseAndTitle(BuildContext context) { return TButton( - text: '底部弹出层-带操作', + text: '底部弹出层-带标题及关闭', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push(TSlidePopupRoute( - modalBarrierColor: TTheme.of(context).fontGyColor2, - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomConfirmPanel( - leftClick: () { - Navigator.maybePop(context); - }, - rightClick: () { - TToast.showText('确定', context: context); - Navigator.maybePop(context); - }, - child: Container( - height: 200, - ), - ); - })); + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + cancel: TText( + '关闭', + textColor: TTheme.of(context).textColorSecondary, + font: TTheme.of(context).fontBodyLarge, + ), + titleWidget: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(TIcons.info_circle, + color: TTheme.of(context).brandNormalColor, size: 18), + const SizedBox(width: 4), + TText( + '自定义标题', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + ), + ], + ), + confirm: TText( + '完成', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + fontWeight: FontWeight.w600, + ), + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: Container(height: 200), + ); }, ); } @Demo(group: 'popup') - Widget _buildPopFromBottomWithCloseAndTitle(BuildContext context) { + Widget _buildPopFromCenterWithClose(BuildContext context) { return TButton( - text: '底部弹出层-带标题及关闭', + text: '居中弹出层-带关闭', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字', - closeClick: () { - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.center, + closeOnOverlayClick: false, + width: 240, + height: 240, + close: IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).fontWhColor1, + size: 32, + ), + onPressed: () => TPopup.close(context), + ), + child: const SizedBox(width: 240, height: 240), ); }, ); } @Demo(group: 'popup') - Widget _buildPopFromBottomWithCloseAndLeftTitle(BuildContext context) { + Widget _buildPopFromCenterWithUnderClose(BuildContext context) { return TButton( - text: '底部弹出层-带左边标题及关闭', + text: '居中弹出层-自定义下方按钮', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字', - titleLeft: true, - closeClick: () { - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.center, + closeOnOverlayClick: true, + width: 240, + height: 200, + close: IconButton( + icon: Icon( + TIcons.poweroff, + color: TTheme.of(context).fontWhColor1, + size: 36, + ), + onPressed: () => TPopup.close(context), + ), + child: Container( + width: 240, + height: 200, + color: TTheme.of(context).bgColorContainer, + ), ); }, ); } + // --- 更多 API --- + @Demo(group: 'popup') - Widget _buildPopFromBottomWithClose(BuildContext context) { + Widget _buildApiMarginTop(BuildContext context) { return TButton( - text: '底部弹出层-带关闭', + text: 'bottom margin.top', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - closeClick: () { - Navigator.maybePop(context); - }, - child: Container(height: 200), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 320, + margin: const EdgeInsets.only(top: 120, left: 16, right: 16), + title: '日历式留白', + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: Container( + height: 240, + color: TTheme.of(context).bgColorContainer, + ), ); }, ); } @Demo(group: 'popup') - Widget _buildPopFromBottomWithTitle(BuildContext context) { + Widget _buildApiShowOverlayFalse(BuildContext context) { return TButton( - text: '底部弹出层-仅标题', + text: 'showOverlay: false(无蒙层)', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - builder: (context) { - return TPopupBottomDisplayPanel( - title: '标题文字', - hideClose: true, - // closeClick: () { - // Navigator.maybePop(context); - // }, - child: Container(height: 200), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 280, + showOverlay: false, + // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) + title: '无蒙层', + onCancel: () => TPopup.close(context), + onConfirm: () => TPopup.close(context), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + ), ); }, ); } @Demo(group: 'popup') - Widget _buildPopFromCenterWithClose(BuildContext context) { + Widget _buildApiOnOverlayClick(BuildContext context) { return TButton( - text: '居中弹出层-带关闭', + text: 'onOverlayClick', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - isDismissible: false, - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return TPopupCenterPanel( - closeClick: () { - Navigator.maybePop(context); - }, - child: const SizedBox(width: 240, height: 240), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 260, + onOverlayClick: () => + TToast.showText('点击蒙层', context: context), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + ), ); }, ); } @Demo(group: 'popup') - Widget _buildPopFromCenterWithUnderClose(BuildContext context) { + Widget _buildApiDuration(BuildContext context) { return TButton( - text: '居中弹出层-关闭在下方', + text: 'duration: 600ms', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, size: TButtonSize.large, onTap: () { - Navigator.of(context).push( - TSlidePopupRoute( - isDismissible: false, - slideTransitionFrom: SlideTransitionFrom.center, - builder: (context) { - return TPopupCenterPanel( - closeUnderBottom: true, - closeClick: () { - Navigator.maybePop(context); - }, - child: const SizedBox(width: 240, height: 240), - ); - }), + TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + height: 240, + duration: const Duration(milliseconds: 600), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + ), ); }, ); diff --git a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart index 9117f6a3d..f48700fe1 100644 --- a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart +++ b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import '../../util/context_extension.dart'; -import '../popup/t_popup_route.dart'; +import '../popup/t_popup.dart'; import 't_action_sheet.dart'; import 't_action_sheet_grid.dart'; import 't_action_sheet_group.dart'; @@ -112,7 +112,7 @@ class TActionSheet { /// 使用安全区域 final bool useSafeArea; - static TSlidePopupRoute? _actionSheetRoute; + static TPopupHandle? _actionSheetHandle; /// 显示列表类型面板 static void showListActionSheet( @@ -253,9 +253,7 @@ class TActionSheet { @mustCallSuper void close() { - if (_actionSheetRoute != null) { - Navigator.of(context).pop(); - } + _actionSheetHandle?.close(); } /// 创建路由 @@ -280,66 +278,69 @@ class TActionSheet { VoidCallback? onClose, bool useSafeArea = true, }) { - if (_actionSheetRoute != null) { + if (_actionSheetHandle?.isShowing == true) { return; } cancelText = cancelText ?? context.resource.cancel; - _actionSheetRoute = TSlidePopupRoute( - slideTransitionFrom: SlideTransitionFrom.bottom, - isDismissible: showOverlay ? closeOnOverlayClick : false, - modalBarrierColor: showOverlay ? null : Colors.transparent, - builder: (context) { - switch (theme) { - case TActionSheetTheme.list: - return TActionSheetList( - items: items, - align: align, - cancelText: cancelText, - description: description, - showCancel: showCancel, - onCancel: onCancel, - onSelected: onSelected, - useSafeArea: useSafeArea, - ); - case TActionSheetTheme.grid: - return TActionSheetGrid( - items: items, - align: align, - onSelected: onSelected, - showCancel: showCancel, - showPagination: showPagination, - scrollable: scrollable, - cancelText: cancelText, - description: description, - count: count, - rows: rows, - onCancel: onCancel, - itemHeight: itemHeight, - itemMinWidth: itemMinWidth, - useSafeArea: useSafeArea, - ); - case TActionSheetTheme.group: - return TActionSheetGroup( - items: items, - align: align, - cancelText: cancelText, - showCancel: showCancel, - onCancel: onCancel, - onSelected: onSelected, - itemHeight: itemHeight, - itemMinWidth: itemMinWidth, - useSafeArea: useSafeArea, - ); - default: - return const SizedBox.shrink(); - } - }, + Widget sheetChild; + switch (theme) { + case TActionSheetTheme.list: + sheetChild = TActionSheetList( + items: items, + align: align, + cancelText: cancelText, + description: description, + showCancel: showCancel, + onCancel: onCancel, + onSelected: onSelected, + useSafeArea: useSafeArea, + ); + break; + case TActionSheetTheme.grid: + sheetChild = TActionSheetGrid( + items: items, + align: align, + onSelected: onSelected, + showCancel: showCancel, + showPagination: showPagination, + scrollable: scrollable, + cancelText: cancelText, + description: description, + count: count, + rows: rows, + onCancel: onCancel, + itemHeight: itemHeight, + itemMinWidth: itemMinWidth, + useSafeArea: useSafeArea, + ); + break; + case TActionSheetTheme.group: + sheetChild = TActionSheetGroup( + items: items, + align: align, + cancelText: cancelText, + showCancel: showCancel, + onCancel: onCancel, + onSelected: onSelected, + itemHeight: itemHeight, + itemMinWidth: itemMinWidth, + useSafeArea: useSafeArea, + ); + break; + } + + _actionSheetHandle = TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + showOverlay: showOverlay, + closeOnOverlayClick: showOverlay && closeOnOverlayClick, + overlayColor: showOverlay ? null : Colors.transparent, + onClosed: onClose, + child: sheetChild, ); - Navigator.of(context).push(_actionSheetRoute!).then((_) { - _actionSheetRoute = null; - onClose?.call(); - }); } } diff --git a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart index b31fb034f..9d09dfefe 100644 --- a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart +++ b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart @@ -53,7 +53,7 @@ class TCalendarPopup { /// 点击确认按钮时触发 final void Function(List value)? onConfirm; - static TSlidePopupRoute? _calendarPopup; + static TPopupHandle? _calendarHandle; /// 当前选中值 final ValueNotifier> _selected = ValueNotifier>([]); @@ -65,33 +65,32 @@ class TCalendarPopup { /// 打开日历 void show() { - if (_calendarPopup != null) { + if (_calendarHandle?.isShowing == true) { return; } - _calendarPopup = TSlidePopupRoute( - isDismissible: false, - slideTransitionFrom: SlideTransitionFrom.bottom, - modalTop: top, - barrierClick: () { + final childWidget = builder?.call(context) ?? child; + _calendarHandle = TPopup.show( + context: context, + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + margin: EdgeInsets.only(top: top ?? 0), + closeOnOverlayClick: false, + onOverlayClick: () { if (_autoClose) { close(); } }, - builder: (context) { - final childWidget = builder?.call(context) ?? child; - return TCalendarInherited( - selected: _selected, - usePopup: true, - confirmBtn: confirmBtn, - onClose: _onClose, - onConfirm: _onConfirm, - child: childWidget!, - ); - }, + onClosed: _deleteRouter, + child: TCalendarInherited( + selected: _selected, + usePopup: true, + confirmBtn: confirmBtn, + onClose: _onClose, + onConfirm: _onConfirm, + child: childWidget!, + ), ); - Navigator.of(context).push(_calendarPopup!).then((_) { - _deleteRouter(); - }); } void _onClose() { @@ -109,14 +108,11 @@ class TCalendarPopup { /// 关闭日历 void close() { - if (_calendarPopup != null) { - Navigator.of(context).pop(); - // _deleteRouter(); - } + _calendarHandle?.close(); } void _deleteRouter() { - _calendarPopup = null; + _calendarHandle = null; onClose?.call(); } } diff --git a/tdesign-component/lib/src/components/drawer/t_drawer.dart b/tdesign-component/lib/src/components/drawer/t_drawer.dart index f4a4fea24..534183af9 100644 --- a/tdesign-component/lib/src/components/drawer/t_drawer.dart +++ b/tdesign-component/lib/src/components/drawer/t_drawer.dart @@ -7,7 +7,7 @@ import '../cell/t_cell.dart'; import '../cell/t_cell_group.dart'; import '../cell/t_cell_style.dart'; import '../icon/t_icons.dart'; -import '../popup/t_popup_route.dart'; +import '../popup/t_popup.dart'; import 't_drawer_widget.dart'; /// 抽屉方向 @@ -98,45 +98,42 @@ class TDrawer { /// 是否显示最后一行分割线 final bool? isShowLastBordered; - TSlidePopupRoute? _drawerRoute; + TPopupHandle? _drawerHandle; void show() { - if (_drawerRoute != null) { - return; // 如果抽屉已经显示了,就不要再显示 + if (_drawerHandle?.isShowing == true) { + return; } final overlayEnabled = showOverlay ?? true; final dismissible = overlayEnabled && (closeOnOverlayClick ?? true); - _drawerRoute = TSlidePopupRoute( - slideTransitionFrom: placement == TDrawerPlacement.right - ? SlideTransitionFrom.right - : SlideTransitionFrom.left, - isDismissible: dismissible, - modalBarrierColor: overlayEnabled ? null : Colors.transparent, - modalTop: drawerTop, - builder: (context) { - return TDrawerWidget( - footer: footer, - items: items, - contentWidget: contentWidget, - title: title, - titleWidget: titleWidget, - onItemClick: onItemClick, - width: width, - style: style, - hover: hover, - backgroundColor: backgroundColor, - bordered: bordered, - isShowLastBordered: isShowLastBordered, - ); - }, + _drawerHandle = TPopup.show( + context: context, + placement: placement == TDrawerPlacement.right + ? TPopupPlacement.right + : TPopupPlacement.left, + width: width, + margin: EdgeInsets.only(top: drawerTop ?? 0), + showOverlay: overlayEnabled, + closeOnOverlayClick: dismissible, + overlayColor: overlayEnabled ? null : Colors.transparent, + onClosed: _deleteRouter, + child: TDrawerWidget( + footer: footer, + items: items, + contentWidget: contentWidget, + title: title, + titleWidget: titleWidget, + onItemClick: onItemClick, + width: width, + style: style, + hover: hover, + backgroundColor: backgroundColor, + bordered: bordered, + isShowLastBordered: isShowLastBordered, + ), ); - - Navigator.of(context).push(_drawerRoute!).then((_) { - // 当抽屉关闭时,将_drawerRoute置为null - _deleteRouter(); - }); } void open() { @@ -145,14 +142,11 @@ class TDrawer { @mustCallSuper void close() { - if (_drawerRoute != null) { - Navigator.of(context).pop(); - _deleteRouter(); - } + _drawerHandle?.close(); } void _deleteRouter() { - _drawerRoute = null; + _drawerHandle = null; onClose?.call(); } } diff --git a/tdesign-component/lib/src/components/image_viewer/t_image_viewer.dart b/tdesign-component/lib/src/components/image_viewer/t_image_viewer.dart index afb7b4906..d22b14063 100644 --- a/tdesign-component/lib/src/components/image_viewer/t_image_viewer.dart +++ b/tdesign-component/lib/src/components/image_viewer/t_image_viewer.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import '../../theme/t_colors.dart'; import '../../theme/t_theme.dart'; -import '../popup/t_popup_route.dart'; import 't_image_viewer_widget.dart'; /// 图片预览工具 diff --git a/tdesign-component/lib/src/components/picker/t_picker.dart b/tdesign-component/lib/src/components/picker/t_picker.dart index ced374659..762891e5c 100644 --- a/tdesign-component/lib/src/components/picker/t_picker.dart +++ b/tdesign-component/lib/src/components/picker/t_picker.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../../tdesign_flutter.dart'; import '../../util/context_extension.dart'; +import '../../util/t_toolbar_pressable.dart'; import 'no_wave_behavior.dart'; // =============== 文件级常量(魔法数字归一) =============== @@ -10,12 +11,6 @@ import 'no_wave_behavior.dart'; /// 工具栏高度(px) const double _kToolbarHeight = 48; -/// 按钮按压态动画时长 -const Duration _kPressAnimDuration = Duration(milliseconds: 100); - -/// 按钮按压态透明度 -const double _kPressedOpacity = 0.5; - /// disabled 项修正动画 - 距离 ≤ 2 时的时长 const int _kCorrectAnimShortMs = 200; @@ -245,8 +240,6 @@ class _TPickerState extends State { final _scrollBehavior = NoWaveBehavior(); /// 工具栏按钮按压态(参考 TCheckbox 的反馈方式) - bool _cancelPressed = false; - bool _confirmPressed = false; double get _itemHeight => widget.height / widget.itemCount; @@ -399,23 +392,31 @@ class _TPickerState extends State { padding: EdgeInsets.symmetric(horizontal: theme.spacer16), child: Row( children: [ - _buildToolbarButton( - theme: theme, - pressed: _cancelPressed, - onPressChange: (v) => setState(() => _cancelPressed = v), - onTap: () => widget.onCancel?.call(), - defaultColor: theme.fontGyColor2, + TToolbarPressable( + onTap: widget.onCancel, + mergeTextStyle: TextStyle( + fontSize: theme.fontTitleMedium?.size ?? _kDefaultFontSize, + color: theme.fontGyColor2, + ), + mergeIconTheme: IconThemeData( + color: theme.fontGyColor2, + size: _kDefaultIconSize, + ), child: cancelText, ), Expanded( child: Center(child: _buildTitle(theme)), ), - _buildToolbarButton( - theme: theme, - pressed: _confirmPressed, - onPressChange: (v) => setState(() => _confirmPressed = v), + TToolbarPressable( onTap: () => widget.onConfirm?.call(_buildValue()), - defaultColor: theme.brandNormalColor, + mergeTextStyle: TextStyle( + fontSize: theme.fontTitleMedium?.size ?? _kDefaultFontSize, + color: theme.brandNormalColor, + ), + mergeIconTheme: IconThemeData( + color: theme.brandNormalColor, + size: _kDefaultIconSize, + ), child: confirmText, ), ], @@ -441,48 +442,6 @@ class _TPickerState extends State { ); } - /// 带按压反馈的工具栏按钮 - /// - /// - 按下时 [AnimatedOpacity] 平滑过渡到 [_kPressedOpacity] - /// - [DefaultTextStyle] / [IconTheme] 为默认 [Text] / [Icon] 提供统一样式, - /// 用户传入的 Widget 若已指定样式,会优先采用自己的样式(merge 语义) - Widget _buildToolbarButton({ - required TThemeData theme, - required bool pressed, - required ValueChanged onPressChange, - required VoidCallback onTap, - required Color defaultColor, - required Widget child, - }) { - final styledChild = DefaultTextStyle.merge( - style: TextStyle( - fontSize: theme.fontTitleMedium?.size ?? _kDefaultFontSize, - color: defaultColor, - ), - child: IconTheme.merge( - data: IconThemeData(color: defaultColor, size: _kDefaultIconSize), - child: child, - ), - ); - - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTapDown: (_) => onPressChange(true), - onTapUp: (_) => onPressChange(false), - onTapCancel: () => onPressChange(false), - onTap: onTap, - child: AnimatedOpacity( - duration: _kPressAnimDuration, - opacity: pressed ? _kPressedOpacity : 1.0, - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: theme.spacer8, vertical: theme.spacer12), - child: styledChild, - ), - ), - ); - } - Widget _buildColumn(int colIndex) { final data = _columns[colIndex]; if (data.isEmpty) { diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart new file mode 100644 index 000000000..edabe0540 --- /dev/null +++ b/tdesign-component/lib/src/components/popup/_popup_header.dart @@ -0,0 +1,243 @@ +import 'package:flutter/material.dart'; + +import '../../../tdesign_flutter.dart'; +import '../../util/context_extension.dart'; +import '../../util/t_toolbar_pressable.dart'; +import '../icon/t_icons.dart'; +import 't_popup_config.dart'; +import 't_popup_types.dart'; + +/// 内置标题栏区域(仅 [TPopupPlacement.bottom])。 +class PopupHeader extends StatelessWidget { + const PopupHeader({ + super.key, + required this.config, + required this.onCloseWithTrigger, + }); + + final TPopupConfig config; + final void Function(TPopupTrigger trigger) onCloseWithTrigger; + + static const double headerHeight = 58; + + @override + Widget build(BuildContext context) { + if (config.placement != TPopupPlacement.bottom) { + return const SizedBox.shrink(); + } + + if (config.headerBuilder != null) { + return config.headerBuilder!(context); + } + + if (config.useActionHeader) { + return SizedBox( + height: headerHeight, + child: _ActionHeader( + config: config, + onCloseWithTrigger: onCloseWithTrigger, + ), + ); + } + + final title = config.titleWidget ?? + (config.title != null && config.title!.isNotEmpty + ? TText( + config.title!, + textColor: TTheme.of(context).textColorPrimary, + font: TTheme.of(context).fontTitleLarge, + fontWeight: FontWeight.w700, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + : null); + + if (title == null) { + return const SizedBox.shrink(); + } + + return SizedBox( + height: headerHeight, + child: Container( + alignment: config.titleAlignLeft + ? Alignment.centerLeft + : Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 16), + child: title, + ), + ); + } +} + +class _ActionHeader extends StatelessWidget { + const _ActionHeader({ + required this.config, + required this.onCloseWithTrigger, + }); + + final TPopupConfig config; + final void Function(TPopupTrigger trigger) onCloseWithTrigger; + + @override + Widget build(BuildContext context) { + final theme = TTheme.of(context); + final title = config.titleWidget ?? + TText( + config.title ?? '', + textColor: theme.textColorPrimary, + font: theme.fontTitleLarge, + fontWeight: FontWeight.w700, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + + return Row( + children: [ + if (config.showCancelSlot) + Padding( + padding: EdgeInsets.only(left: theme.spacer8), + child: Semantics( + button: true, + label: _cancelSemanticsLabel(context, config), + excludeSemantics: true, + child: TToolbarPressable( + onTap: () { + config.onCancel?.call(); + if (config.autoCloseOnCancel) { + onCloseWithTrigger(TPopupTrigger.cancelBtn); + } + }, + child: _buildCancel(context, theme), + ), + ), + ) + else + SizedBox(width: theme.spacer16), + Expanded(child: Center(child: title)), + if (config.showConfirmSlot) + Padding( + padding: EdgeInsets.only(right: theme.spacer8), + child: Semantics( + button: true, + label: _confirmSemanticsLabel(context, config), + excludeSemantics: true, + child: TToolbarPressable( + onTap: () { + config.onConfirm?.call(); + if (config.autoCloseOnConfirm) { + onCloseWithTrigger(TPopupTrigger.confirmBtn); + } + }, + child: _buildConfirm(context, theme), + ), + ), + ) + else + SizedBox(width: theme.spacer16), + ], + ); + } + + Widget _buildCancel(BuildContext context, TThemeData theme) { + if (config.cancelBuilder != null) { + return config.cancelBuilder!(context); + } + if (TPopupConfig.isActionDefault(config.cancel)) { + return TText( + config.cancelBtn ?? context.resource.cancel, + textColor: theme.textColorSecondary, + font: theme.fontBodyLarge, + ); + } + return config.cancel!; + } + + Widget _buildConfirm(BuildContext context, TThemeData theme) { + if (config.confirmBuilder != null) { + return config.confirmBuilder!(context); + } + if (TPopupConfig.isActionDefault(config.confirm)) { + return TText( + config.confirmBtn ?? context.resource.confirm, + textColor: theme.brandNormalColor, + font: theme.fontTitleMedium, + fontWeight: FontWeight.w600, + ); + } + return config.confirm!; + } +} + +String _cancelSemanticsLabel(BuildContext context, TPopupConfig config) { + final btn = config.cancelBtn; + if (btn != null && btn.isNotEmpty) { + return btn; + } + return context.resource.cancel; +} + +String _confirmSemanticsLabel(BuildContext context, TPopupConfig config) { + final btn = config.confirmBtn; + if (btn != null && btn.isNotEmpty) { + return btn; + } + return context.resource.confirm; +} + +/// 居中:关闭按钮在内容下方。 +class PopupCenterUnderClose extends StatelessWidget { + const PopupCenterUnderClose({ + super.key, + required this.config, + required this.content, + required this.onCloseWithTrigger, + }); + + final TPopupConfig config; + final Widget content; + final void Function(TPopupTrigger trigger) onCloseWithTrigger; + + @override + Widget build(BuildContext context) { + final theme = TTheme.of(context); + Widget panel = content; + if (config.width != null || config.height != null) { + panel = SizedBox( + width: config.width, + height: config.height, + child: content, + ); + } + + Widget closeControl; + if (config.closeBuilder != null) { + closeControl = config.closeBuilder!(context); + } else if (config.close != null) { + closeControl = config.close!; + } else { + closeControl = IconButton( + tooltip: context.resource.close, + icon: Icon( + TIcons.close_circle, + color: theme.fontWhColor1, + size: 32, + ), + onPressed: () { + config.onCloseBtn?.call(); + onCloseWithTrigger(TPopupTrigger.closeBtn); + }, + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(height: 40), + panel, + const SizedBox(height: 24), + closeControl, + ], + ); + } +} diff --git a/tdesign-component/lib/src/components/popup/_popup_layout.dart b/tdesign-component/lib/src/components/popup/_popup_layout.dart new file mode 100644 index 000000000..3e2550b6e --- /dev/null +++ b/tdesign-component/lib/src/components/popup/_popup_layout.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; + +import 't_popup_types.dart'; + +/// 根据 placement 计算 Positioned 约束。 +class PopupLayout { + PopupLayout({ + required this.placement, + required this.screenSize, + required this.margin, + this.width, + this.height, + this.centerLooseHeight = false, + }); + + final TPopupPlacement placement; + final Size screenSize; + final EdgeInsets margin; + final double? width; + final double? height; + /// 居中且关闭按钮在内容下方时,不限制总高度(含下方关闭区)。 + final bool centerLooseHeight; + + static const double defaultDrawerWidth = 280; + + EdgeInsets resolvedMargin() { + if (placement == TPopupPlacement.center) { + return EdgeInsets.zero; + } + return margin; + } + + Widget wrapPositioned({required Widget child}) { + final m = resolvedMargin(); + switch (placement) { + case TPopupPlacement.top: + return Positioned( + top: m.top, + left: m.left, + right: m.right, + height: height, + child: child, + ); + case TPopupPlacement.bottom: + final bottomHeight = _bottomHeight(m); + if (bottomHeight != null && m.top > 0) { + return Positioned( + top: m.top, + left: m.left, + right: m.right, + height: bottomHeight, + child: child, + ); + } + if (bottomHeight != null) { + return Positioned( + left: m.left, + right: m.right, + bottom: m.bottom, + height: bottomHeight, + child: child, + ); + } + return Positioned( + top: m.top > 0 ? m.top : null, + left: m.left, + right: m.right, + bottom: m.bottom, + child: child, + ); + case TPopupPlacement.left: + return Positioned( + top: m.top, + bottom: m.bottom, + left: m.left, + width: width ?? defaultDrawerWidth, + child: child, + ); + case TPopupPlacement.right: + return Positioned( + top: m.top, + bottom: m.bottom, + right: m.right, + width: width ?? defaultDrawerWidth, + child: child, + ); + case TPopupPlacement.center: + return Positioned.fill( + child: Center( + child: SizedBox( + width: centerLooseHeight ? null : width, + height: centerLooseHeight ? null : height, + child: child, + ), + ), + ); + } + } + + double? _bottomHeight(EdgeInsets m) { + if (height != null) { + return height; + } + if (m.top > 0) { + return screenSize.height - m.top - m.bottom; + } + return null; + } + + Alignment get alignment { + switch (placement) { + case TPopupPlacement.top: + return Alignment.topCenter; + case TPopupPlacement.bottom: + return Alignment.bottomCenter; + case TPopupPlacement.left: + return Alignment.centerLeft; + case TPopupPlacement.right: + return Alignment.centerRight; + case TPopupPlacement.center: + return Alignment.center; + } + } + + Offset slideOffset(double t) { + switch (placement) { + case TPopupPlacement.top: + return Offset(0, t - 1); + case TPopupPlacement.bottom: + return Offset(0, 1 - t); + case TPopupPlacement.left: + return Offset(t - 1, 0); + case TPopupPlacement.right: + return Offset(1 - t, 0); + case TPopupPlacement.center: + return Offset.zero; + } + } +} diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart new file mode 100644 index 000000000..b1868eff8 --- /dev/null +++ b/tdesign-component/lib/src/components/popup/_popup_route.dart @@ -0,0 +1,240 @@ +import 'package:flutter/material.dart'; + +import '_popup_layout.dart'; +import '_popup_shell.dart'; +import 't_popup_config.dart'; +import 't_popup_types.dart'; + +const Duration _kReverseDuration = Duration(milliseconds: 200); + +/// 私有 Popup 路由。 +class TPopupNavigatorRoute extends PopupRoute { + TPopupNavigatorRoute({ + required this.config, + required this.onCloseWithTrigger, + }) : _layout = PopupLayout( + placement: config.placement, + screenSize: Size.zero, + margin: config.margin, + width: config.width, + height: config.height, + ); + + final TPopupConfig config; + final void Function(TPopupTrigger trigger, [Object? result]) + onCloseWithTrigger; + + late PopupLayout _layout; + bool _animationListenerAttached = false; + bool _openedFired = false; + bool _closedFired = false; + bool _closeStartFired = false; + String? _barrierSemanticsLabel; + + Color get _barrierColor { + if (!config.showOverlay) { + return Colors.transparent; + } + final base = config.overlayColor ?? Colors.black54; + if (config.overlayOpacity != null) { + final opacity = config.overlayOpacity!.clamp(0.0, 1.0); + return base.withValues(alpha: base.a * opacity); + } + return base; + } + + @override + Duration get transitionDuration => config.duration; + + @override + Duration get reverseTransitionDuration => _kReverseDuration; + + @override + bool get barrierDismissible => false; + + @override + String? get barrierLabel => + config.showOverlay ? _barrierSemanticsLabel : null; + + @override + Color get barrierColor => Colors.transparent; + + /// 路由须非 opaque,否则透明区域会露出 Modal 默认黑底。 + /// + /// 无蒙层时滚动穿透由 [_scrollBlocker] 处理,不能靠 opaque=true(会整屏发黑)。 + @override + bool get opaque => false; + + @override + bool get maintainState => !config.destroyOnClose; + + /// 关闭动画开始前回调(系统返回 / handle.close / 蒙层等统一入口)。 + void fireCloseStart(TPopupTrigger trigger) { + if (_closeStartFired) { + return; + } + _closeStartFired = true; + config.onVisibleChange?.call(false, trigger); + config.onClose?.call(); + } + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return const SizedBox.shrink(); + } + + @override + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + final curved = CurvedAnimation( + parent: animation, + curve: Curves.decelerate, + reverseCurve: Curves.easeOut, + ); + + final mediaQuery = MediaQuery.of(context); + if (config.showOverlay) { + _barrierSemanticsLabel ??= + MaterialLocalizations.of(context).modalBarrierDismissLabel; + } + _layout = PopupLayout( + placement: config.placement, + screenSize: mediaQuery.size, + margin: config.margin, + width: config.width, + height: config.height, + centerLooseHeight: + config.placement == TPopupPlacement.center && config.closeBtn, + ); + + final t = curved.value; + final shell = PopupShell( + config: config, + onCloseWithTrigger: onCloseWithTrigger, + ); + + Widget popupContent; + if (config.placement == TPopupPlacement.center) { + popupContent = Transform.scale( + scale: t, + alignment: Alignment.center, + child: shell, + ); + } else { + popupContent = FractionalTranslation( + translation: _layout.slideOffset(t), + child: shell, + ); + } + + final positioned = _layout.wrapPositioned(child: popupContent); + + final barrier = _buildBarrier(context, t); + + return Stack( + fit: StackFit.expand, + children: [ + if (config.showOverlay) barrier, + if (!config.showOverlay && config.preventScrollThrough) + _scrollBlocker(child: const SizedBox.expand()), + positioned, + ], + ); + } + + Widget _buildBarrier(BuildContext context, double t) { + Widget barrier = GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: _handleOverlayTap, + child: Container( + color: _barrierColor.withValues( + alpha: _barrierColor.a * t, + ), + ), + ); + if (config.showOverlay) { + final label = _barrierSemanticsLabel ?? + MaterialLocalizations.of(context).modalBarrierDismissLabel; + barrier = Semantics( + label: label, + button: true, + child: barrier, + ); + } + if (config.preventScrollThrough) { + barrier = _scrollBlocker(child: barrier); + } + return barrier; + } + + Widget _scrollBlocker({required Widget child}) { + return NotificationListener( + onNotification: (_) => true, + child: child, + ); + } + + void _handleOverlayTap() { + config.onOverlayClick?.call(); + if (config.closeOnOverlayClick) { + onCloseWithTrigger(TPopupTrigger.overlay); + } + } + + void _onAnimationStatus(AnimationStatus status) { + if (status == AnimationStatus.completed && !_openedFired) { + _openedFired = true; + config.onOpened?.call(); + } + if (status == AnimationStatus.dismissed && !_closedFired) { + _closedFired = true; + config.onClosed?.call(); + } + } + + void _attachAnimationListener() { + if (_animationListenerAttached) { + return; + } + final anim = animation; + if (anim == null) { + return; + } + _animationListenerAttached = true; + anim.addStatusListener(_onAnimationStatus); + if (anim.status == AnimationStatus.completed) { + _onAnimationStatus(AnimationStatus.completed); + } + } + + @override + TickerFuture didPush() { + config.onOpen?.call(); + config.onVisibleChange?.call(true, TPopupTrigger.programmatic); + final future = super.didPush(); + future.whenComplete(_attachAnimationListener); + return future; + } + + @override + bool didPop(T? result) { + fireCloseStart(TPopupTrigger.programmatic); + return super.didPop(result); + } + + @override + void dispose() { + if (_animationListenerAttached) { + animation?.removeStatusListener(_onAnimationStatus); + } + super.dispose(); + } +} diff --git a/tdesign-component/lib/src/components/popup/_popup_shell.dart b/tdesign-component/lib/src/components/popup/_popup_shell.dart new file mode 100644 index 000000000..d2e7ec0ec --- /dev/null +++ b/tdesign-component/lib/src/components/popup/_popup_shell.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; + +import '../../theme/t_colors.dart'; +import '../../theme/t_radius.dart'; +import '../../theme/t_theme.dart'; +import '_popup_header.dart'; +import 't_popup_config.dart'; +import 't_popup_types.dart'; + +/// 浮层内容外壳:圆角、Header(仅 bottom)、child。 +class PopupShell extends StatelessWidget { + const PopupShell({ + super.key, + required this.config, + required this.onCloseWithTrigger, + }); + + final TPopupConfig config; + final void Function(TPopupTrigger trigger) onCloseWithTrigger; + + @override + Widget build(BuildContext context) { + final theme = TTheme.of(context); + final radius = config.radius ?? theme.radiusExtraLarge; + final backgroundColor = + config.backgroundColor ?? theme.bgColorContainer; + final borderRadius = _borderRadius(config.placement, radius); + + Widget content = config.child; + + if (config.placement == TPopupPlacement.center) { + if (config.closeBtn) { + return PopupCenterUnderClose( + config: config, + content: Container( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(radius), + ), + clipBehavior: Clip.antiAlias, + child: content, + ), + onCloseWithTrigger: onCloseWithTrigger, + ); + } + return Center( + child: SizedBox( + width: config.width, + height: config.height, + child: Container( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(radius), + ), + clipBehavior: Clip.antiAlias, + child: content, + ), + ), + ); + } + + final useExpanded = config.placement == TPopupPlacement.left || + config.placement == TPopupPlacement.right || + config.height != null; + + Widget panel = Container( + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: borderRadius, + ), + clipBehavior: Clip.antiAlias, + child: config.placement == TPopupPlacement.bottom + ? Column( + mainAxisSize: + useExpanded ? MainAxisSize.max : MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + PopupHeader( + config: config, + onCloseWithTrigger: onCloseWithTrigger, + ), + if (useExpanded) Expanded(child: content) else content, + ], + ) + : (useExpanded + ? Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [Expanded(child: content)], + ) + : content), + ); + + return panel; + } + + BorderRadius? _borderRadius(TPopupPlacement placement, double radius) { + switch (placement) { + case TPopupPlacement.top: + return BorderRadius.vertical(bottom: Radius.circular(radius)); + case TPopupPlacement.bottom: + return BorderRadius.vertical(top: Radius.circular(radius)); + case TPopupPlacement.left: + return BorderRadius.horizontal(right: Radius.circular(radius)); + case TPopupPlacement.right: + return BorderRadius.horizontal(left: Radius.circular(radius)); + case TPopupPlacement.center: + return BorderRadius.circular(radius); + } + } +} diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart new file mode 100644 index 000000000..5e899176c --- /dev/null +++ b/tdesign-component/lib/src/components/popup/t_popup.dart @@ -0,0 +1,357 @@ +import 'package:flutter/material.dart'; + +import '_popup_route.dart'; +import 't_popup_config.dart'; +import 't_popup_tracker.dart'; +import 't_popup_types.dart'; + +export 't_popup_types.dart'; + +/// 弹出层。 +class TPopup extends StatefulWidget { + const TPopup({ + super.key, + required this.child, + this.initialVisible = false, + this.placement = TPopupPlacement.bottom, + this.width, + this.height, + this.margin, + this.radius, + this.backgroundColor, + this.showOverlay = true, + this.closeOnOverlayClick = true, + this.overlayColor, + this.overlayOpacity, + this.preventScrollThrough = true, + /// 关闭后 Popup 路由是否不再 [maintainState](不保留路由内 State)。 + /// 不影响声明式 [TPopup] 自身 [State] 的存活。 + this.destroyOnClose = false, + this.duration = const Duration(milliseconds: 240), + this.title, + this.titleWidget, + this.titleAlignLeft = false, + this.cancelBtn, + this.cancel = kPopupActionDefault, + this.cancelBuilder, + this.onCancel, + this.confirmBtn, + this.confirm = kPopupActionDefault, + this.confirmBuilder, + this.onConfirm, + this.autoCloseOnCancel = true, + this.autoCloseOnConfirm = true, + this.closeBtn, + Widget? close, + this.closeBuilder, + this.closeBelowContent, + this.onCloseBtn, + this.headerBuilder, + this.onOpen, + this.onOpened, + this.onClose, + this.onClosed, + this.onVisibleChange, + this.onOverlayClick, + this.navigatorContext, + this.useRootNavigator = false, + }) : closeWidget = close; + + final Widget child; + final bool initialVisible; + + final TPopupPlacement placement; + final double? width; + final double? height; + final EdgeInsets? margin; + final double? radius; + final Color? backgroundColor; + final bool showOverlay; + final bool closeOnOverlayClick; + final Color? overlayColor; + final double? overlayOpacity; + final bool preventScrollThrough; + + /// 关闭后 Popup 路由是否不再 [maintainState](不保留路由内 State)。 + /// 不影响声明式 [TPopup] 自身 [State] 的存活。 + final bool destroyOnClose; + final Duration duration; + + final String? title; + final Widget? titleWidget; + final bool titleAlignLeft; + final String? cancelBtn; + final Widget? cancel; + final WidgetBuilder? cancelBuilder; + final VoidCallback? onCancel; + final String? confirmBtn; + final Widget? confirm; + final WidgetBuilder? confirmBuilder; + final VoidCallback? onConfirm; + final bool autoCloseOnCancel; + final bool autoCloseOnConfirm; + + /// center 默认 true;bottom / 三边忽略。 + final bool? closeBtn; + /// [TPopupPlacement.center] 自定义关闭控件;构造参数名为 [close](与 [show] 一致)。 + final Widget? closeWidget; + final WidgetBuilder? closeBuilder; + + /// center 且显示关闭时默认 true。 + final bool? closeBelowContent; + final VoidCallback? onCloseBtn; + + /// 仅 [TPopupPlacement.bottom] 生效。 + final WidgetBuilder? headerBuilder; + + final VoidCallback? onOpen; + final VoidCallback? onOpened; + final VoidCallback? onClose; + final VoidCallback? onClosed; + final TPopupVisibleChangeCallback? onVisibleChange; + final VoidCallback? onOverlayClick; + + final BuildContext? navigatorContext; + final bool useRootNavigator; + + /// 打开浮层。 + static TPopupHandle show({ + required BuildContext context, + required Widget child, + TPopupPlacement placement = TPopupPlacement.bottom, + double? width, + double? height, + EdgeInsets? margin, + double? radius, + Color? backgroundColor, + bool showOverlay = true, + bool closeOnOverlayClick = true, + Color? overlayColor, + double? overlayOpacity, + bool preventScrollThrough = true, + /// 关闭后 Popup 路由是否不再 [maintainState](不保留路由内 State)。 + bool destroyOnClose = false, + Duration duration = const Duration(milliseconds: 240), + String? title, + Widget? titleWidget, + bool titleAlignLeft = false, + String? cancelBtn, + Widget? cancel = kPopupActionDefault, + WidgetBuilder? cancelBuilder, + VoidCallback? onCancel, + String? confirmBtn, + Widget? confirm = kPopupActionDefault, + WidgetBuilder? confirmBuilder, + VoidCallback? onConfirm, + bool autoCloseOnCancel = true, + bool autoCloseOnConfirm = true, + bool? closeBtn, + Widget? close, + WidgetBuilder? closeBuilder, + bool? closeBelowContent, + VoidCallback? onCloseBtn, + WidgetBuilder? headerBuilder, + VoidCallback? onOpen, + VoidCallback? onOpened, + VoidCallback? onClose, + VoidCallback? onClosed, + TPopupVisibleChangeCallback? onVisibleChange, + VoidCallback? onOverlayClick, + BuildContext? navigatorContext, + bool useRootNavigator = false, + }) { + final config = TPopupConfig.create( + child: child, + placement: placement, + width: width, + height: height, + margin: margin ?? EdgeInsets.zero, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + title: title, + titleWidget: titleWidget, + titleAlignLeft: titleAlignLeft, + cancelBtn: cancelBtn, + cancel: cancel, + cancelBuilder: cancelBuilder, + onCancel: onCancel, + confirmBtn: confirmBtn, + confirm: confirm, + confirmBuilder: confirmBuilder, + onConfirm: onConfirm, + autoCloseOnCancel: autoCloseOnCancel, + autoCloseOnConfirm: autoCloseOnConfirm, + closeBtn: closeBtn, + close: close, + closeBuilder: closeBuilder, + closeBelowContent: closeBelowContent, + onCloseBtn: onCloseBtn, + headerBuilder: headerBuilder, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + config.assertPlacementParams(); + + final navContext = navigatorContext ?? context; + final navigator = Navigator.of( + navContext, + rootNavigator: useRootNavigator, + ); + + TPopupNavigatorRoute? route; + late TPopupHandle handle; + + void closeWithTrigger(TPopupTrigger trigger, [Object? result]) { + if (handle._isClosed) { + return; + } + handle._isClosed = true; + route?.fireCloseStart(trigger); + navigator.pop(result); + } + + route = TPopupNavigatorRoute( + config: config, + onCloseWithTrigger: closeWithTrigger, + ); + + handle = TPopupHandle._( + navigator: navigator, + route: route, + onCloseWithTrigger: closeWithTrigger, + ); + + TPopupTracker.push(navigator, handle); + + navigator.push(route).whenComplete(() { + TPopupTracker.remove(navigator, handle); + handle._isClosed = true; + handle._route = null; + }); + + return handle; + } + + /// 关闭当前 Navigator 栈顶 [TPopup];触发与 [TPopupHandle.close] 相同的生命周期回调。 + static void close(BuildContext context, [Object? result]) { + final navigator = Navigator.of(context); + final handle = TPopupTracker.top(navigator); + if (handle?.isShowing == true) { + handle!.close(result); + return; + } + navigator.maybePop(result); + } + + @override + State createState() => _TPopupState(); +} + +class _TPopupState extends State { + TPopupHandle? _handle; + + @override + void initState() { + super.initState(); + if (widget.initialVisible) { + WidgetsBinding.instance.addPostFrameCallback((_) => _open()); + } + } + + @override + void dispose() { + _handle?.close(); + super.dispose(); + } + + void _open() { + if (_handle?.isShowing == true) { + return; + } + _handle = TPopup.show( + context: context, + navigatorContext: widget.navigatorContext ?? context, + useRootNavigator: widget.useRootNavigator, + child: widget.child, + placement: widget.placement, + width: widget.width, + height: widget.height, + margin: widget.margin, + radius: widget.radius, + backgroundColor: widget.backgroundColor, + showOverlay: widget.showOverlay, + closeOnOverlayClick: widget.closeOnOverlayClick, + overlayColor: widget.overlayColor, + overlayOpacity: widget.overlayOpacity, + preventScrollThrough: widget.preventScrollThrough, + destroyOnClose: widget.destroyOnClose, + duration: widget.duration, + title: widget.title, + titleWidget: widget.titleWidget, + titleAlignLeft: widget.titleAlignLeft, + cancelBtn: widget.cancelBtn, + cancel: widget.cancel, + cancelBuilder: widget.cancelBuilder, + onCancel: widget.onCancel, + confirmBtn: widget.confirmBtn, + confirm: widget.confirm, + confirmBuilder: widget.confirmBuilder, + onConfirm: widget.onConfirm, + autoCloseOnCancel: widget.autoCloseOnCancel, + autoCloseOnConfirm: widget.autoCloseOnConfirm, + closeBtn: widget.closeBtn, + close: widget.closeWidget, + closeBuilder: widget.closeBuilder, + closeBelowContent: widget.closeBelowContent, + onCloseBtn: widget.onCloseBtn, + headerBuilder: widget.headerBuilder, + onOpen: widget.onOpen, + onOpened: widget.onOpened, + onClose: widget.onClose, + onClosed: widget.onClosed, + onVisibleChange: widget.onVisibleChange, + onOverlayClick: widget.onOverlayClick, + ); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} + +/// [TPopup.show] 返回的句柄。 +class TPopupHandle { + TPopupHandle._({ + required NavigatorState navigator, + required TPopupNavigatorRoute? route, + required void Function(TPopupTrigger trigger, [Object? result]) + onCloseWithTrigger, + }) : _route = route, + _onCloseWithTrigger = onCloseWithTrigger; + + TPopupNavigatorRoute? _route; + final void Function(TPopupTrigger trigger, [Object? result]) + _onCloseWithTrigger; + bool _isClosed = false; + + bool get isShowing => _route != null && !_isClosed; + + void close([Object? result]) { + if (!isShowing) { + return; + } + _onCloseWithTrigger(TPopupTrigger.programmatic, result); + } +} diff --git a/tdesign-component/lib/src/components/popup/t_popup_config.dart b/tdesign-component/lib/src/components/popup/t_popup_config.dart new file mode 100644 index 000000000..a2a4894f5 --- /dev/null +++ b/tdesign-component/lib/src/components/popup/t_popup_config.dart @@ -0,0 +1,247 @@ +import 'package:flutter/material.dart'; + +import 't_popup_types.dart'; + +/// Popup 运行时配置(库内共享)。 +class TPopupConfig { + TPopupConfig({ + required this.child, + required this.placement, + this.width, + this.height, + this.margin = EdgeInsets.zero, + this.radius, + this.backgroundColor, + this.showOverlay = true, + this.closeOnOverlayClick = true, + this.overlayColor, + this.overlayOpacity, + this.preventScrollThrough = true, + /// 为 true 时路由 [maintainState] 为 false,关闭后丢弃 Popup 路由 State。 + this.destroyOnClose = false, + this.duration = const Duration(milliseconds: 240), + this.title, + this.titleWidget, + this.titleAlignLeft = false, + this.cancelBtn, + this.cancel, + this.cancelBuilder, + this.onCancel, + this.confirmBtn, + this.confirm, + this.confirmBuilder, + this.onConfirm, + this.autoCloseOnCancel = true, + this.autoCloseOnConfirm = true, + this.closeBtn = false, + this.close, + this.closeBuilder, + this.closeBelowContent = false, + this.onCloseBtn, + this.headerBuilder, + this.onOpen, + this.onOpened, + this.onClose, + this.onClosed, + this.onVisibleChange, + this.onOverlayClick, + }); + + /// 按 [placement] 归一化参数(bottom 无 closeBtn;center 默认关闭;三边仅 child)。 + factory TPopupConfig.create({ + required Widget child, + TPopupPlacement placement = TPopupPlacement.bottom, + double? width, + double? height, + EdgeInsets margin = EdgeInsets.zero, + double? radius, + Color? backgroundColor, + bool showOverlay = true, + bool closeOnOverlayClick = true, + Color? overlayColor, + double? overlayOpacity, + bool preventScrollThrough = true, + bool destroyOnClose = false, + Duration duration = const Duration(milliseconds: 240), + String? title, + Widget? titleWidget, + bool titleAlignLeft = false, + String? cancelBtn, + Widget? cancel = kPopupActionDefault, + WidgetBuilder? cancelBuilder, + VoidCallback? onCancel, + String? confirmBtn, + Widget? confirm = kPopupActionDefault, + WidgetBuilder? confirmBuilder, + VoidCallback? onConfirm, + bool autoCloseOnCancel = true, + bool autoCloseOnConfirm = true, + bool? closeBtn, + Widget? close, + WidgetBuilder? closeBuilder, + bool? closeBelowContent, + VoidCallback? onCloseBtn, + WidgetBuilder? headerBuilder, + VoidCallback? onOpen, + VoidCallback? onOpened, + VoidCallback? onClose, + VoidCallback? onClosed, + TPopupVisibleChangeCallback? onVisibleChange, + VoidCallback? onOverlayClick, + }) { + final isBottom = placement == TPopupPlacement.bottom; + final isCenter = placement == TPopupPlacement.center; + final effectiveCloseBtn = isCenter ? (closeBtn ?? true) : false; + final effectiveCloseBelow = + isCenter && effectiveCloseBtn ? (closeBelowContent ?? true) : false; + + return TPopupConfig( + child: child, + placement: placement, + width: width, + height: height, + margin: margin, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + title: isBottom ? title : null, + titleWidget: isBottom ? titleWidget : null, + titleAlignLeft: isBottom ? titleAlignLeft : false, + cancelBtn: isBottom ? cancelBtn : null, + cancel: isBottom ? cancel : null, + cancelBuilder: isBottom ? cancelBuilder : null, + onCancel: isBottom ? onCancel : null, + confirmBtn: isBottom ? confirmBtn : null, + confirm: isBottom ? confirm : null, + confirmBuilder: isBottom ? confirmBuilder : null, + onConfirm: isBottom ? onConfirm : null, + autoCloseOnCancel: autoCloseOnCancel, + autoCloseOnConfirm: autoCloseOnConfirm, + closeBtn: effectiveCloseBtn, + close: isCenter ? close : null, + closeBuilder: isCenter ? closeBuilder : null, + closeBelowContent: effectiveCloseBelow, + onCloseBtn: isCenter ? onCloseBtn : null, + headerBuilder: isBottom ? headerBuilder : null, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + } + + final Widget child; + final TPopupPlacement placement; + final double? width; + final double? height; + final EdgeInsets margin; + final double? radius; + final Color? backgroundColor; + final bool showOverlay; + final bool closeOnOverlayClick; + final Color? overlayColor; + final double? overlayOpacity; + final bool preventScrollThrough; + + /// 为 true 时路由 [maintainState] 为 false,关闭后丢弃 Popup 路由 State。 + final bool destroyOnClose; + final Duration duration; + + final String? title; + final Widget? titleWidget; + final bool titleAlignLeft; + final String? cancelBtn; + final Widget? cancel; + final WidgetBuilder? cancelBuilder; + final VoidCallback? onCancel; + final String? confirmBtn; + final Widget? confirm; + final WidgetBuilder? confirmBuilder; + final VoidCallback? onConfirm; + final bool autoCloseOnCancel; + final bool autoCloseOnConfirm; + + final bool closeBtn; + final Widget? close; + final WidgetBuilder? closeBuilder; + final bool closeBelowContent; + final VoidCallback? onCloseBtn; + + final WidgetBuilder? headerBuilder; + + final VoidCallback? onOpen; + final VoidCallback? onOpened; + final VoidCallback? onClose; + final VoidCallback? onClosed; + final TPopupVisibleChangeCallback? onVisibleChange; + final VoidCallback? onOverlayClick; + + /// bottom 左侧是否渲染([cancel] 为 `null` 时隐藏,未传则用默认文案)。 + bool get showCancelSlot => + placement == TPopupPlacement.bottom && + (cancelBuilder != null || cancel != null); + + /// bottom 右侧是否渲染。 + bool get showConfirmSlot => + placement == TPopupPlacement.bottom && + (confirmBuilder != null || confirm != null); + + /// 底部操作栏(取消 | 标题 | 确认),仅 bottom 且未使用 [headerBuilder]。 + bool get useActionHeader => + placement == TPopupPlacement.bottom && + headerBuilder == null && + (showCancelSlot || showConfirmSlot); + + static bool isActionDefault(Widget? action) => action is TPopupActionDefault; + + bool get hasBuiltInHeader => + placement == TPopupPlacement.bottom && + (headerBuilder != null || + useActionHeader || + (title != null && title!.isNotEmpty) || + titleWidget != null); + + void assertPlacementParams() { + assert(() { + switch (placement) { + case TPopupPlacement.left: + case TPopupPlacement.right: + if (height != null) { + debugPrint( + 'TPopup: height is ignored for placement=$placement', + ); + } + break; + case TPopupPlacement.center: + if (height != null && !(closeBtn && closeBelowContent)) { + debugPrint( + 'TPopup: height is ignored for placement=$placement', + ); + } + break; + case TPopupPlacement.top: + case TPopupPlacement.bottom: + if (width != null) { + debugPrint( + 'TPopup: width is ignored for placement=$placement', + ); + } + break; + } + if (placement != TPopupPlacement.bottom && useActionHeader) { + debugPrint( + 'TPopup: cancel/confirm only applies to placement=bottom', + ); + } + return true; + }()); + } +} diff --git a/tdesign-component/lib/src/components/popup/t_popup_panel.dart b/tdesign-component/lib/src/components/popup/t_popup_panel.dart deleted file mode 100644 index 9a6f3fa85..000000000 --- a/tdesign-component/lib/src/components/popup/t_popup_panel.dart +++ /dev/null @@ -1,641 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import '../../../tdesign_flutter.dart'; -import '../../util/context_extension.dart'; - -typedef PopupClick = Function(); - -/// 弹窗基类 -abstract class TPopupBasePanel extends StatefulWidget { - const TPopupBasePanel({ - Key? key, - required this.child, - this.title, - this.titleColor, - this.backgroundColor, - this.radius, - this.draggable = false, - this.maxHeightRatio = 0.9, - this.minHeightRatio = 0.3, - }) : super(key: key); - - /// 子控件 - final Widget child; - - /// 标题 - final String? title; - - /// 标题颜色 - final Color? titleColor; - - /// 背景颜色 - final Color? backgroundColor; - - /// 圆角 - final double? radius; - - /// 边缘是否可拖动 - final bool draggable; - - /// 最大高度比例 - final double maxHeightRatio; - - /// 最小高度比例 - final double minHeightRatio; - - @override - State createState(); -} - -abstract class _TPopupBaseState extends State - with SingleTickerProviderStateMixin { - final GlobalKey _childKey = GlobalKey(); - static const _dragHandleHeight = 24.0; - static const _headerHeight = 58.0; - - late AnimationController _controller; - double _maxHeight = 0; - double _minHeight = 0; - double _currentHeight = 0; - double _lastChildHeight = 0; - double _lastScreenHeight = 0; - double? _lastMaxHeightRatio; - double? _lastMinHeightRatio; - bool? _lastDraggable; - bool _isFullscreen = false; - bool _isAnimating = false; - bool _isDragging = false; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - duration: const Duration(milliseconds: 400), - vsync: this, - ); - if (widget.draggable) { - _controller.addListener(_updateHeight); - } - WidgetsBinding.instance.addPostFrameCallback((_) => _measureChildHeight()); - } - - /// 测量子组件高度并更新弹窗布局参数 - /// 1.获取子组件渲染尺寸 - /// 2.计算实际需要的基础高度 - /// 3.动态计算计算最大最小高度比例 - /// 4.更新动画控制器状态 - void _measureChildHeight() { - // 获取子组件渲染对象 - final context = _childKey.currentContext; - if (context == null) { - return; - } - final renderBox = context.findRenderObject() as RenderBox?; - if (renderBox == null || !renderBox.hasSize) { - return; - } - - final screenHeight = MediaQuery.of(context).size.height; - final childHeight = renderBox.size.height; - - // 如果高度和相关配置没有变化,则不重新计算 - if (_lastChildHeight == childHeight && - _lastScreenHeight == screenHeight && - _lastMaxHeightRatio == widget.maxHeightRatio && - _lastMinHeightRatio == widget.minHeightRatio && - _lastDraggable == widget.draggable) { - return; - } - _lastChildHeight = childHeight; - _lastScreenHeight = screenHeight; - _lastMaxHeightRatio = widget.maxHeightRatio; - _lastMinHeightRatio = widget.minHeightRatio; - _lastDraggable = widget.draggable; - - final headerHeight = widget.draggable ? _headerHeight : _headerHeight; - final baseHeight = _dragHandleHeight + headerHeight + childHeight; - - // 动态计算最大最小高度 - final maxHeightByRatio = screenHeight * widget.maxHeightRatio; - final minHeightByRatio = screenHeight * widget.minHeightRatio; - - // 内容高度和比例约束 - _maxHeight = min(baseHeight, maxHeightByRatio); - _minHeight = max(baseHeight * 0.5, minHeightByRatio); - if (_minHeight > _maxHeight) { - _minHeight = _maxHeight; - } - - // 初始化当前高度 - _currentHeight = baseHeight.clamp(_minHeight, _maxHeight); - // // 同步动画控制器 如果不赋值,会导致“键盘弹出默认遮挡”用例无法展示 - // final newValue = ((_currentHeight - _minHeight) / - // (_maxHeight - _minHeight).clamp(0.1, 1.0)) - // .clamp(0.0, 1.0); - // if ((_controller.value - newValue).abs() > 0.001) { - // _controller.value = newValue; - // } - // 同步动画控制器 - _controller.value = (_currentHeight - _minHeight) / - (_maxHeight - _minHeight).clamp(0.1, 1.0); - } - - void _updateHeight() => setState(() { - _currentHeight = - _minHeight + (_maxHeight - _minHeight) * _controller.value; - }); - - void _toggleFullscreen(bool fullscreen) { - if (_isAnimating || _isFullscreen == fullscreen) { - return; - } - - setState(() { - _isFullscreen = fullscreen; - _maxHeight = fullscreen - ? MediaQuery.of(context).size.height - : MediaQuery.of(context).size.height * widget.maxHeightRatio; - }); - - _controller.animateTo( - fullscreen ? 1.0 : 0.0, - duration: const Duration(milliseconds: 350), - curve: Curves.fastOutSlowIn, - ); - } - - void _animateTo(double height) { - if (_isAnimating) { - return; - } - _isAnimating = true; - - final value = (height - _minHeight) / (_maxHeight - _minHeight); - _controller - .animateTo( - value.clamp(0.0, 1.0), - duration: const Duration(milliseconds: 300), - curve: Curves.easeOutBack, - ) - .whenComplete(() => _isAnimating = false); - } - - Widget _buildDragHandle() { - if (!widget.draggable) { - return const SizedBox.shrink(); - } - - return GestureDetector( - behavior: HitTestBehavior.opaque, - onVerticalDragUpdate: _handleDragUpdate, - onVerticalDragEnd: _handleDragEnd, - onDoubleTap: () => _toggleFullscreen(!_isFullscreen), - child: Container( - height: _dragHandleHeight, - alignment: Alignment.center, - child: Container( - width: 48, - height: 4, - decoration: BoxDecoration( - color: TTheme.of(context).componentStrokeColor, - borderRadius: BorderRadius.circular(2), - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback((_) { - // 每次 build 都测量子内容高度,确保内容变化时高度自适应 (拖动时不测量) - if (!_isDragging) { - _measureChildHeight(); - } - }); - - return AnimatedBuilder( - animation: _controller, - builder: (context, _) => RepaintBoundary( - child: Container( - height: _currentHeight, - decoration: BoxDecoration( - color: - widget.backgroundColor ?? TTheme.of(context).bgColorContainer, - borderRadius: _isFullscreen - ? null - : BorderRadius.vertical( - top: Radius.circular( - widget.radius ?? TTheme.of(context).radiusExtraLarge)), - ), - child: Column(children: [ - _buildDragHandle(), - buildHeader(context), - SizedBox( - child: _buildContent(), - ), - ]), - ), - ), - ); - } - - Widget _buildContent() => NotificationListener( - onNotification: (notification) { - if (notification is ScrollUpdateNotification) { - final metrics = notification.metrics; - if ((metrics.pixels <= 0 || - metrics.pixels >= metrics.maxScrollExtent) && - notification.dragDetails != null) { - _handleDragUpdate(notification.dragDetails!); - } - } - return false; - }, - child: Container( - key: _childKey, - child: widget.child, - ), - ); - - @protected - void _handleDragUpdate(DragUpdateDetails details); - - @protected - void _handleDragEnd(DragEndDetails details); - - @protected - Widget buildHeader(BuildContext context); - - void _baseHandleDragUpdate(DragUpdateDetails details) { - _isDragging = true; - if (_isAnimating || !widget.draggable) { - return; - } - - final newHeight = _currentHeight - details.primaryDelta! * 1.2; - _currentHeight = newHeight.clamp(_minHeight, _maxHeight); - _controller.value = - (_currentHeight - _minHeight) / (_maxHeight - _minHeight); - } - - void _baseHandleDragEnd(DragEndDetails details) { - final velocity = details.velocity.pixelsPerSecond.dy; - final predictedHeight = _currentHeight + velocity * 0.15; - - if (predictedHeight > _maxHeight * 0.7 || velocity < -800) { - _animateTo(_maxHeight); - } else if (predictedHeight < _minHeight * 1.3 || velocity > 800) { - _animateTo(_minHeight); - } - _isDragging = false; - } -} - -/// 右上角带关闭的底部浮层面板 -class TPopupBottomDisplayPanel extends TPopupBasePanel { - const TPopupBottomDisplayPanel({ - super.key, - required super.child, - super.title, - super.titleColor, - this.titleFontSize, - this.titleLeft = false, - this.hideClose = false, - this.closeColor, - this.closeSize, - this.closeClick, - super.backgroundColor, - super.radius, - super.draggable, - super.maxHeightRatio, - super.minHeightRatio, - }); - - /// 标题字体大小 - final double? titleFontSize; - - /// 标题是否靠左 - final bool titleLeft; - - /// 是否隐藏关闭按钮 - final bool hideClose; - - /// 关闭按钮颜色 - final Color? closeColor; - - /// 关闭按钮图标尺寸 - final double? closeSize; - - /// 关闭按钮点击回调 - final PopupClick? closeClick; - - @override - State createState() => _TPopupBottomDisplayPanelState(); -} - -class _TPopupBottomDisplayPanelState - extends _TPopupBaseState { - @override - Widget buildHeader(BuildContext context) { - Widget header = Container( - alignment: widget.titleLeft ? Alignment.centerLeft : Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 16), - child: TText( - widget.title ?? '', - textColor: widget.titleColor ?? TTheme.of(context).textColorPrimary, - font: TTheme.of(context).fontTitleLarge?.withSize( - widget.titleFontSize?.toInt() ?? - TTheme.of(context).fontTitleLarge!.size.toInt()), - fontWeight: FontWeight.w700, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ); - - if (!widget.hideClose) { - header = Stack( - alignment: Alignment.centerLeft, - children: [ - Padding( - padding: - EdgeInsets.only(right: 40, left: widget.titleLeft ? 0 : 40), - child: header, - ), - Positioned( - right: 0, - child: IconButton( - icon: Icon( - TIcons.close, - color: widget.closeColor, - size: widget.closeSize, - ), - onPressed: widget.closeClick, - ), - ), - ], - ); - } - - return SizedBox( - height: widget.draggable - ? _TPopupBaseState._headerHeight - - _TPopupBaseState._dragHandleHeight - : _TPopupBaseState._headerHeight, - child: header, - ); - } - - @override - void _handleDragUpdate(DragUpdateDetails details) { - super._baseHandleDragUpdate(details); - - final progress = (_currentHeight - _minHeight) / (_maxHeight - _minHeight); - if (progress > 0.85 && !_isFullscreen) { - _toggleFullscreen(true); - } else if (progress < 0.75 && _isFullscreen) { - _toggleFullscreen(false); - } - } - - @override - void _handleDragEnd(DragEndDetails details) => - super._baseHandleDragEnd(details); -} - -/// 带确认的底部浮层面板 -class TPopupBottomConfirmPanel extends TPopupBasePanel { - const TPopupBottomConfirmPanel({ - super.key, - required super.child, - super.title, - super.titleColor, - this.leftText, - this.leftTextColor, - this.leftClick, - this.rightText, - this.rightTextColor, - this.rightClick, - this.titleFontSize, - this.leftTextFontSize, - this.rightTextFontSize, - super.backgroundColor, - super.radius, - super.draggable, - super.maxHeightRatio, - super.minHeightRatio, - }); - - /// 标题字体大小 - final double? titleFontSize; - - /// 左边文本 - final String? leftText; - - /// 左边文本字体大小 - final double? leftTextFontSize; - - /// 左边文本颜色 - final Color? leftTextColor; - - /// 左边文本点击回调 - final PopupClick? leftClick; - - /// 右边文本 - final String? rightText; - - /// 右边文本字体大小 - final double? rightTextFontSize; - - /// 右边文本颜色 - final Color? rightTextColor; - - /// 右边文本点击回调 - final PopupClick? rightClick; - - @override - State createState() => _TPopupBottomConfirmPanelState(); -} - -class _TPopupBottomConfirmPanelState - extends _TPopupBaseState { - @override - Widget buildHeader(BuildContext context) { - return SizedBox( - height: widget.draggable - ? _TPopupBaseState._headerHeight - - _TPopupBaseState._dragHandleHeight - : _TPopupBaseState._headerHeight, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildActionButton( - text: widget.leftText ?? context.resource.cancel, - color: - widget.leftTextColor ?? TTheme.of(context).textColorSecondary, - onTap: widget.leftClick, - left: true, - ), - Expanded( - child: Center( - child: TText( - widget.title ?? '', - textColor: - widget.titleColor ?? TTheme.of(context).textColorPrimary, - font: TTheme.of(context).fontTitleLarge?.withSize( - widget.titleFontSize?.toInt() ?? - TTheme.of(context).fontTitleLarge!.size.toInt()), - fontWeight: FontWeight.w700, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ), - _buildActionButton( - text: widget.rightText ?? context.resource.confirm, - color: - widget.rightTextColor ?? TTheme.of(context).brandNormalColor, - onTap: widget.rightClick, - left: false, - ), - ], - ), - ); - } - - Widget _buildActionButton({ - required String text, - required Color color, - required VoidCallback? onTap, - required bool left, - }) => - GestureDetector( - onTap: onTap, - child: Padding( - padding: EdgeInsets.only( - left: left ? 16 : 0, - right: left ? 0 : 16, - ), - child: TText( - text, - textColor: color, - font: (left - ? TTheme.of(context).fontBodyLarge - : TTheme.of(context).fontTitleMedium) - ?.withSize(left - ? widget.leftTextFontSize?.toInt() ?? - TTheme.of(context).fontBodyLarge!.size.toInt() - : widget.rightTextFontSize?.toInt() ?? - TTheme.of(context).fontTitleMedium!.size.toInt()), - fontWeight: left ? FontWeight.w400 : FontWeight.w600, - ), - ), - ); - - @override - void _handleDragUpdate(DragUpdateDetails details) { - super._baseHandleDragUpdate(details); - - const threshold = 0.15; - final progress = (_currentHeight - _minHeight) / (_maxHeight - _minHeight); - if (progress > (1 - threshold) && !_isFullscreen) { - _toggleFullscreen(true); - } else if (progress < threshold && _isFullscreen) { - _toggleFullscreen(false); - } - } - - @override - void _handleDragEnd(DragEndDetails details) => - super._baseHandleDragEnd(details); -} - -/// 居中浮层面板 -class TPopupCenterPanel extends StatelessWidget { - const TPopupCenterPanel({ - super.key, - required this.child, - this.closeUnderBottom = false, - this.closeColor, - this.closeClick, - this.backgroundColor, - this.radius, - this.closeSize, - }); - - /// 子控件 - final Widget child; - - /// 关闭按钮是否在视图框下方 - final bool closeUnderBottom; - - /// 关闭按钮颜色 - final Color? closeColor; - - /// 关闭按钮图标尺寸 - final double? closeSize; - - /// 关闭按钮点击回调 - final PopupClick? closeClick; - - /// 背景颜色 - final Color? backgroundColor; - - /// 圆角 - final double? radius; - - @override - Widget build(BuildContext context) { - if (closeUnderBottom) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 40), - Container( - margin: const EdgeInsets.symmetric(vertical: 24), - decoration: BoxDecoration( - color: backgroundColor ?? TTheme.of(context).bgColorContainer, - borderRadius: BorderRadius.circular( - radius ?? TTheme.of(context).radiusExtraLarge), - ), - child: child, - ), - IconButton( - icon: Icon( - TIcons.close_circle, - color: closeColor ?? TTheme.of(context).fontWhColor1, - size: closeSize ?? 32, - ), - onPressed: closeClick, - ), - ], - ); - } - - return Container( - decoration: BoxDecoration( - color: backgroundColor ?? TTheme.of(context).bgColorContainer, - borderRadius: BorderRadius.circular( - radius ?? TTheme.of(context).radiusExtraLarge), - ), - child: Stack( - children: [ - child, - Positioned( - top: TTheme.of(context).spacer8, - right: TTheme.of(context).spacer8, - child: IconButton( - icon: Icon( - TIcons.close, - color: closeColor, - size: closeSize, - ), - onPressed: closeClick, - ), - ), - ], - ), - ); - } -} diff --git a/tdesign-component/lib/src/components/popup/t_popup_route.dart b/tdesign-component/lib/src/components/popup/t_popup_route.dart deleted file mode 100644 index 226f6eaf7..000000000 --- a/tdesign-component/lib/src/components/popup/t_popup_route.dart +++ /dev/null @@ -1,300 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; - -const Duration _bottomSheetEnterDuration = Duration(milliseconds: 250); -const Duration _bottomSheetExitDuration = Duration(milliseconds: 200); - -/// 从屏幕弹出的方向 -enum SlideTransitionFrom { top, right, left, bottom, center } - -/// 从屏幕的某个方向滑动弹出的Dialog框的路由,比如从顶部、底部、左、右滑出页面 -class TSlidePopupRoute extends PopupRoute { - TSlidePopupRoute({ - required this.builder, - this.barrierLabel, - this.modalBarrierColor = Colors.black54, - this.isDismissible = true, - this.modalBarrierFull = false, - this.slideTransitionFrom = SlideTransitionFrom.bottom, - this.modalWidth, - this.modalHeight, - this.modalTop = 0, - this.modalLeft = 0, - this.open, - this.opened, - this.close, - this.barrierClick, - this.focusMove = false, - }); - - /// 控件构建器 - final WidgetBuilder builder; - - /// 蒙层颜色 - final Color? modalBarrierColor; - - /// 点击蒙层能否关闭 - final bool isDismissible; - - /// 是否全屏显示蒙层 - final bool modalBarrierFull; - - /// 设置从屏幕的哪个方向滑出 - final SlideTransitionFrom slideTransitionFrom; - - /// 弹出框宽度 - final double? modalWidth; - - /// 弹出框高度 - final double? modalHeight; - - /// 弹出框顶部距离 - final double? modalTop; - - /// 弹出框左侧距离 - final double? modalLeft; - - /// 打开前事件 - final VoidCallback? open; - - /// 打开后事件 - final VoidCallback? opened; - - /// 关闭前事件 - final VoidCallback? close; - - /// 蒙层点击事件,仅在[modalBarrierFull]为false时触发 - final VoidCallback? barrierClick; - - /// 是否有输入框获取焦点时整体平移避免输入框被遮挡 - final bool focusMove; - - Color get _barrierColor => modalBarrierColor ?? Colors.black54; - - @override - Duration get transitionDuration => _bottomSheetEnterDuration; - - @override - Duration get reverseTransitionDuration => _bottomSheetExitDuration; - - @override - bool get barrierDismissible => isDismissible; - - @override - final String? barrierLabel; - - @override - Color get barrierColor => modalBarrierFull ? _barrierColor : Colors.transparent; - - /// 键盘焦点对象的Y坐标 - var _focusY = 0.0; - - /// 键盘焦点对象的高度 - var _focusHeight = 0.0; - - /// 键盘出现后bottom的偏移量 - var _lastBottom = 0.0; - - // 实现转场动画 - @override - Widget buildTransitions( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, - ) { - var animValue = decelerateEasing.transform(animation.value); - return Stack( - children: [ - if (!modalBarrierFull) - _getPositionWidget( - context, - IgnorePointer( - ignoring: true, - child: Container( - color: _barrierColor.withAlpha((animValue * _barrierColor.alpha).toInt()), - child: GestureDetector( - onTap: () { - barrierClick?.call(); - if (isDismissible) { - Navigator.pop(context); - } - }, - onDoubleTap: () { - barrierClick?.call(); - if (isDismissible) { - Navigator.pop(context); - } - }, - ), - ), - ), - ), - _getPositionWidget( - context, - Align( - alignment: slideTransitionFromToAlignment(slideTransitionFrom), - child: slideTransitionFrom != SlideTransitionFrom.center - ? FractionalTranslation( - translation: _getOffset(animValue, slideTransitionFrom), - child: ClipRect( - clipper: RectClipper(animValue, slideTransitionFrom), - child: child, - ), - ) - : Transform( - transform: Matrix4.diagonal3Values(animValue, animValue, 1), - alignment: Alignment.center, - child: child, - ), - ), - ), - ], - ); - } - - @override - Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { - return Material( - child: builder.call(context), - color: Colors.transparent, - ); - } - - @override - TickerFuture didPush() { - startFocusListener(navigator!.context); - open?.call(); - animation?.addStatusListener((status) { - if (status == AnimationStatus.completed) { - opened?.call(); - } - }); - return super.didPush(); - } - - @override - void dispose() { - stopFocusListener(navigator!.context); - super.dispose(); - } - - /// 监听焦点变化 - void startFocusListener(BuildContext context) { - FocusManager.instance.addListener(_handleFocusChange); - } - - /// 停止监听焦点变化 - void stopFocusListener(BuildContext context) { - FocusManager.instance.removeListener(_handleFocusChange); - } - - void _handleFocusChange() { - // 获取当前的焦点节点 - var focusNode = FocusManager.instance.primaryFocus; - if (focusNode != null && focusNode.context != null) { - var renderObject = focusNode.context!.findRenderObject(); - if (renderObject is RenderPointerListener) { - _focusY = renderObject.localToGlobal(Offset.zero).dy; - _focusHeight = renderObject.size.height; - } - } - (focusNode?.context as Element?)?.markNeedsBuild(); - } - - @override - bool didPop(T? result) { - close?.call(); - return super.didPop(result); - } - - Widget _getPositionWidget(BuildContext context, Widget child) { - var bottom = 0.0; - var mediaQuery = MediaQuery.of(context); - if (slideTransitionFrom == SlideTransitionFrom.bottom) { - bottom = mediaQuery.viewInsets.bottom; - } else { - if ((_focusY + mediaQuery.viewInsets.bottom + _focusHeight) > mediaQuery.size.height) { - bottom = -(mediaQuery.size.height - (_focusY + mediaQuery.viewInsets.bottom + _focusHeight + 10)); - _lastBottom = bottom; - } else { - if (_lastBottom > 0.0) { - bottom = max((_lastBottom -= 5), 0).toDouble(); - } - } - } - - var screenSize = mediaQuery.size; - var _modalTop = (modalTop ?? 0).clamp(0, screenSize.height).toDouble() - (focusMove ? bottom : 0); - var _modalLeft = (modalLeft ?? 0).clamp(0, screenSize.width).toDouble(); - var _modalHeight = (modalHeight ?? screenSize.height).clamp(0, screenSize.height - _modalTop).toDouble(); - var _modalWidth = (modalWidth ?? screenSize.width).clamp(0, screenSize.width - _modalLeft).toDouble(); - return Positioned( - top: _modalTop, - bottom: screenSize.height - _modalTop - _modalHeight, - left: _modalLeft, - right: screenSize.width - _modalLeft - _modalWidth, - child: child, - ); - } - - Offset _getOffset(double animValue, SlideTransitionFrom slideTransitionFrom) { - switch (slideTransitionFrom) { - case SlideTransitionFrom.top: - return Offset(0, animValue - 1); - case SlideTransitionFrom.right: - return Offset(1 - animValue, 0); - case SlideTransitionFrom.left: - return Offset(animValue - 1, 0); - case SlideTransitionFrom.bottom: - return Offset(0, 1 - animValue); - default: - return const Offset(0, 0); - } - } -} - -Alignment slideTransitionFromToAlignment(SlideTransitionFrom from) { - switch (from) { - case SlideTransitionFrom.top: - return Alignment.topCenter; - case SlideTransitionFrom.right: - return Alignment.centerRight; - case SlideTransitionFrom.left: - return Alignment.centerLeft; - case SlideTransitionFrom.bottom: - return Alignment.bottomCenter; - case SlideTransitionFrom.center: - return Alignment.center; - } -} - -class RectClipper extends CustomClipper { - final double animValue; - final SlideTransitionFrom slideTransitionFrom; - - RectClipper(this.animValue, this.slideTransitionFrom); - - @override - Rect getClip(Size size) { - switch (slideTransitionFrom) { - case SlideTransitionFrom.top: - return Rect.fromLTWH(0, size.height * (1 - animValue), size.width, size.height); - case SlideTransitionFrom.right: - return Rect.fromLTWH(0, 0, size.width * animValue, size.height); - case SlideTransitionFrom.left: - return Rect.fromLTWH(size.width * (1 - animValue), 0, size.width, size.height); - case SlideTransitionFrom.bottom: - return Rect.fromLTWH(0, 0, size.width, size.height * animValue); - default: - return Rect.fromLTWH(0, 0, size.width, size.height); - } - } - - @override - bool shouldReclip(covariant CustomClipper oldClipper) { - return oldClipper != this; - } -} diff --git a/tdesign-component/lib/src/components/popup/t_popup_tracker.dart b/tdesign-component/lib/src/components/popup/t_popup_tracker.dart new file mode 100644 index 000000000..9b177475a --- /dev/null +++ b/tdesign-component/lib/src/components/popup/t_popup_tracker.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 't_popup.dart'; + +/// 按 Navigator 追踪当前展示的 [TPopupHandle] 栈。 +class TPopupTracker { + TPopupTracker._(); + + static final Map> _stacks = {}; + + static void push(NavigatorState navigator, TPopupHandle handle) { + _stacks.putIfAbsent(navigator, () => []).add(handle); + } + + static void remove(NavigatorState navigator, TPopupHandle handle) { + _stacks[navigator]?.remove(handle); + if (_stacks[navigator]?.isEmpty ?? false) { + _stacks.remove(navigator); + } + } + + static TPopupHandle? top(NavigatorState navigator) { + final stack = _stacks[navigator]; + if (stack == null || stack.isEmpty) { + return null; + } + return stack.last; + } +} diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart new file mode 100644 index 000000000..a3a3f11f8 --- /dev/null +++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart @@ -0,0 +1,32 @@ +import 'package:flutter/widgets.dart'; + +/// 浮层出现位置。 +enum TPopupPlacement { top, left, right, bottom, center } + +/// [TPopup.show] 未传 [cancel]/[confirm] 时的占位,表示使用默认「取消」「确定」文案。 +/// +/// 传 `cancel: null` / `confirm: null` 可隐藏对应侧;两侧均为 `null` 且无 +/// [cancelBuilder]/[confirmBuilder] 时不渲染操作栏。 +class TPopupActionDefault extends StatelessWidget { + const TPopupActionDefault({super.key}); + + @override + Widget build(BuildContext context) => const SizedBox.shrink(); +} + +/// 与 [TPopupActionDefault] 同一实例,供默认参数使用。 +const Widget kPopupActionDefault = TPopupActionDefault(); + +/// 显隐变化触发来源。 +enum TPopupTrigger { + overlay, + closeBtn, + cancelBtn, + confirmBtn, + programmatic, +} + +typedef TPopupVisibleChangeCallback = void Function( + bool visible, + TPopupTrigger trigger, +); diff --git a/tdesign-component/lib/src/util/t_toolbar_pressable.dart b/tdesign-component/lib/src/util/t_toolbar_pressable.dart new file mode 100644 index 000000000..7ee6d2de3 --- /dev/null +++ b/tdesign-component/lib/src/util/t_toolbar_pressable.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; + +import '../theme/t_spacers.dart'; +import '../theme/t_theme.dart'; + +/// 工具栏文字/图标按钮统一按压反馈:按下时整体透明度动画。 +/// +/// 用于 [TPicker]、[TPopup] 等「取消 | 标题 | 确认」类工具栏,后续组件请复用。 +class TToolbarPressable extends StatefulWidget { + const TToolbarPressable({ + super.key, + required this.child, + this.onTap, + this.padding, + this.enabled = true, + this.pressDuration = kToolbarPressDuration, + this.pressedOpacity = kToolbarPressedOpacity, + this.mergeTextStyle, + this.mergeIconTheme, + }); + + /// 按压动画时长(与 TDesign 工具栏规范一致)。 + static const Duration kToolbarPressDuration = Duration(milliseconds: 100); + + /// 按下时的目标透明度。 + static const double kToolbarPressedOpacity = 0.5; + + final Widget child; + final VoidCallback? onTap; + final EdgeInsetsGeometry? padding; + final bool enabled; + final Duration pressDuration; + final double pressedOpacity; + + /// 为子树 [Text] 提供默认样式(merge 语义,子控件已有样式优先)。 + final TextStyle? mergeTextStyle; + + /// 为子树 [Icon] 提供默认样式。 + final IconThemeData? mergeIconTheme; + + @override + State createState() => _TToolbarPressableState(); +} + +class _TToolbarPressableState extends State { + bool _pressed = false; + + void _setPressed(bool value) { + if (!widget.enabled || widget.onTap == null) { + return; + } + if (_pressed == value) { + return; + } + setState(() => _pressed = value); + } + + @override + Widget build(BuildContext context) { + final theme = TTheme.of(context); + final padding = widget.padding ?? + EdgeInsets.symmetric( + horizontal: theme.spacer8, + vertical: theme.spacer12, + ); + + Widget child = widget.child; + if (widget.mergeTextStyle != null) { + child = DefaultTextStyle.merge( + style: widget.mergeTextStyle!, + child: child, + ); + } + if (widget.mergeIconTheme != null) { + child = IconTheme.merge( + data: widget.mergeIconTheme!, + child: child, + ); + } + + final enabled = widget.enabled && widget.onTap != null; + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: enabled ? (_) => _setPressed(true) : null, + onTapUp: enabled ? (_) => _setPressed(false) : null, + onTapCancel: enabled ? () => _setPressed(false) : null, + onTap: enabled ? widget.onTap : null, + child: AnimatedOpacity( + duration: widget.pressDuration, + opacity: _pressed ? widget.pressedOpacity : 1, + child: Padding(padding: padding, child: child), + ), + ); + } +} diff --git a/tdesign-component/lib/tdesign_flutter.dart b/tdesign-component/lib/tdesign_flutter.dart index 82d5e6261..5c42d90fa 100644 --- a/tdesign-component/lib/tdesign_flutter.dart +++ b/tdesign-component/lib/tdesign_flutter.dart @@ -54,8 +54,7 @@ export 'src/components/picker/t_picker_option.dart'; export 'src/components/picker/t_picker_value.dart'; export 'src/components/popover/t_popover.dart'; export 'src/components/popover/t_popover_widget.dart'; -export 'src/components/popup/t_popup_panel.dart'; -export 'src/components/popup/t_popup_route.dart'; +export 'src/components/popup/t_popup.dart'; export 'src/components/progress/t_progress.dart'; export 'src/components/radio/t_radio.dart'; export 'src/components/rate/t_rate.dart'; @@ -107,3 +106,4 @@ export 'src/theme/t_shadows.dart'; export 'src/theme/t_spacers.dart'; export 'src/theme/t_theme.dart'; export 'src/util/platform_util.dart'; +export 'src/util/t_toolbar_pressable.dart'; diff --git a/tdesign-component/test/helpers/popup_test_helpers.dart b/tdesign-component/test/helpers/popup_test_helpers.dart new file mode 100644 index 000000000..702cceb1d --- /dev/null +++ b/tdesign-component/test/helpers/popup_test_helpers.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +import 'popup_test_resource.dart'; + +Widget wrapPopupTest( + Widget child, { + PopupTestResourceDelegate? resource, +}) { + final resolved = resource ?? PopupTestResourceDelegate.zh(); + bindPopupTestResource(resolved); + return MaterialApp( + locale: resolved.locale, + home: TTheme( + data: TThemeData.defaultData(), + child: Scaffold(body: child), + ), + ); +} + +Future openPopup( + WidgetTester tester, { + required VoidCallback onPressed, + PopupTestResourceDelegate? resource, +}) async { + await tester.pumpWidget( + wrapPopupTest( + Builder( + builder: (context) => ElevatedButton( + onPressed: onPressed, + child: const Text('open'), + ), + ), + resource: resource ?? PopupTestResourceDelegate.zh(), + ), + ); + await tester.tap(find.text('open')); +} diff --git a/tdesign-component/test/helpers/popup_test_resource.dart b/tdesign-component/test/helpers/popup_test_resource.dart new file mode 100644 index 000000000..2537875d9 --- /dev/null +++ b/tdesign-component/test/helpers/popup_test_resource.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +/// Popup 测试用文案资源:仅覆盖 cancel / confirm,其余与库内默认中文一致。 +class PopupTestResourceDelegate extends TResourceDelegate { + PopupTestResourceDelegate({ + required this.cancelText, + required this.confirmText, + required this.locale, + }); + + factory PopupTestResourceDelegate.zh() => PopupTestResourceDelegate( + cancelText: '取消', + confirmText: '确定', + locale: const Locale('zh'), + ); + + factory PopupTestResourceDelegate.en() => PopupTestResourceDelegate( + cancelText: 'Cancel', + confirmText: 'Confirm', + locale: const Locale('en'), + ); + + final String cancelText; + final String confirmText; + final Locale locale; + + @override + String get cancel => cancelText; + + @override + String get confirm => confirmText; + + @override + String get open => '开'; + + @override + String get close => '关'; + + @override + String get badgeZero => '0'; + + @override + String get other => '其它'; + + @override + String get reset => '重置'; + + @override + String get loading => '加载中'; + + @override + String get loadingWithPoint => '加载中...'; + + @override + String get knew => '知道了'; + + @override + String get refreshing => '正在刷新'; + + @override + String get releaseRefresh => '松开刷新'; + + @override + String get pullToRefresh => '下拉刷新'; + + @override + String get completeRefresh => '刷新完成'; + + @override + String get days => '天'; + + @override + String get hours => '时'; + + @override + String get minutes => '分'; + + @override + String get seconds => '秒'; + + @override + String get milliseconds => '毫秒'; + + @override + String get yearLabel => '年'; + + @override + String get monthLabel => '月'; + + @override + String get dateLabel => '日'; + + @override + String get weeksLabel => '周'; + + @override + String get sunday => '日'; + + @override + String get monday => '一'; + + @override + String get tuesday => '二'; + + @override + String get wednesday => '三'; + + @override + String get thursday => '四'; + + @override + String get friday => '五'; + + @override + String get saturday => '六'; + + @override + String get year => ' 年'; + + @override + String get january => '1 月'; + + @override + String get february => '2 月'; + + @override + String get march => '3 月'; + + @override + String get april => '4 月'; + + @override + String get may => '5 月'; + + @override + String get june => '6 月'; + + @override + String get july => '7 月'; + + @override + String get august => '8 月'; + + @override + String get september => '9 月'; + + @override + String get october => '10 月'; + + @override + String get november => '11 月'; + + @override + String get december => '12 月'; + + @override + String get time => '时间'; + + @override + String get start => '开始'; + + @override + String get end => '结束'; + + @override + String get notRated => '未评分'; + + @override + String get cascadeLabel => '选择选项'; + + @override + String get back => '返回'; + + @override + String get top => '顶部'; + + @override + String get emptyData => '暂无数据'; +} + +/// 在测试中注入 [resource];与业务侧 `TTheme.setResourceBuilder` 用法一致。 +void bindPopupTestResource(PopupTestResourceDelegate resource) { + TTheme.setResourceBuilder((_) => resource, needAlwaysBuild: true); +} + +/// 恢复为库内默认资源(中文 cancel/confirm)。 +void resetPopupTestResource() { + TTheme.setResourceBuilder((_) => null, needAlwaysBuild: false); +} diff --git a/tdesign-component/test/t_popup_config_test.dart b/tdesign-component/test/t_popup_config_test.dart new file mode 100644 index 000000000..41a815d3e --- /dev/null +++ b/tdesign-component/test/t_popup_config_test.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tdesign_flutter/src/components/popup/t_popup_config.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +void main() { + group('TPopupConfig', () { + test('create 默认 placement 为 bottom', () { + final config = TPopupConfig.create(child: const SizedBox()); + expect(config.placement, TPopupPlacement.bottom); + }); + + test('bottom 默认渲染操作栏', () { + final config = TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + ); + expect(config.useActionHeader, isTrue); + expect(config.showCancelSlot, isTrue); + expect(config.showConfirmSlot, isTrue); + expect(config.hasBuiltInHeader, isTrue); + }); + + test('cancel 与 confirm 均为 null 时不渲染操作栏', () { + final config = TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + ); + expect(config.useActionHeader, isFalse); + expect(config.showCancelSlot, isFalse); + expect(config.showConfirmSlot, isFalse); + }); + + test('create 忽略 bottom 的 closeBtn', () { + final config = TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + closeBtn: true, + onCancel: () {}, + ); + expect(config.closeBtn, isFalse); + expect(config.useActionHeader, isTrue); + }); + + test('create center 默认 closeBtn 与 closeBelowContent', () { + final config = TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.center, + ); + expect(config.closeBtn, isTrue); + expect(config.closeBelowContent, isTrue); + }); + + test('create center closeBtn false', () { + final config = TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.center, + closeBtn: false, + ); + expect(config.closeBtn, isFalse); + }); + + test('create top 剥离 title 与 headerBuilder', () { + final config = TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.top, + title: '标题', + headerBuilder: (_) => const Text('h'), + onCancel: () {}, + ); + expect(config.title, isNull); + expect(config.headerBuilder, isNull); + expect(config.useActionHeader, isFalse); + expect(config.hasBuiltInHeader, isFalse); + }); + + test('hasBuiltInHeader 识别 title 与 headerBuilder', () { + expect( + TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + title: '标题', + cancel: null, + confirm: null, + ).hasBuiltInHeader, + isTrue, + ); + expect( + TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + title: '标题', + ).useActionHeader, + isTrue, + ); + expect( + TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + headerBuilder: (_) => const Text('h'), + ).hasBuiltInHeader, + isTrue, + ); + expect( + TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.top, + ).hasBuiltInHeader, + isFalse, + ); + }); + + test('assertPlacementParams 在 debug 模式不抛错', () { + final config = TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.left, + height: 100, + width: 200, + ); + expect(() => config.assertPlacementParams(), returnsNormally); + }); + }); +} diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart new file mode 100644 index 000000000..c54165357 --- /dev/null +++ b/tdesign-component/test/t_popup_coverage_test.dart @@ -0,0 +1,374 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tdesign_flutter/src/components/popup/_popup_layout.dart'; +import 'package:tdesign_flutter/src/components/popup/t_popup_config.dart'; +import 'package:tdesign_flutter/src/components/popup/t_popup_types.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +import 'helpers/popup_test_helpers.dart'; +import 'helpers/popup_test_resource.dart'; + +void main() { + tearDown(resetPopupTestResource); + + group('TPopup 覆盖率补充', () { + testWidgets('bottom 仅标题行(无操作栏)', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 160, + title: '仅标题行', + cancel: null, + confirm: null, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('仅标题行'), findsOneWidget); + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsNothing); + }); + + testWidgets('bottom 仅标题且 titleAlignLeft', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 160, + title: '左对齐标题', + titleAlignLeft: true, + cancel: null, + confirm: null, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('左对齐标题'), findsOneWidget); + }); + + testWidgets('bottom 仅隐藏 confirm 槽位', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 160, + confirm: null, + onCancel: () => TPopup.close( + tester.element(find.text('open')), + ), + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('取消'), findsOneWidget); + expect(find.text('确定'), findsNothing); + }); + + testWidgets('bottom 仅隐藏 cancel 槽位', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 160, + cancel: null, + onConfirm: () => TPopup.close( + tester.element(find.text('open')), + ), + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsOneWidget); + }); + + testWidgets('center closeBuilder 自定义关闭区', (tester) async { + late BuildContext hostContext; + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.center, + width: 160, + height: 120, + closeBuilder: (ctx) => TextButton( + onPressed: () => TPopup.close(ctx), + child: const Text('builder关闭'), + ), + child: const SizedBox(height: 80, width: 120), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('builder关闭'), findsOneWidget); + await tester.tap(find.text('builder关闭')); + await tester.pumpAndSettle(); + }); + + testWidgets('center 默认关闭按钮触发 onCloseBtn', (tester) async { + var closeBtnCount = 0; + late BuildContext hostContext; + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.center, + width: 120, + height: 120, + onCloseBtn: () => closeBtnCount++, + child: const SizedBox(height: 80, width: 80), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.byIcon(TIcons.close_circle)); + await tester.pumpAndSettle(); + expect(closeBtnCount, 1); + }); + + testWidgets('bottom 无固定 height 贴底布局', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + child: const SizedBox(height: 80, width: 200), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byType(Positioned), findsWidgets); + }); + + testWidgets('top 无固定 height 可打开', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.top, + child: const SizedBox(height: 60, width: 200), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byType(Positioned), findsWidgets); + }); + + testWidgets('center closeBtn false 无下方关闭', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.center, + width: 100, + height: 100, + closeBtn: false, + child: const SizedBox(height: 80, width: 80), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byIcon(TIcons.close_circle), findsNothing); + expect(find.byType(Center), findsWidgets); + }); + + testWidgets('handle 重复 close 安全', (tester) async { + TPopupHandle? handle; + await openPopup( + tester, + onPressed: () { + handle = TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 80, + cancel: null, + confirm: null, + child: const SizedBox(height: 40), + ); + }, + ); + await tester.pumpAndSettle(); + handle!.close(); + handle!.close(); + await tester.pumpAndSettle(); + expect(handle!.isShowing, isFalse); + }); + + testWidgets('TPopupActionDefault 可构建', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: kPopupActionDefault, + ), + ), + ); + expect(find.byType(SizedBox), findsWidgets); + }); + + testWidgets('TToolbarPressable 按压与禁用', (tester) async { + var tapped = false; + await tester.pumpWidget( + MaterialApp( + home: TTheme( + data: TThemeData.defaultData(), + child: Scaffold( + body: TToolbarPressable( + onTap: () => tapped = true, + child: const Text('press'), + ), + ), + ), + ), + ); + await tester.tap(find.text('press')); + await tester.pumpAndSettle(); + expect(tapped, isTrue); + + await tester.pumpWidget( + MaterialApp( + home: TTheme( + data: TThemeData.defaultData(), + child: Scaffold( + body: TToolbarPressable( + onTap: null, + mergeTextStyle: const TextStyle(color: Colors.red), + mergeIconTheme: const IconThemeData(color: Colors.red), + child: const Icon(Icons.add), + ), + ), + ), + ), + ); + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + }); + }); + + group('TPopupConfig 覆盖率补充', () { + test('hasBuiltInHeader 识别 titleWidget', () { + expect( + TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + titleWidget: const Text('w'), + cancel: null, + confirm: null, + ).hasBuiltInHeader, + isTrue, + ); + }); + + test('isActionDefault 识别占位 Widget', () { + expect(TPopupConfig.isActionDefault(kPopupActionDefault), isTrue); + expect(TPopupConfig.isActionDefault(const Text('x')), isFalse); + expect(TPopupConfig.isActionDefault(null), isFalse); + }); + + test('assertPlacementParams 覆盖 width 与 top 操作栏提示', () { + expect( + () => TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + width: 200, + ).assertPlacementParams(), + returnsNormally, + ); + expect( + () => TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.top, + onCancel: () {}, + ).assertPlacementParams(), + returnsNormally, + ); + expect( + () => TPopupConfig.create( + child: const SizedBox(), + placement: TPopupPlacement.center, + height: 100, + closeBtn: false, + ).assertPlacementParams(), + returnsNormally, + ); + }); + }); + + group('PopupLayout 覆盖率补充', () { + const screen = Size(400, 800); + + testWidgets('bottom 无 height 且无 margin.top 贴底', (tester) async { + final layout = PopupLayout( + placement: TPopupPlacement.bottom, + screenSize: screen, + margin: EdgeInsets.zero, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack( + children: [layout.wrapPositioned(child: const SizedBox(height: 1))], + ), + ), + ), + ); + final positioned = tester.widget(find.byType(Positioned)); + expect(positioned.bottom, 0); + expect(positioned.top, isNull); + expect(positioned.height, isNull); + }); + + test('alignment center', () { + expect( + PopupLayout( + placement: TPopupPlacement.center, + screenSize: screen, + margin: EdgeInsets.zero, + ).alignment, + Alignment.center, + ); + }); + + testWidgets('left 默认 drawer 宽度', (tester) async { + final layout = PopupLayout( + placement: TPopupPlacement.left, + screenSize: screen, + margin: EdgeInsets.zero, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack( + children: [layout.wrapPositioned(child: const SizedBox())], + ), + ), + ), + ); + expect( + tester.widget(find.byType(Positioned)).width, + PopupLayout.defaultDrawerWidth, + ); + }); + }); +} diff --git a/tdesign-component/test/t_popup_layout_test.dart b/tdesign-component/test/t_popup_layout_test.dart new file mode 100644 index 000000000..c06eb0be7 --- /dev/null +++ b/tdesign-component/test/t_popup_layout_test.dart @@ -0,0 +1,204 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tdesign_flutter/src/components/popup/_popup_layout.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +void main() { + group('PopupLayout', () { + const screen = Size(400, 800); + + testWidgets('top placement 使用 height 与 margin', (tester) async { + final layout = PopupLayout( + placement: TPopupPlacement.top, + screenSize: screen, + margin: const EdgeInsets.only(top: 8, left: 4, right: 4), + height: 100, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack(children: [layout.wrapPositioned(child: const SizedBox(height: 50))]), + ), + ), + ); + final positioned = tester.widget(find.byType(Positioned)); + expect(positioned.top, 8); + expect(positioned.height, 100); + }); + + testWidgets('bottom placement 含 margin.top 与固定 height', (tester) async { + final layout = PopupLayout( + placement: TPopupPlacement.bottom, + screenSize: screen, + margin: const EdgeInsets.only(top: 100), + height: 200, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack(children: [layout.wrapPositioned(child: const SizedBox(height: 50))]), + ), + ), + ); + final positioned = tester.widget(find.byType(Positioned)); + expect(positioned.top, 100); + expect(positioned.height, 200); + }); + + testWidgets('bottom 无 height 有 margin.top 计算高度', (tester) async { + final layout = PopupLayout( + placement: TPopupPlacement.bottom, + screenSize: screen, + margin: const EdgeInsets.only(top: 50, bottom: 10), + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack( + children: [layout.wrapPositioned(child: const SizedBox(height: 1))], + ), + ), + ), + ); + final positioned = tester.widget(find.byType(Positioned)); + expect(positioned.top, 50); + expect(positioned.height, screen.height - 50 - 10); + }); + + testWidgets('left / right 使用默认或自定义 width', (tester) async { + for (final p in [TPopupPlacement.left, TPopupPlacement.right]) { + final layout = PopupLayout( + placement: p, + screenSize: screen, + margin: const EdgeInsets.only(top: 56), + width: 300, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack(children: [layout.wrapPositioned(child: const SizedBox())]), + ), + ), + ); + final positioned = tester.widget(find.byType(Positioned)); + expect(positioned.width, 300); + expect(positioned.top, 56); + } + }); + + testWidgets('center placement 使用 width 与 height', (tester) async { + final layout = PopupLayout( + placement: TPopupPlacement.center, + screenSize: screen, + margin: EdgeInsets.zero, + width: 200, + height: 150, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack(children: [layout.wrapPositioned(child: const SizedBox())]), + ), + ), + ); + expect(find.byType(Center), findsOneWidget); + final sizedBoxes = tester.widgetList( + find.descendant(of: find.byType(Center), matching: find.byType(SizedBox)), + ); + final sizedBox = sizedBoxes.firstWhere((w) => w.width == 200); + expect(sizedBox.height, 150); + }); + + test('slideOffset 五向偏移', () { + final layout = PopupLayout( + placement: TPopupPlacement.top, + screenSize: screen, + margin: EdgeInsets.zero, + ); + expect(layout.slideOffset(0), const Offset(0, -1)); + expect(layout.slideOffset(1), const Offset(0, 0)); + + final bottom = PopupLayout( + placement: TPopupPlacement.bottom, + screenSize: screen, + margin: EdgeInsets.zero, + ); + expect(bottom.slideOffset(0), const Offset(0, 1)); + + final left = PopupLayout( + placement: TPopupPlacement.left, + screenSize: screen, + margin: EdgeInsets.zero, + ); + expect(left.slideOffset(0.5), const Offset(-0.5, 0)); + + final right = PopupLayout( + placement: TPopupPlacement.right, + screenSize: screen, + margin: EdgeInsets.zero, + ); + expect(right.slideOffset(0.5), const Offset(0.5, 0)); + + final center = PopupLayout( + placement: TPopupPlacement.center, + screenSize: screen, + margin: EdgeInsets.zero, + ); + expect(center.slideOffset(0.5), Offset.zero); + }); + + testWidgets('centerLooseHeight 不限制高度', (tester) async { + final layout = PopupLayout( + placement: TPopupPlacement.center, + screenSize: screen, + margin: EdgeInsets.zero, + width: 100, + height: 80, + centerLooseHeight: true, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack( + children: [ + layout.wrapPositioned(child: const SizedBox(height: 200)), + ], + ), + ), + ), + ); + final center = tester.widget
(find.byType(Center)); + final sizedBox = center.child! as SizedBox; + expect(sizedBox.width, isNull); + expect(sizedBox.height, isNull); + }); + + test('resolvedMargin center 为零', () { + final layout = PopupLayout( + placement: TPopupPlacement.center, + screenSize: screen, + margin: const EdgeInsets.all(20), + ); + expect(layout.resolvedMargin(), EdgeInsets.zero); + }); + + test('alignment 各方向', () { + expect( + PopupLayout( + placement: TPopupPlacement.top, + screenSize: screen, + margin: EdgeInsets.zero, + ).alignment, + Alignment.topCenter, + ); + expect( + PopupLayout( + placement: TPopupPlacement.right, + screenSize: screen, + margin: EdgeInsets.zero, + ).alignment, + Alignment.centerRight, + ); + }); + }); +} diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart new file mode 100644 index 000000000..051f71bcb --- /dev/null +++ b/tdesign-component/test/t_popup_test.dart @@ -0,0 +1,793 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tdesign_flutter/tdesign_flutter.dart'; + +import 'helpers/popup_test_helpers.dart'; +import 'helpers/popup_test_resource.dart'; + +void main() { + tearDown(resetPopupTestResource); + + group('TPopup 国际化文案', () { + for (final resource in [ + PopupTestResourceDelegate.zh(), + PopupTestResourceDelegate.en(), + ]) { + testWidgets( + '${resource.locale.languageCode} 底部操作栏默认 cancel / confirm', + (tester) async { + var cancelCount = 0; + var confirmCount = 0; + late BuildContext hostContext; + + await openPopup( + tester, + resource: resource, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 200, + onCancel: () => cancelCount++, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text(resource.cancelText), findsOneWidget); + expect(find.text(resource.confirmText), findsOneWidget); + + await tester.tap(find.text(resource.cancelText)); + await tester.pumpAndSettle(); + expect(cancelCount, 1); + + await openPopup( + tester, + resource: resource, + onPressed: () { + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 200, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text(resource.confirmText)); + await tester.pumpAndSettle(); + expect(confirmCount, 1); + }, + ); + } + + testWidgets('同一用例内切换中英文资源', (tester) async { + late BuildContext hostContext; + + for (final resource in [ + PopupTestResourceDelegate.zh(), + PopupTestResourceDelegate.en(), + ]) { + await openPopup( + tester, + resource: resource, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 160, + onCancel: () {}, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text(resource.cancelText), findsOneWidget); + expect(find.text(resource.confirmText), findsOneWidget); + TPopup.close(hostContext); + await tester.pumpAndSettle(); + } + }); + }); + + group('TPopup 生命周期', () { + testWidgets('show 触发 onOpen / onOpened 各一次', (tester) async { + var openCount = 0; + var openedCount = 0; + late BuildContext hostContext; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 120, + onOpen: () => openCount++, + onOpened: () => openedCount++, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pump(); + expect(openCount, 1); + await tester.pumpAndSettle(); + expect(openedCount, 1); + + TPopup.close(hostContext); + await tester.pumpAndSettle(); + }); + + testWidgets('handle.close 与 TPopup.close 触发 onClose / onClosed', + (tester) async { + var closeCount = 0; + var closedCount = 0; + var visibleChanges = []; + late BuildContext hostContext; + TPopupHandle? handle; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + handle = TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 120, + onClose: () => closeCount++, + onClosed: () => closedCount++, + onVisibleChange: (v, _) => visibleChanges.add(v), + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + handle!.close(); + await tester.pumpAndSettle(); + expect(closeCount, 1); + expect(closedCount, 1); + expect(visibleChanges, [true, false]); + expect(handle!.isShowing, false); + }); + + testWidgets('重复 close 不会重复触发 onClose', (tester) async { + var closeCount = 0; + late BuildContext hostContext; + TPopupHandle? handle; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + handle = TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 100, + onClose: () => closeCount++, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + handle!.close(); + handle!.close(); + await tester.pumpAndSettle(); + expect(closeCount, 1); + }); + }); + + group('TPopup placement', () { + const placements = [ + TPopupPlacement.top, + TPopupPlacement.bottom, + TPopupPlacement.left, + TPopupPlacement.right, + TPopupPlacement.center, + ]; + for (final placement in placements) { + testWidgets('$placement 可正常打开关闭', (tester) async { + late BuildContext hostContext; + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: placement, + height: placement == TPopupPlacement.left || + placement == TPopupPlacement.right + ? null + : 120, + width: placement == TPopupPlacement.top || + placement == TPopupPlacement.bottom + ? null + : 200, + child: const SizedBox(height: 60, width: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byType(Stack), findsWidgets); + TPopup.close(hostContext); + await tester.pumpAndSettle(); + }); + } + }); + + group('TPopup Header', () { + testWidgets('底部操作栏:取消与确认', (tester) async { + var cancelCount = 0; + var confirmCount = 0; + late BuildContext hostContext; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 200, + title: '标题', + onCancel: () => cancelCount++, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('标题'), findsOneWidget); + await tester.tap(find.text('取消')); + await tester.pumpAndSettle(); + expect(cancelCount, 1); + + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 200, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('确定')); + await tester.pumpAndSettle(); + expect(confirmCount, 1); + }); + + testWidgets('autoCloseOnCancel 为 false 时不自动关闭', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 200, + autoCloseOnCancel: false, + onCancel: () {}, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('取消')); + await tester.pump(); + expect(find.text('取消'), findsOneWidget); + }); + + testWidgets('autoCloseOnConfirm 为 false 时不自动关闭', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 200, + autoCloseOnConfirm: false, + onConfirm: () {}, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('确定')); + await tester.pump(); + expect(find.text('确定'), findsOneWidget); + }); + + testWidgets('bottom 仅标题无操作栏', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 180, + title: '仅标题', + cancel: null, + confirm: null, + child: const SizedBox(height: 80), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('仅标题'), findsOneWidget); + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsNothing); + expect(find.byIcon(TIcons.close), findsNothing); + }); + + testWidgets('headerBuilder 自定义头部', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 160, + title: '被忽略', + headerBuilder: (_) => const Text('自定义头部'), + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('自定义头部'), findsOneWidget); + expect(find.text('被忽略'), findsNothing); + }); + + testWidgets('居中关闭在内容与下方', (tester) async { + late BuildContext hostContext; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.center, + width: 120, + height: 120, + child: const SizedBox(height: 80, width: 80), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byIcon(TIcons.close_circle), findsOneWidget); + await tester.tap(find.byIcon(TIcons.close_circle)); + await tester.pumpAndSettle(); + }); + + testWidgets('居中关闭在下方与示例一致 expand 不报错', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.center, + width: 240, + height: 240, + child: const SizedBox.expand(), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byIcon(TIcons.close_circle), findsOneWidget); + }); + + testWidgets('center 默认显示下方关闭 closeBtn false 隐藏', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.center, + width: 120, + height: 120, + closeBtn: false, + child: const SizedBox(height: 80, width: 80), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byIcon(TIcons.close_circle), findsNothing); + }); + + testWidgets('cancelBuilder / confirmBuilder', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 160, + cancelBuilder: (_) => const Text('自定义取消'), + confirmBuilder: (_) => const Text('自定义确认'), + onCancel: () {}, + onConfirm: () {}, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('自定义取消'), findsOneWidget); + expect(find.text('自定义确认'), findsOneWidget); + }); + }); + + group('TPopup 蒙层与行为', () { + testWidgets('点击蒙层关闭', (tester) async { + var overlayClose = 0; + late BuildContext hostContext; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 100, + onClosed: () => overlayClose++, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + // 点击蒙层区域(屏幕上方) + await tester.tapAt(const Offset(10, 10)); + await tester.pumpAndSettle(); + expect(overlayClose, 1); + }); + + testWidgets('closeOnOverlayClick 为 false 点击蒙层不关闭', (tester) async { + late BuildContext hostContext; + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 100, + closeOnOverlayClick: false, + onCancel: () {}, + onOverlayClick: () {}, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('取消'), findsOneWidget); + await tester.tapAt(const Offset(10, 10)); + await tester.pump(); + expect(find.text('取消'), findsOneWidget); + TPopup.close(hostContext); + await tester.pumpAndSettle(); + }); + + testWidgets('showOverlay false 且 preventScrollThrough', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 100, + showOverlay: false, + preventScrollThrough: true, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect( + find.byType(NotificationListener), + findsWidgets, + ); + }); + + testWidgets('overlayOpacity 与自定义颜色', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 80, + overlayColor: Colors.red, + overlayOpacity: 0.5, + child: const SizedBox(height: 40), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.pump(); + }); + + testWidgets('margin.top 底部日历式布局', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + margin: const EdgeInsets.only(top: 80), + child: const SizedBox(height: 200), + ); + }, + ); + await tester.pumpAndSettle(); + }); + }); + + group('TPopup 声明式', () { + testWidgets('initialVisible 自动打开', (tester) async { + await tester.pumpWidget( + wrapPopupTest( + const TPopup( + initialVisible: true, + placement: TPopupPlacement.bottom, + height: 100, + child: SizedBox(height: 60), + ), + ), + ); + await tester.pump(); + await tester.pumpAndSettle(); + expect(find.byType(SizedBox), findsWidgets); + }); + + testWidgets('dispose 时关闭弹层', (tester) async { + await tester.pumpWidget( + wrapPopupTest( + const TPopup( + initialVisible: true, + placement: TPopupPlacement.bottom, + height: 100, + child: SizedBox(height: 60), + ), + ), + ); + await tester.pumpAndSettle(); + await tester.pumpWidget(const SizedBox()); + await tester.pumpAndSettle(); + }); + }); + + group('TPopupHandle / Tracker', () { + testWidgets('连续打开仅保留一个(第二次无效)', (tester) async { + TPopupHandle? first; + await openPopup( + tester, + onPressed: () { + final ctx = tester.element(find.text('open')); + first = TPopup.show( + context: ctx, + placement: TPopupPlacement.bottom, + height: 80, + child: const SizedBox(height: 40), + ); + final second = TPopup.show( + context: ctx, + placement: TPopupPlacement.bottom, + height: 80, + child: const SizedBox(height: 40), + ); + expect(second.isShowing, isTrue); + expect(identical(first, second), isFalse); + }, + ); + await tester.pumpAndSettle(); + first?.close(); + await tester.pumpAndSettle(); + }); + + testWidgets('系统返回键关闭', (tester) async { + var closedCount = 0; + late BuildContext hostContext; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 100, + onClosed: () => closedCount++, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.binding.handlePopRoute(); + await tester.pumpAndSettle(); + expect(closedCount, 1); + }); + }); + + group('TPopup 扩展场景', () { + testWidgets('top 忽略 title 仅 child', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.top, + height: 120, + title: '顶部标题', + child: const Text('内容'), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('顶部标题'), findsNothing); + expect(find.text('内容'), findsOneWidget); + }); + + testWidgets('left / right 侧栏展开内容', (tester) async { + for (final placement in [ + TPopupPlacement.left, + TPopupPlacement.right, + ]) { + late BuildContext hostContext; + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: placement, + width: 240, + child: const SizedBox(height: 40), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.byType(Expanded), findsOneWidget); + TPopup.close(hostContext); + await tester.pumpAndSettle(); + } + }); + + testWidgets('titleWidget 与 cancelBtn / confirmBtn 文案', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 180, + titleWidget: const Text('Widget标题'), + cancelBtn: '左', + confirmBtn: '右', + child: const SizedBox(height: 40), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('Widget标题'), findsOneWidget); + expect(find.text('左'), findsOneWidget); + expect(find.text('右'), findsOneWidget); + }); + + testWidgets('destroyOnClose 与自定义 close 组件', (tester) async { + late BuildContext hostContext; + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.center, + width: 140, + destroyOnClose: true, + close: const Text('关'), + onCloseBtn: () {}, + child: const SizedBox(height: 60, width: 120), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('关')); + await tester.pumpAndSettle(); + }); + + testWidgets('onOverlayClick 且点击蒙层关闭', (tester) async { + var overlayClick = 0; + late BuildContext hostContext; + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + TPopup.show( + context: hostContext, + placement: TPopupPlacement.bottom, + height: 100, + onOverlayClick: () => overlayClick++, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tapAt(const Offset(10, 10)); + await tester.pumpAndSettle(); + expect(overlayClick, 1); + }); + + testWidgets('TPopup.close 无 handle 时 maybePop', (tester) async { + await tester.pumpWidget( + MaterialApp( + home: TTheme( + data: TThemeData.defaultData(), + child: Scaffold( + body: Builder( + builder: (context) { + return ElevatedButton( + onPressed: () => TPopup.close(context), + child: const Text('close'), + ); + }, + ), + ), + ), + ), + ); + await tester.tap(find.text('close')); + await tester.pump(); + }); + + testWidgets('show 返回的 handle 关闭后 isShowing 为 false', (tester) async { + TPopupHandle? handle; + await openPopup( + tester, + onPressed: () { + handle = TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 80, + child: const SizedBox(height: 40), + ); + }, + ); + await tester.pumpAndSettle(); + handle!.close(); + await tester.pumpAndSettle(); + expect(handle!.isShowing, isFalse); + handle!.close(); + }); + }); + + group('TPopup 自定义控件', () { + testWidgets('自定义 cancel / confirm / close 组件', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + context: tester.element(find.text('open')), + placement: TPopupPlacement.bottom, + height: 200, + cancel: const Text('自定义取消'), + confirm: const Text('自定义确认'), + onCancel: () {}, + onConfirm: () {}, + child: const SizedBox(height: 60), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('自定义取消'), findsOneWidget); + expect(find.text('自定义确认'), findsOneWidget); + }); + }); +} diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md index ec55fb953..a9a6d3cd1 100644 --- a/tdesign-site/src/popup/README.md +++ b/tdesign-site/src/popup/README.md @@ -430,7 +430,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
   Widget _buildPopFromCenterWithUnderClose(BuildContext context) {
     return TButton(
-      text: '居中弹出层-关闭在下方',
+      text: '居中弹出层-自定义下方按钮',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,

From 2e5ee23f6c42b006e3c0602ef8e1ccfb71ea004e Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Thu, 21 May 2026 08:59:57 +0000
Subject: [PATCH 02/27] [autofix.ci] apply automated fixes

---
 .../example/assets/api/popup_api.md           |  98 +---
 .../code/popup._buildApiAutoCloseFalse.txt    |  23 -
 .../code/popup._buildApiBackgroundColor.txt   |  18 -
 .../popup._buildApiCancelBtnConfirmBtn.txt    |  21 -
 .../popup._buildApiCancelConfirmBuilders.txt  |  30 --
 .../popup._buildApiCancelConfirmWidgets.txt   |  23 -
 .../code/popup._buildApiCloseBuilder.txt      |  25 -
 .../popup._buildApiCloseOverlayClickFalse.txt |  17 -
 .../code/popup._buildApiHeaderBuilder.txt     |  26 -
 .../assets/code/popup._buildApiOverlay.txt    |  18 -
 .../code/popup._buildApiTitleAlignLeft.txt    |  20 -
 .../code/popup._buildApiTitleWidget.txt       |  31 --
 .../code/popup._buildBottomActionBar.txt      |  22 -
 .../code/popup._buildBottomTitleOnly.txt      |  17 -
 .../assets/code/popup._buildCenterClose.txt   |  27 --
 ...up._buildPopFromBottomWithCustomAction.txt |  37 --
 ...popup._buildPopFromBottomWithOperation.txt |  24 -
 .../popup._buildPopFromBottomWithTitle.txt    |  19 -
 tdesign-site/src/indexes/README.md            | 132 +++--
 tdesign-site/src/popup/README.md              | 456 +++++++-----------
 20 files changed, 236 insertions(+), 848 deletions(-)
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiOverlay.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildBottomActionBar.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildCenterClose.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt
 delete mode 100644 tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt

diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md
index 8ad1680cc..414b436c9 100644
--- a/tdesign-component/example/assets/api/popup_api.md
+++ b/tdesign-component/example/assets/api/popup_api.md
@@ -1,97 +1 @@
-## API
-### TSlidePopupRoute
-#### 简介
-从屏幕的某个方向滑动弹出的Dialog框的路由,比如从顶部、底部、左、右滑出页面
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| barrierClick | VoidCallback? | - | 蒙层点击事件,仅在[modalBarrierFull]为false时触发 |
-| barrierLabel |  | - |  |
-| builder | WidgetBuilder | - | 控件构建器 |
-| close | VoidCallback? | - | 关闭前事件 |
-| focusMove | bool | false | 是否有输入框获取焦点时整体平移避免输入框被遮挡 |
-| isDismissible | bool | true | 点击蒙层能否关闭 |
-| modalBarrierColor | Color? | Colors.black54 | 蒙层颜色 |
-| modalBarrierFull | bool | false | 是否全屏显示蒙层 |
-| modalHeight | double? | - | 弹出框高度 |
-| modalLeft | double? | 0 | 弹出框左侧距离 |
-| modalTop | double? | 0 | 弹出框顶部距离 |
-| modalWidth | double? | - | 弹出框宽度 |
-| open | VoidCallback? | - | 打开前事件 |
-| opened | VoidCallback? | - | 打开后事件 |
-| slideTransitionFrom | SlideTransitionFrom | SlideTransitionFrom.bottom | 设置从屏幕的哪个方向滑出 |
-
-```
-```
-
-### TPopupBottomDisplayPanel
-#### 简介
-右上角带关闭的底部浮层面板
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| backgroundColor |  | - |  |
-| child |  | - |  |
-| closeClick | PopupClick? | - | 关闭按钮点击回调 |
-| closeColor | Color? | - | 关闭按钮颜色 |
-| closeSize | double? | - | 关闭按钮图标尺寸 |
-| draggable |  | - |  |
-| hideClose | bool | false | 是否隐藏关闭按钮 |
-| key |  | - |  |
-| maxHeightRatio |  | - |  |
-| minHeightRatio |  | - |  |
-| radius |  | - |  |
-| title |  | - |  |
-| titleColor |  | - |  |
-| titleFontSize | double? | - | 标题字体大小 |
-| titleLeft | bool | false | 标题是否靠左 |
-
-```
-```
-
-### TPopupBottomConfirmPanel
-#### 简介
-带确认的底部浮层面板
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| backgroundColor |  | - |  |
-| child |  | - |  |
-| draggable |  | - |  |
-| key |  | - |  |
-| leftClick | PopupClick? | - | 左边文本点击回调 |
-| leftText | String? | - | 左边文本 |
-| leftTextColor | Color? | - | 左边文本颜色 |
-| leftTextFontSize | double? | - | 左边文本字体大小 |
-| maxHeightRatio |  | - |  |
-| minHeightRatio |  | - |  |
-| radius |  | - |  |
-| rightClick | PopupClick? | - | 右边文本点击回调 |
-| rightText | String? | - | 右边文本 |
-| rightTextColor | Color? | - | 右边文本颜色 |
-| rightTextFontSize | double? | - | 右边文本字体大小 |
-| title |  | - |  |
-| titleColor |  | - |  |
-| titleFontSize | double? | - | 标题字体大小 |
-
-```
-```
-
-### TPopupCenterPanel
-#### 简介
-居中浮层面板
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| backgroundColor | Color? | - | 背景颜色 |
-| child | Widget | - | 子控件 |
-| closeClick | PopupClick? | - | 关闭按钮点击回调 |
-| closeColor | Color? | - | 关闭按钮颜色 |
-| closeSize | double? | - | 关闭按钮图标尺寸 |
-| closeUnderBottom | bool | false | 关闭按钮是否在视图框下方 |
-| key |  | - |  |
-| radius | double? | - | 圆角 |
+## API
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt b/tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt
deleted file mode 100644
index a4dc51338..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiAutoCloseFalse.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-
-  Widget _buildApiAutoCloseFalse(BuildContext context) {
-    return TButton(
-      text: 'autoCloseOnCancel/Confirm=false',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        title: '不自动关闭',
-        autoCloseOnCancel: false,
-        autoCloseOnConfirm: false,
-        onCancel: () =>
-            TToast.showText('已点取消', context: context),
-        onConfirm: () =>
-            TToast.showText('已点确定', context: context),
-        child: _body(context, height: 140),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt b/tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt
deleted file mode 100644
index b4c4ef6fc..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiBackgroundColor.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-
-  Widget _buildApiBackgroundColor(BuildContext context) {
-    return TButton(
-      text: 'backgroundColor + radius',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 260,
-        radius: 16,
-        backgroundColor: const Color(0xFFFFF8E7),
-        child: _body(context, height: 220),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt b/tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt
deleted file mode 100644
index 63f3b0906..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiCancelBtnConfirmBtn.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-
-  Widget _buildApiCancelBtnConfirmBtn(BuildContext context) {
-    return TButton(
-      text: 'cancelBtn / confirmBtn',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        title: '自定义按钮文案',
-        cancelBtn: '暂不',
-        confirmBtn: '好的',
-        onCancel: () => TPopup.close(context),
-        onConfirm: () => TPopup.close(context),
-        child: _body(context),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt b/tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt
deleted file mode 100644
index 7a330e0a7..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiCancelConfirmBuilders.txt
+++ /dev/null
@@ -1,30 +0,0 @@
-
-  Widget _buildApiCancelConfirmBuilders(BuildContext context) {
-    return TButton(
-      text: 'cancelBuilder / confirmBuilder',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        title: 'Builder 插槽',
-        cancelBuilder: (ctx) => TText(
-          '自定义取消',
-          textColor: TTheme.of(ctx).textColorSecondary,
-          font: TTheme.of(ctx).fontBodyLarge,
-        ),
-        confirmBuilder: (ctx) => TText(
-          '自定义确定',
-          textColor: TTheme.of(ctx).brandNormalColor,
-          font: TTheme.of(ctx).fontTitleMedium,
-          fontWeight: FontWeight.w600,
-        ),
-        onCancel: () => TPopup.close(context),
-        onConfirm: () => TPopup.close(context),
-        child: _body(context),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt b/tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt
deleted file mode 100644
index b13320e05..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiCancelConfirmWidgets.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-
-  Widget _buildApiCancelConfirmWidgets(BuildContext context) {
-    return TButton(
-      text: 'cancel / confirm Widget',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        title: 'Widget 插槽',
-        cancel: Icon(Icons.undo,
-            color: TTheme.of(context).textColorSecondary),
-        confirm:
-            Icon(Icons.check, color: TTheme.of(context).brandNormalColor),
-        onCancel: () => TPopup.close(context),
-        onConfirm: () => TPopup.close(context),
-        child: _body(context),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt b/tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt
deleted file mode 100644
index 444de7c0b..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiCloseBuilder.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-
-  Widget _buildApiCloseBuilder(BuildContext context) {
-    return TButton(
-      text: 'closeBuilder',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.center,
-        width: 260,
-        height: 160,
-        closeBuilder: (ctx) => TextButton(
-          onPressed: () => TPopup.close(ctx),
-          child: TText(
-            '关闭',
-            textColor: TTheme.of(ctx).fontWhColor1,
-            font: TTheme.of(ctx).fontBodyLarge,
-          ),
-        ),
-        child: _body(context, height: 120),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt b/tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt
deleted file mode 100644
index 733170092..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiCloseOverlayClickFalse.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-
-  Widget _buildApiCloseOverlayClickFalse(BuildContext context) {
-    return TButton(
-      text: 'closeOnOverlayClick: false',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 260,
-        closeOnOverlayClick: false,
-        child: _body(context),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt b/tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt
deleted file mode 100644
index 4b1887c92..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiHeaderBuilder.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-
-  Widget _buildApiHeaderBuilder(BuildContext context) {
-    return TButton(
-      text: 'headerBuilder(非 closeBtn)',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        headerBuilder: (ctx) => Container(
-          height: _headerHeight,
-          alignment: Alignment.center,
-          color: TTheme.of(ctx).brandColor1,
-          child: TText(
-            '自定义头部',
-            textColor: TTheme.of(ctx).brandNormalColor,
-            font: TTheme.of(ctx).fontTitleMedium,
-          ),
-        ),
-        child: _body(context, height: 200),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiOverlay.txt b/tdesign-component/example/assets/code/popup._buildApiOverlay.txt
deleted file mode 100644
index fa638be58..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiOverlay.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-
-  Widget _buildApiOverlay(BuildContext context) {
-    return TButton(
-      text: 'overlayColor + overlayOpacity',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 260,
-        overlayColor: TTheme.of(context).brandNormalColor,
-        overlayOpacity: 0.35,
-        child: _body(context),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt b/tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt
deleted file mode 100644
index 27a65a384..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiTitleAlignLeft.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-
-  Widget _buildApiTitleAlignLeft(BuildContext context) {
-    return TButton(
-      text: 'titleAlignLeft',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        title: '左对齐标题',
-        titleAlignLeft: true,
-        onCancel: () => TPopup.close(context),
-        onConfirm: () => TPopup.close(context),
-        child: _body(context),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt b/tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt
deleted file mode 100644
index bdbc8078b..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiTitleWidget.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-
-  Widget _buildApiTitleWidget(BuildContext context) {
-    return TButton(
-      text: 'titleWidget',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        titleWidget: Row(
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: [
-            Icon(TIcons.info_circle,
-                color: TTheme.of(context).brandNormalColor),
-            const SizedBox(width: 4),
-            TText(
-              '自定义标题',
-              textColor: TTheme.of(context).brandNormalColor,
-              font: TTheme.of(context).fontTitleMedium,
-            ),
-          ],
-        ),
-        onCancel: () => TPopup.close(context),
-        onConfirm: () => TPopup.close(context),
-        child: _body(context),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildBottomActionBar.txt b/tdesign-component/example/assets/code/popup._buildBottomActionBar.txt
deleted file mode 100644
index 11eda7be2..000000000
--- a/tdesign-component/example/assets/code/popup._buildBottomActionBar.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-
-  Widget _buildBottomActionBar(BuildContext context) {
-    return TButton(
-      text: 'bottom 操作栏(title + onCancel/onConfirm)',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        title: '标题文字',
-        onCancel: () => TPopup.close(context),
-        onConfirm: () {
-          TToast.showText('确定', context: context);
-          TPopup.close(context);
-        },
-        child: _body(context, height: 200),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt b/tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt
deleted file mode 100644
index e48a77791..000000000
--- a/tdesign-component/example/assets/code/popup._buildBottomTitleOnly.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-
-  Widget _buildBottomTitleOnly(BuildContext context) {
-    return TButton(
-      text: 'bottom 仅标题(无操作按钮)',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.bottom,
-        height: 280,
-        title: '标题文字',
-        child: _body(context, height: 200),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildCenterClose.txt b/tdesign-component/example/assets/code/popup._buildCenterClose.txt
deleted file mode 100644
index c9045eef5..000000000
--- a/tdesign-component/example/assets/code/popup._buildCenterClose.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-
-  Widget _buildCenterClose(BuildContext context) {
-    return TButton(
-      text: 'center close + onCloseBtn',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () => TPopup.show(
-        context: context,
-        placement: TPopupPlacement.center,
-        width: 240,
-        height: 180,
-        close: IconButton(
-          icon: Icon(
-            TIcons.close_circle,
-            color: TTheme.of(context).fontWhColor1,
-            size: 32,
-          ),
-          onPressed: () => TPopup.close(context),
-        ),
-        onCloseBtn: () =>
-            TToast.showText('onCloseBtn', context: context),
-        child: _body(context, height: 160),
-      ),
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt
deleted file mode 100644
index bc044696a..000000000
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCustomAction.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-
-  Widget _buildPopFromBottomWithCustomAction(BuildContext context) {
-    return TButton(
-      text: '底部弹出层-操作栏全自定义',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () {
-        TPopup.show(
-          context: context,
-          placement: TPopupPlacement.bottom,
-          height: 280,
-          titleWidget: Row(
-            mainAxisSize: MainAxisSize.min,
-            children: [
-              Icon(TIcons.info_circle,
-                  color: TTheme.of(context).brandNormalColor, size: 18),
-              const SizedBox(width: 4),
-              TText(
-                '自定义标题',
-                textColor: TTheme.of(context).brandNormalColor,
-                font: TTheme.of(context).fontTitleMedium,
-              ),
-            ],
-          ),
-          cancel: Icon(Icons.undo,
-              color: TTheme.of(context).textColorSecondary),
-          confirm: Icon(Icons.check,
-              color: TTheme.of(context).brandNormalColor),
-          onCancel: () => TPopup.close(context),
-          onConfirm: () => TPopup.close(context),
-          child: Container(height: 200),
-        );
-      },
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt
deleted file mode 100644
index 68c8cf39c..000000000
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperation.txt
+++ /dev/null
@@ -1,24 +0,0 @@
-
-  Widget _buildPopFromBottomWithOperation(BuildContext context) {
-    return TButton(
-      text: '底部弹出层-带操作',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () {
-        TPopup.show(
-          context: context,
-          placement: TPopupPlacement.bottom,
-          height: 280,
-          overlayColor: TTheme.of(context).fontGyColor2,
-          onCancel: () => TPopup.close(context),
-          onConfirm: () {
-            TToast.showText('确定', context: context);
-            TPopup.close(context);
-          },
-          child: Container(height: 200),
-        );
-      },
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt
deleted file mode 100644
index bce3f8e95..000000000
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithTitle.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-
-  Widget _buildPopFromBottomWithTitle(BuildContext context) {
-    return TButton(
-      text: '底部弹出层-仅标题',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () {
-        TPopup.show(
-          context: context,
-          placement: TPopupPlacement.bottom,
-          height: 280,
-          title: '标题文字',
-          child: Container(height: 200),
-        );
-      },
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-site/src/indexes/README.md b/tdesign-site/src/indexes/README.md
index 991028b4b..2f5287d96 100644
--- a/tdesign-site/src/indexes/README.md
+++ b/tdesign-site/src/indexes/README.md
@@ -36,25 +36,19 @@ Widget _buildSimple(BuildContext context) {
     theme: TButtonTheme.primary,
     type: TButtonType.outline,
     onTap: () {
-      Navigator.of(context).push(
-        TSlidePopupRoute(
-          slideTransitionFrom: SlideTransitionFrom.right,
-          modalTop: renderBox?.size.height,
-          builder: (context) {
-            return TIndexes(
-              indexList: indexList,
-              builderContent: (context, index) {
-                final list = _list.firstWhere(
-                        (element) => element['index'] == index)['children']
-                    as List;
-                return TCellGroup(
-                  cells: list
-                      .map((e) => TCell(
-                            title: e,
-                          ))
-                      .toList(),
-                );
-              },
+      TPopup.show(
+        context: context,
+        placement: TPopupPlacement.right,
+        width: 280,
+        margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+        child: TIndexes(
+          indexList: indexList,
+          builderContent: (context, index) {
+            final list = _list
+                .firstWhere((element) => element['index'] == index)['children']
+                as List;
+            return TCellGroup(
+              cells: list.map((e) => TCell(title: e)).toList(),
             );
           },
         ),
@@ -80,25 +74,19 @@ Widget _buildSimple(BuildContext context) {
     theme: TButtonTheme.primary,
     type: TButtonType.outline,
     onTap: () {
-      Navigator.of(context).push(
-        TSlidePopupRoute(
-          slideTransitionFrom: SlideTransitionFrom.right,
-          modalTop: renderBox?.size.height,
-          builder: (context) {
-            return TIndexes(
-              indexList: indexList,
-              builderContent: (context, index) {
-                final list = _list.firstWhere(
-                        (element) => element['index'] == index)['children']
-                    as List;
-                return TCellGroup(
-                  cells: list
-                      .map((e) => TCell(
-                            title: e,
-                          ))
-                      .toList(),
-                );
-              },
+      TPopup.show(
+        context: context,
+        placement: TPopupPlacement.right,
+        width: 280,
+        margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+        child: TIndexes(
+          indexList: indexList,
+          builderContent: (context, index) {
+            final list = _list
+                .firstWhere((element) => element['index'] == index)['children']
+                as List;
+            return TCellGroup(
+              cells: list.map((e) => TCell(title: e)).toList(),
             );
           },
         ),
@@ -127,26 +115,20 @@ Widget _buildOther(BuildContext context) {
     theme: TButtonTheme.primary,
     type: TButtonType.outline,
     onTap: () {
-      Navigator.of(context).push(
-        TSlidePopupRoute(
-          slideTransitionFrom: SlideTransitionFrom.right,
-          modalTop: renderBox?.size.height,
-          builder: (context) {
-            return TIndexes(
-              indexList: indexList,
-              capsuleTheme: true,
-              builderContent: (context, index) {
-                final list = _list.firstWhere(
-                        (element) => element['index'] == index)['children']
-                    as List;
-                return TCellGroup(
-                  cells: list
-                      .map((e) => TCell(
-                            title: e,
-                          ))
-                      .toList(),
-                );
-              },
+      TPopup.show(
+        context: context,
+        placement: TPopupPlacement.right,
+        width: 280,
+        margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+        child: TIndexes(
+          indexList: indexList,
+          capsuleTheme: true,
+          builderContent: (context, index) {
+            final list = _list
+                .firstWhere((element) => element['index'] == index)['children']
+                as List;
+            return TCellGroup(
+              cells: list.map((e) => TCell(title: e)).toList(),
             );
           },
         ),
@@ -172,26 +154,20 @@ Widget _buildOther(BuildContext context) {
     theme: TButtonTheme.primary,
     type: TButtonType.outline,
     onTap: () {
-      Navigator.of(context).push(
-        TSlidePopupRoute(
-          slideTransitionFrom: SlideTransitionFrom.right,
-          modalTop: renderBox?.size.height,
-          builder: (context) {
-            return TIndexes(
-              indexList: indexList,
-              capsuleTheme: true,
-              builderContent: (context, index) {
-                final list = _list.firstWhere(
-                        (element) => element['index'] == index)['children']
-                    as List;
-                return TCellGroup(
-                  cells: list
-                      .map((e) => TCell(
-                            title: e,
-                          ))
-                      .toList(),
-                );
-              },
+      TPopup.show(
+        context: context,
+        placement: TPopupPlacement.right,
+        width: 280,
+        margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+        child: TIndexes(
+          indexList: indexList,
+          capsuleTheme: true,
+          builderContent: (context, index) {
+            final list = _list
+                .firstWhere((element) => element['index'] == index)['children']
+                as List;
+            return TCellGroup(
+              cells: list.map((e) => TCell(title: e)).toList(),
             );
           },
         ),
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index a9a6d3cd1..15a631a13 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -33,21 +33,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.top,
-              open: () {
-                print('open');
-              },
-              opened: () {
-                print('opened');
-              },
-              builder: (context) {
-                return Container(
-                  color: TTheme.of(context).bgColorContainer,
-                  height: 240,
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.top,
+          height: 240,
+          onOpen: () => print('open'),
+          onOpened: () => print('opened'),
+          child: Container(
+            color: TTheme.of(context).bgColorContainer,
+            height: 240,
+          ),
         );
       },
     );
@@ -69,15 +64,13 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.left,
-              builder: (context) {
-                return Container(
-                  color: TTheme.of(context).bgColorContainer,
-                  width: 280,
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.left,
+          width: 280,
+          child: Container(
+            color: TTheme.of(context).bgColorContainer,
+          ),
         );
       },
     );
@@ -99,20 +92,19 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.center,
-              builder: (context) {
-                return Container(
-                  decoration: BoxDecoration(
-                    color: TTheme.of(context).bgColorContainer,
-                    borderRadius:
-                        BorderRadius.circular(TTheme.of(context).radiusLarge),
-                  ),
-                  width: 240,
-                  height: 240,
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.center,
+          closeBtn: false,
+          child: Container(
+            decoration: BoxDecoration(
+              color: TTheme.of(context).bgColorContainer,
+              borderRadius:
+                  BorderRadius.circular(TTheme.of(context).radiusLarge),
+            ),
+            width: 240,
+            height: 240,
+          ),
         );
       },
     );
@@ -134,15 +126,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.bottom,
-              builder: (context) {
-                return Container(
-                  color: TTheme.of(context).bgColorContainer,
-                  height: 240,
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 240,
+          child: Container(
+            color: TTheme.of(context).bgColorContainer,
+            height: 240,
+          ),
         );
       },
     );
@@ -164,15 +155,13 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.right,
-              builder: (context) {
-                return Container(
-                  color: TTheme.of(context).bgColorContainer,
-                  width: 280,
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.right,
+          width: 280,
+          child: Container(
+            color: TTheme.of(context).bgColorContainer,
+          ),
         );
       },
     );
@@ -195,23 +184,17 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-            slideTransitionFrom: SlideTransitionFrom.bottom,
-            builder: (context) {
-              return TPopupBottomConfirmPanel(
-                title: '标题文字',
-                leftClick: () {
-                  Navigator.maybePop(context);
-                },
-                rightClick: () {
-                  TToast.showText('确定', context: context);
-                  Navigator.maybePop(context);
-                },
-                child: Container(height: 200),
-              );
-            },
-          ),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 280,
+          title: '标题文字',
+          onCancel: () => TPopup.close(context),
+          onConfirm: () {
+            TToast.showText('确定', context: context);
+            TPopup.close(context);
+          },
+          child: Container(height: 200),
         );
       },
     );
@@ -225,31 +208,46 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
   
-  Widget _buildPopFromBottomWithOperation(BuildContext context) {
+  Widget _buildPopFromBottomWithCloseAndTitle(BuildContext context) {
     return TButton(
-      text: '底部弹出层-带操作',
+      text: '底部弹出层-带标题及关闭',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(TSlidePopupRoute(
-            modalBarrierColor: TTheme.of(context).fontGyColor2,
-            slideTransitionFrom: SlideTransitionFrom.bottom,
-            builder: (context) {
-              return TPopupBottomConfirmPanel(
-                leftClick: () {
-                  Navigator.maybePop(context);
-                },
-                rightClick: () {
-                  TToast.showText('确定', context: context);
-                  Navigator.maybePop(context);
-                },
-                child: Container(
-                  height: 200,
-                ),
-              );
-            }));
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 280,
+          cancel: TText(
+            '关闭',
+            textColor: TTheme.of(context).textColorSecondary,
+            font: TTheme.of(context).fontBodyLarge,
+          ),
+          titleWidget: Row(
+            mainAxisSize: MainAxisSize.min,
+            children: [
+              Icon(TIcons.info_circle,
+                  color: TTheme.of(context).brandNormalColor, size: 18),
+              const SizedBox(width: 4),
+              TText(
+                '自定义标题',
+                textColor: TTheme.of(context).brandNormalColor,
+                font: TTheme.of(context).fontTitleMedium,
+              ),
+            ],
+          ),
+          confirm: TText(
+            '完成',
+            textColor: TTheme.of(context).brandNormalColor,
+            font: TTheme.of(context).fontTitleMedium,
+            fontWeight: FontWeight.w600,
+          ),
+          onCancel: () => TPopup.close(context),
+          onConfirm: () => TPopup.close(context),
+          child: Container(height: 200),
+        );
       },
     );
   }
@@ -262,26 +260,29 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
-  Widget _buildPopFromBottomWithCloseAndTitle(BuildContext context) {
+  Widget _buildPopFromCenterWithClose(BuildContext context) {
     return TButton(
-      text: '底部弹出层-带标题及关闭',
+      text: '居中弹出层-带关闭',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.bottom,
-              builder: (context) {
-                return TPopupBottomDisplayPanel(
-                  title: '标题文字',
-                  closeClick: () {
-                    Navigator.maybePop(context);
-                  },
-                  child: Container(height: 200),
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.center,
+          closeOnOverlayClick: false,
+          width: 240,
+          height: 240,
+          close: IconButton(
+            icon: Icon(
+              TIcons.close_circle,
+              color: TTheme.of(context).fontWhColor1,
+              size: 32,
+            ),
+            onPressed: () => TPopup.close(context),
+          ),
+          child: const SizedBox(width: 240, height: 240),
         );
       },
     );
@@ -295,27 +296,33 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
   
-  Widget _buildPopFromBottomWithCloseAndLeftTitle(BuildContext context) {
+  Widget _buildPopFromCenterWithUnderClose(BuildContext context) {
     return TButton(
-      text: '底部弹出层-带左边标题及关闭',
+      text: '居中弹出层-自定义下方按钮',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.bottom,
-              builder: (context) {
-                return TPopupBottomDisplayPanel(
-                  title: '标题文字',
-                  titleLeft: true,
-                  closeClick: () {
-                    Navigator.maybePop(context);
-                  },
-                  child: Container(height: 200),
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.center,
+          closeOnOverlayClick: true,
+          width: 240,
+          height: 200,
+          close: IconButton(
+            icon: Icon(
+              TIcons.poweroff,
+              color: TTheme.of(context).fontWhColor1,
+              size: 36,
+            ),
+            onPressed: () => TPopup.close(context),
+          ),
+          child: Container(
+            width: 240,
+            height: 200,
+            color: TTheme.of(context).bgColorContainer,
+          ),
         );
       },
     );
@@ -323,31 +330,33 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
                                   
+### 1 更多 API
 
 
             
 
 
   
-  Widget _buildPopFromBottomWithClose(BuildContext context) {
+  Widget _buildApiMarginTop(BuildContext context) {
     return TButton(
-      text: '底部弹出层-带关闭',
+      text: 'bottom margin.top',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.bottom,
-              builder: (context) {
-                return TPopupBottomDisplayPanel(
-                  closeClick: () {
-                    Navigator.maybePop(context);
-                  },
-                  child: Container(height: 200),
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 320,
+          margin: const EdgeInsets.only(top: 120, left: 16, right: 16),
+          title: '日历式留白',
+          onCancel: () => TPopup.close(context),
+          onConfirm: () => TPopup.close(context),
+          child: Container(
+            height: 240,
+            color: TTheme.of(context).bgColorContainer,
+          ),
         );
       },
     );
@@ -361,27 +370,27 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
   
-  Widget _buildPopFromBottomWithTitle(BuildContext context) {
+  Widget _buildApiShowOverlayFalse(BuildContext context) {
     return TButton(
-      text: '底部弹出层-仅标题',
+      text: 'showOverlay: false(无蒙层)',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              slideTransitionFrom: SlideTransitionFrom.bottom,
-              builder: (context) {
-                return TPopupBottomDisplayPanel(
-                  title: '标题文字',
-                  hideClose: true,
-                  // closeClick: () {
-                  //   Navigator.maybePop(context);
-                  // },
-                  child: Container(height: 200),
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 280,
+          showOverlay: false,
+          // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口)
+          title: '无蒙层',
+          onCancel: () => TPopup.close(context),
+          onConfirm: () => TPopup.close(context),
+          child: Container(
+            height: 200,
+            color: TTheme.of(context).bgColorContainer,
+          ),
         );
       },
     );
@@ -395,26 +404,24 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
   
-  Widget _buildPopFromCenterWithClose(BuildContext context) {
+  Widget _buildApiOnOverlayClick(BuildContext context) {
     return TButton(
-      text: '居中弹出层-带关闭',
+      text: 'onOverlayClick',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              isDismissible: false,
-              slideTransitionFrom: SlideTransitionFrom.center,
-              builder: (context) {
-                return TPopupCenterPanel(
-                  closeClick: () {
-                    Navigator.maybePop(context);
-                  },
-                  child: const SizedBox(width: 240, height: 240),
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 260,
+          onOverlayClick: () =>
+              TToast.showText('点击蒙层', context: context),
+          child: Container(
+            height: 200,
+            color: TTheme.of(context).bgColorContainer,
+          ),
         );
       },
     );
@@ -428,27 +435,23 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
   
-  Widget _buildPopFromCenterWithUnderClose(BuildContext context) {
+  Widget _buildApiDuration(BuildContext context) {
     return TButton(
-      text: '居中弹出层-自定义下方按钮',
+      text: 'duration: 600ms',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        Navigator.of(context).push(
-          TSlidePopupRoute(
-              isDismissible: false,
-              slideTransitionFrom: SlideTransitionFrom.center,
-              builder: (context) {
-                return TPopupCenterPanel(
-                  closeUnderBottom: true,
-                  closeClick: () {
-                    Navigator.maybePop(context);
-                  },
-                  child: const SizedBox(width: 240, height: 240),
-                );
-              }),
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 240,
+          duration: const Duration(milliseconds: 600),
+          child: Container(
+            height: 200,
+            color: TTheme.of(context).bgColorContainer,
+          ),
         );
       },
     );
@@ -459,102 +462,5 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
 ## API
-### TSlidePopupRoute
-#### 简介
-从屏幕的某个方向滑动弹出的Dialog框的路由,比如从顶部、底部、左、右滑出页面
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| barrierClick | VoidCallback? | - | 蒙层点击事件,仅在[modalBarrierFull]为false时触发 |
-| barrierLabel |  | - |  |
-| builder | WidgetBuilder | - | 控件构建器 |
-| close | VoidCallback? | - | 关闭前事件 |
-| focusMove | bool | false | 是否有输入框获取焦点时整体平移避免输入框被遮挡 |
-| isDismissible | bool | true | 点击蒙层能否关闭 |
-| modalBarrierColor | Color? | Colors.black54 | 蒙层颜色 |
-| modalBarrierFull | bool | false | 是否全屏显示蒙层 |
-| modalHeight | double? | - | 弹出框高度 |
-| modalLeft | double? | 0 | 弹出框左侧距离 |
-| modalTop | double? | 0 | 弹出框顶部距离 |
-| modalWidth | double? | - | 弹出框宽度 |
-| open | VoidCallback? | - | 打开前事件 |
-| opened | VoidCallback? | - | 打开后事件 |
-| slideTransitionFrom | SlideTransitionFrom | SlideTransitionFrom.bottom | 设置从屏幕的哪个方向滑出 |
-
-```
-```
-
-### TPopupBottomDisplayPanel
-#### 简介
-右上角带关闭的底部浮层面板
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| backgroundColor |  | - |  |
-| child |  | - |  |
-| closeClick | PopupClick? | - | 关闭按钮点击回调 |
-| closeColor | Color? | - | 关闭按钮颜色 |
-| closeSize | double? | - | 关闭按钮图标尺寸 |
-| draggable |  | - |  |
-| hideClose | bool | false | 是否隐藏关闭按钮 |
-| key |  | - |  |
-| maxHeightRatio |  | - |  |
-| minHeightRatio |  | - |  |
-| radius |  | - |  |
-| title |  | - |  |
-| titleColor |  | - |  |
-| titleFontSize | double? | - | 标题字体大小 |
-| titleLeft | bool | false | 标题是否靠左 |
-
-```
-```
-
-### TPopupBottomConfirmPanel
-#### 简介
-带确认的底部浮层面板
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| backgroundColor |  | - |  |
-| child |  | - |  |
-| draggable |  | - |  |
-| key |  | - |  |
-| leftClick | PopupClick? | - | 左边文本点击回调 |
-| leftText | String? | - | 左边文本 |
-| leftTextColor | Color? | - | 左边文本颜色 |
-| leftTextFontSize | double? | - | 左边文本字体大小 |
-| maxHeightRatio |  | - |  |
-| minHeightRatio |  | - |  |
-| radius |  | - |  |
-| rightClick | PopupClick? | - | 右边文本点击回调 |
-| rightText | String? | - | 右边文本 |
-| rightTextColor | Color? | - | 右边文本颜色 |
-| rightTextFontSize | double? | - | 右边文本字体大小 |
-| title |  | - |  |
-| titleColor |  | - |  |
-| titleFontSize | double? | - | 标题字体大小 |
-
-```
-```
-
-### TPopupCenterPanel
-#### 简介
-居中浮层面板
-#### 默认构造方法
-
-| 参数 | 类型 | 默认值 | 说明 |
-| --- | --- | --- | --- |
-| backgroundColor | Color? | - | 背景颜色 |
-| child | Widget | - | 子控件 |
-| closeClick | PopupClick? | - | 关闭按钮点击回调 |
-| closeColor | Color? | - | 关闭按钮颜色 |
-| closeSize | double? | - | 关闭按钮图标尺寸 |
-| closeUnderBottom | bool | false | 关闭按钮是否在视图框下方 |
-| key |  | - |  |
-| radius | double? | - | 圆角 |
-
 
   
\ No newline at end of file

From f183a4c0cd6b3b49aadc357ef364e260416ba4be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Fri, 22 May 2026 03:40:54 +0800
Subject: [PATCH 03/27] feat(popup): enhance TPopup functionality and
 documentation

---
 tdesign-component/coverage/lcov.info          | 1320 +++++++++--------
 tdesign-component/demo_tool/all_build.sh      |    2 +-
 tdesign-component/doc/popup_api.md            |  300 ++++
 .../example/assets/api/popup_api.md           |   62 +-
 .../assets/code/popup._buildPopFromCenter.txt |    2 +-
 .../lib/component_test/popup_test.dart        |    1 -
 .../example/lib/page/t_popup_page.dart        |  109 +-
 .../components/popup/_popup_center_close.dart |   77 +
 .../src/components/popup/_popup_header.dart   |  148 +-
 .../src/components/popup/_popup_route.dart    |   11 +-
 .../src/components/popup/_popup_shell.dart    |   20 +-
 .../lib/src/components/popup/t_popup.dart     |  173 ++-
 .../src/components/popup/t_popup_config.dart  |   71 +-
 .../src/components/popup/t_popup_handle.dart  |   38 +
 .../src/components/popup/t_popup_tracker.dart |   10 +-
 .../src/components/popup/t_popup_types.dart   |   94 +-
 .../test/t_popup_config_test.dart             |   63 +-
 .../test/t_popup_coverage_test.dart           |  355 ++++-
 .../test/t_popup_route_test.dart              |  128 ++
 tdesign-component/test/t_popup_test.dart      |  142 +-
 20 files changed, 2258 insertions(+), 868 deletions(-)
 create mode 100644 tdesign-component/doc/popup_api.md
 create mode 100644 tdesign-component/lib/src/components/popup/_popup_center_close.dart
 create mode 100644 tdesign-component/lib/src/components/popup/t_popup_handle.dart
 create mode 100644 tdesign-component/test/t_popup_route_test.dart

diff --git a/tdesign-component/coverage/lcov.info b/tdesign-component/coverage/lcov.info
index ca2c15ea0..7c52f5e7e 100644
--- a/tdesign-component/coverage/lcov.info
+++ b/tdesign-component/coverage/lcov.info
@@ -1,48 +1,62 @@
 SF:lib/src/components/popup/t_popup_config.dart
-DA:7,3
-DA:50,3
-DA:91,3
-DA:92,3
-DA:97,3
-DA:185,3
-DA:186,6
-DA:187,6
-DA:190,3
-DA:191,6
-DA:192,6
-DA:195,3
-DA:196,6
-DA:197,3
-DA:198,5
-DA:200,4
-DA:202,2
-DA:203,4
-DA:204,2
-DA:205,2
-DA:206,4
-DA:207,1
-DA:209,3
-DA:210,6
-DA:211,3
-DA:212,3
-DA:213,2
-DA:214,2
-DA:215,2
-DA:216,2
+DA:7,4
+DA:47,4
+DA:85,4
+DA:86,4
+DA:89,4
+DA:175,4
+DA:176,8
+DA:177,8
+DA:180,4
+DA:181,8
+DA:182,8
+DA:185,4
+DA:186,12
+DA:189,4
+DA:190,8
+DA:191,8
+DA:192,8
+DA:195,4
+DA:196,8
+DA:197,4
+DA:198,8
+DA:200,6
+DA:203,2
+DA:204,4
+DA:205,4
+DA:206,2
+DA:207,2
+DA:208,7
+DA:210,2
+DA:211,4
+DA:212,2
+DA:213,6
+DA:215,4
+DA:216,8
+DA:217,4
+DA:218,4
+DA:219,4
 DA:220,2
-DA:221,6
-DA:222,4
-DA:223,4
-DA:227,2
-DA:228,2
-DA:229,2
-DA:230,2
-DA:231,2
-DA:236,9
-DA:237,0
-DA:242,3
-LF:42
-LH:41
+DA:221,2
+DA:222,2
+DA:226,4
+DA:227,5
+DA:228,4
+DA:229,4
+DA:233,3
+DA:234,3
+DA:235,3
+DA:236,2
+DA:237,2
+DA:242,8
+DA:243,3
+DA:244,3
+DA:245,3
+DA:246,3
+DA:247,2
+DA:252,4
+LF:56
+LH:56
 end_of_record
 SF:lib/src/components/action_sheet/t_action_sheet.dart
 DA:20,0
@@ -93,97 +107,119 @@ LF:44
 LH:0
 end_of_record
 SF:lib/src/util/context_extension.dart
-DA:7,6
+DA:7,9
 LF:1
 LH:1
 end_of_record
 SF:lib/src/components/popup/t_popup.dart
-DA:12,1
-DA:112,2
-DA:156,2
-DA:197,2
-DA:200,2
-DA:208,2
-DA:209,2
-DA:212,2
-DA:213,2
-DA:214,2
-DA:217,2
-DA:222,2
-DA:228,2
-DA:230,6
-DA:231,2
-DA:232,2
-DA:233,2
-DA:240,2
-DA:241,2
-DA:242,2
-DA:243,4
-DA:244,2
-DA:247,1
-DA:250,1
-DA:251,1
-DA:257,1
-DA:259,1
-DA:260,2
-DA:261,4
-DA:265,1
-DA:267,2
-DA:268,1
-DA:271,1
-DA:272,2
-DA:275,2
-DA:276,1
-DA:277,3
-DA:278,2
-DA:279,2
-DA:280,2
-DA:281,2
-DA:282,2
-DA:283,2
-DA:284,2
-DA:285,2
-DA:286,2
-DA:287,2
-DA:288,2
-DA:289,2
-DA:290,2
-DA:291,2
-DA:292,2
-DA:293,2
-DA:294,2
-DA:295,2
-DA:296,2
-DA:297,2
-DA:298,2
-DA:299,2
-DA:300,2
-DA:301,2
-DA:302,2
-DA:303,2
-DA:304,2
-DA:305,2
-DA:306,2
-DA:307,2
-DA:308,2
-DA:309,2
-DA:310,2
-DA:311,2
-DA:312,2
-DA:313,2
-DA:314,2
-DA:315,2
-DA:316,2
-DA:317,2
-DA:321,1
-DA:323,2
-DA:329,2
-DA:342,6
-DA:344,2
-DA:345,2
-DA:348,4
-LF:84
-LH:84
+DA:19,1
+DA:187,3
+DA:228,3
+DA:266,3
+DA:269,3
+DA:274,3
+DA:276,2
+DA:277,4
+DA:284,3
+DA:285,3
+DA:288,3
+DA:289,3
+DA:290,3
+DA:293,3
+DA:298,3
+DA:303,3
+DA:305,9
+DA:306,3
+DA:307,3
+DA:316,3
+DA:317,3
+DA:318,3
+DA:319,6
+DA:320,3
+DA:324,1
+DA:325,1
+DA:331,1
+DA:333,1
+DA:334,2
+DA:335,4
+DA:339,1
+DA:341,2
+DA:342,1
+DA:345,1
+DA:346,2
+DA:349,2
+DA:350,1
+DA:351,3
+DA:352,2
+DA:353,2
+DA:354,2
+DA:355,2
+DA:356,2
+DA:357,2
+DA:358,2
+DA:359,2
+DA:360,2
+DA:361,2
+DA:362,2
+DA:363,2
+DA:364,2
+DA:365,2
+DA:366,2
+DA:367,2
+DA:368,2
+DA:369,2
+DA:370,2
+DA:371,2
+DA:372,2
+DA:373,2
+DA:374,2
+DA:375,2
+DA:376,2
+DA:377,2
+DA:378,2
+DA:379,2
+DA:380,2
+DA:381,2
+DA:382,2
+DA:383,2
+DA:384,2
+DA:385,2
+DA:386,2
+DA:387,2
+DA:388,2
+DA:392,1
+DA:394,2
+LF:77
+LH:77
+end_of_record
+SF:lib/src/components/popup/t_popup_tracker.dart
+DA:5,9
+DA:7,3
+DA:8,15
+DA:11,3
+DA:12,9
+DA:13,9
+DA:14,6
+DA:18,3
+DA:19,6
+DA:20,3
+DA:23,3
+LF:11
+LH:11
+end_of_record
+SF:lib/src/components/popup/t_popup_handle.dart
+DA:5,3
+DA:18,9
+DA:23,3
+DA:24,3
+DA:27,6
+DA:30,3
+DA:31,3
+DA:34,3
+DA:35,3
+DA:36,3
+LF:10
+LH:10
 end_of_record
 SF:lib/src/components/action_sheet/t_action_sheet_grid.dart
 DA:30,0
@@ -467,7 +503,7 @@ DA:48,0
 DA:51,0
 DA:54,0
 DA:57,0
-DA:60,6
+DA:60,9
 DA:63,0
 DA:68,0
 DA:71,0
@@ -541,7 +577,7 @@ DA:280,0
 DA:283,0
 DA:286,0
 DA:290,0
-DA:292,6
+DA:292,9
 DA:294,0
 DA:296,0
 DA:298,0
@@ -556,8 +592,8 @@ DA:312,0
 DA:314,0
 DA:316,0
 DA:318,0
-DA:322,6
-DA:324,6
+DA:322,9
+DA:324,9
 DA:326,0
 DA:328,0
 DA:330,0
@@ -573,11 +609,11 @@ DA:14,0
 DA:17,0
 DA:20,0
 DA:23,0
-DA:26,6
-DA:29,6
+DA:26,9
+DA:29,9
 DA:32,0
 DA:35,0
-DA:38,6
+DA:38,9
 DA:41,0
 DA:44,0
 DA:47,0
@@ -595,7 +631,7 @@ SF:lib/src/theme/t_radius.dart
 DA:8,0
 DA:9,0
 DA:10,0
-DA:11,6
+DA:11,9
 DA:14,0
 DA:17,0
 LF:6
@@ -603,8 +639,8 @@ LH:1
 end_of_record
 SF:lib/src/theme/t_spacers.dart
 DA:5,0
-DA:7,6
-DA:9,6
+DA:7,9
+DA:9,9
 DA:11,3
 DA:13,0
 DA:15,0
@@ -617,21 +653,21 @@ LF:11
 LH:3
 end_of_record
 SF:lib/src/theme/t_theme.dart
-DA:13,2
-DA:18,2
-DA:35,2
-DA:38,2
-DA:40,4
-DA:41,2
-DA:42,2
-DA:43,2
-DA:44,2
+DA:13,3
+DA:18,3
+DA:35,3
+DA:38,3
+DA:40,6
+DA:41,3
+DA:42,3
+DA:43,3
+DA:44,3
 DA:48,0
-DA:55,2
-DA:59,4
+DA:55,3
+DA:59,6
 DA:63,0
 DA:64,0
-DA:69,2
+DA:69,3
 DA:72,0
 DA:76,0
 DA:77,0
@@ -639,9 +675,9 @@ DA:79,0
 DA:80,0
 DA:86,0
 DA:89,0
-DA:136,2
-DA:149,2
-DA:152,2
+DA:136,3
+DA:149,3
+DA:152,3
 DA:158,0
 DA:159,0
 DA:167,0
@@ -679,64 +715,64 @@ DA:245,0
 DA:247,0
 DA:248,0
 DA:251,0
-DA:257,2
-DA:261,2
-DA:262,2
-DA:264,2
-DA:265,2
-DA:266,2
-DA:268,2
-DA:269,2
-DA:270,8
-DA:284,2
-DA:291,2
+DA:257,3
+DA:261,3
+DA:262,3
+DA:264,3
+DA:265,3
+DA:266,3
+DA:268,3
+DA:269,3
+DA:270,12
+DA:284,3
+DA:291,3
 DA:292,0
-DA:297,2
-DA:298,2
-DA:299,2
-DA:300,2
+DA:297,3
+DA:298,3
+DA:299,3
+DA:300,3
 DA:301,0
-DA:302,2
-DA:304,2
-DA:305,2
-DA:306,2
-DA:308,6
-DA:309,4
+DA:302,3
+DA:304,3
+DA:305,3
+DA:306,3
+DA:308,9
+DA:309,6
 DA:332,0
 DA:333,0
 DA:337,0
-DA:342,2
-DA:343,2
-DA:344,4
-DA:345,2
-DA:350,2
-DA:351,4
-DA:352,2
-DA:354,4
-DA:359,2
-DA:360,4
-DA:361,4
-DA:365,2
-DA:366,4
-DA:367,6
-DA:371,2
-DA:372,4
-DA:373,6
-DA:377,2
-DA:378,4
-DA:379,6
-DA:383,2
-DA:384,4
-DA:385,2
-DA:386,4
-DA:387,4
-DA:388,4
-DA:389,4
-DA:390,4
-DA:391,8
-DA:392,6
-DA:396,4
-DA:400,2
+DA:342,3
+DA:343,3
+DA:344,6
+DA:345,3
+DA:350,3
+DA:351,6
+DA:352,3
+DA:354,6
+DA:359,3
+DA:360,6
+DA:361,6
+DA:365,3
+DA:366,6
+DA:367,9
+DA:371,3
+DA:372,6
+DA:373,9
+DA:377,3
+DA:378,6
+DA:379,9
+DA:383,3
+DA:384,6
+DA:385,3
+DA:386,6
+DA:387,6
+DA:388,6
+DA:389,6
+DA:390,6
+DA:391,12
+DA:392,9
+DA:396,6
+DA:400,3
 DA:401,0
 DA:402,0
 DA:406,0
@@ -765,15 +801,15 @@ DA:456,0
 DA:457,0
 DA:458,0
 DA:459,0
-DA:474,2
-DA:477,4
-DA:481,2
-DA:484,4
-DA:485,2
-DA:489,6
-DA:490,2
-DA:496,2
-DA:497,2
+DA:474,3
+DA:477,6
+DA:481,3
+DA:484,6
+DA:485,3
+DA:489,9
+DA:490,3
+DA:496,3
+DA:497,3
 LF:157
 LH:77
 end_of_record
@@ -826,7 +862,7 @@ LF:14
 LH:0
 end_of_record
 SF:lib/src/components/badge/t_badge.dart
-DA:43,4
+DA:43,5
 DA:57,0
 DA:95,0
 DA:96,0
@@ -950,15 +986,15 @@ LF:120
 LH:1
 end_of_record
 SF:lib/src/components/text/t_text.dart
-DA:41,2
-DA:68,2
+DA:41,3
+DA:68,3
 DA:71,0
 DA:98,0
-DA:163,2
-DA:165,2
+DA:163,3
+DA:165,3
 DA:167,0
 DA:169,0
-DA:172,2
+DA:172,3
 DA:173,0
 DA:174,0
 DA:176,0
@@ -976,68 +1012,68 @@ DA:192,0
 DA:193,0
 DA:194,0
 DA:196,0
-DA:199,4
-DA:201,2
+DA:199,6
+DA:201,3
 DA:203,0
 DA:205,0
-DA:210,2
-DA:211,2
-DA:214,2
-DA:216,2
+DA:210,3
+DA:211,3
+DA:214,3
+DA:216,3
 DA:217,0
 DA:218,0
-DA:220,4
-DA:221,4
-DA:223,2
+DA:220,6
+DA:221,6
+DA:223,3
 DA:224,0
 DA:225,0
-DA:227,4
-DA:229,2
+DA:227,6
+DA:229,3
 DA:230,0
 DA:232,0
-DA:237,4
-DA:238,2
-DA:239,2
-DA:244,4
-DA:245,6
-DA:246,2
-DA:247,2
-DA:248,2
-DA:249,2
-DA:250,4
-DA:251,2
-DA:252,2
-DA:253,2
-DA:254,2
-DA:255,2
-DA:256,2
-DA:257,2
-DA:258,2
-DA:259,4
-DA:260,2
-DA:261,2
-DA:262,2
-DA:265,2
-DA:266,2
+DA:237,6
+DA:238,3
+DA:239,3
+DA:244,6
+DA:245,9
+DA:246,3
+DA:247,3
+DA:248,3
+DA:249,3
+DA:250,6
+DA:251,3
+DA:252,3
+DA:253,3
+DA:254,3
+DA:255,3
+DA:256,3
+DA:257,3
+DA:258,3
+DA:259,6
+DA:260,3
+DA:261,3
+DA:262,3
+DA:265,3
+DA:266,3
 DA:272,0
 DA:273,0
-DA:276,2
-DA:280,2
-DA:281,2
-DA:282,2
-DA:283,2
-DA:285,2
-DA:286,2
-DA:287,2
-DA:288,2
-DA:289,2
-DA:290,2
-DA:291,2
-DA:292,2
-DA:293,2
-DA:294,2
-DA:295,2
-DA:296,2
+DA:276,3
+DA:280,3
+DA:281,3
+DA:282,3
+DA:283,3
+DA:285,3
+DA:286,3
+DA:287,3
+DA:288,3
+DA:289,3
+DA:290,3
+DA:291,3
+DA:292,3
+DA:293,3
+DA:294,3
+DA:295,3
+DA:296,3
 DA:298,0
 DA:299,0
 DA:301,0
@@ -1973,7 +2009,7 @@ DA:114,0
 DA:115,0
 DA:116,0
 DA:117,0
-DA:133,4
+DA:133,5
 DA:150,0
 DA:153,0
 DA:154,0
@@ -3952,7 +3988,7 @@ LF:10
 LH:0
 end_of_record
 SF:lib/src/components/collapse/t_inset_divider.dart
-DA:9,4
+DA:9,5
 DA:11,0
 DA:13,0
 DA:15,0
@@ -4421,7 +4457,7 @@ LF:47
 LH:0
 end_of_record
 SF:lib/src/components/divider/t_divider.dart
-DA:12,24
+DA:12,30
 DA:26,0
 DA:64,0
 DA:67,0
@@ -4506,7 +4542,7 @@ LF:33
 LH:0
 end_of_record
 SF:lib/src/components/icon/t_icons.dart
-DA:8,4
+DA:8,5
 DA:9,0
 DA:22,0
 LF:3
@@ -6894,7 +6930,7 @@ DA:94,0
 DA:98,0
 DA:102,0
 DA:104,0
-DA:111,4
+DA:111,5
 DA:120,0
 DA:125,0
 DA:129,0
@@ -7955,7 +7991,7 @@ LF:47
 LH:0
 end_of_record
 SF:lib/src/components/loading/t_loading.dart
-DA:38,12
+DA:38,15
 DA:49,0
 DA:78,0
 DA:80,0
@@ -8461,8 +8497,8 @@ end_of_record
 SF:lib/src/util/platform_util.dart
 DA:6,0
 DA:7,0
-DA:10,2
-DA:11,2
+DA:10,3
+DA:11,3
 DA:14,0
 DA:15,0
 DA:18,0
@@ -8717,36 +8753,36 @@ LF:235
 LH:0
 end_of_record
 SF:lib/src/util/t_toolbar_pressable.dart
-DA:10,2
-DA:41,2
-DA:42,2
+DA:10,3
+DA:41,3
+DA:42,3
 DA:48,2
 DA:49,8
 DA:52,4
 DA:55,6
-DA:58,2
-DA:60,2
-DA:61,4
-DA:62,2
-DA:63,2
-DA:64,2
-DA:67,4
-DA:68,4
+DA:58,3
+DA:60,3
+DA:61,6
+DA:62,3
+DA:63,3
+DA:64,3
+DA:67,6
+DA:68,6
 DA:69,1
 DA:70,2
-DA:74,4
+DA:74,6
 DA:75,1
 DA:76,2
-DA:81,8
-DA:83,2
+DA:81,12
+DA:83,3
 DA:85,4
 DA:86,4
 DA:87,0
-DA:88,4
-DA:89,2
-DA:90,4
-DA:91,2
-DA:92,2
+DA:88,6
+DA:89,3
+DA:90,6
+DA:91,3
+DA:92,3
 LF:30
 LH:29
 end_of_record
@@ -8795,7 +8831,7 @@ LF:40
 LH:0
 end_of_record
 SF:lib/src/components/picker/t_picker_keys.dart
-DA:13,4
+DA:13,5
 DA:35,0
 DA:38,0
 DA:39,0
@@ -9095,130 +9131,187 @@ DA:545,0
 LF:231
 LH:0
 end_of_record
-SF:lib/src/components/popup/_popup_header.dart
-DA:12,2
-DA:23,2
-DA:25,6
-DA:29,4
-DA:30,3
-DA:33,4
-DA:34,2
-DA:36,2
-DA:37,2
-DA:38,2
-DA:43,2
-DA:44,5
-DA:45,1
+SF:lib/src/components/popup/_popup_center_close.dart
+DA:11,2
+DA:16,4
+DA:17,2
+DA:18,2
+DA:19,4
+DA:20,2
+DA:22,2
+DA:28,4
+DA:33,2
+DA:44,2
 DA:46,2
-DA:47,2
+DA:47,4
 DA:48,2
-DA:59,1
-DA:61,1
+DA:49,4
+DA:50,4
+DA:51,2
+DA:55,2
+DA:56,6
+DA:57,4
+DA:60,2
 DA:62,2
-DA:73,2
-DA:81,2
-DA:83,2
-DA:84,4
-DA:85,2
-DA:86,4
-DA:87,2
-DA:88,2
-DA:94,2
-DA:95,2
-DA:96,4
-DA:97,2
-DA:98,4
-DA:99,2
-DA:100,1
-DA:101,3
-DA:102,2
-DA:103,2
-DA:106,2
-DA:110,2
-DA:111,4
-DA:112,4
-DA:113,2
-DA:114,4
-DA:115,2
-DA:116,1
-DA:117,3
-DA:118,2
-DA:119,2
-DA:122,2
-DA:126,2
-DA:131,2
-DA:132,4
-DA:133,3
-DA:135,6
-DA:136,2
-DA:137,8
-DA:138,2
-DA:139,2
-DA:142,2
-DA:145,2
-DA:146,4
-DA:147,3
-DA:149,6
-DA:150,2
-DA:151,8
-DA:152,2
-DA:153,2
-DA:157,2
-DA:163,2
-DA:174,2
-DA:176,2
-DA:177,2
-DA:178,4
-DA:179,2
-DA:180,4
-DA:181,4
-DA:182,2
-DA:187,4
-DA:188,3
-DA:189,4
-DA:190,2
-DA:192,2
-DA:193,2
-DA:195,2
-DA:198,2
-DA:199,5
-DA:200,4
-DA:205,2
-DA:208,2
-LF:89
-LH:89
+DA:66,2
+DA:69,2
+LF:23
+LH:23
 end_of_record
 SF:lib/src/components/popup/t_popup_types.dart
-DA:11,4
-DA:13,1
-LF:2
-LH:2
+DA:26,5
+DA:28,1
+DA:37,3
+DA:68,1
+DA:73,4
+DA:74,4
+DA:86,1
+DA:91,3
+DA:92,3
+LF:9
+LH:9
 end_of_record
-SF:lib/src/components/popup/_popup_layout.dart
-DA:7,3
+SF:lib/src/components/popup/_popup_header.dart
+DA:15,3
 DA:26,3
-DA:27,6
-DA:30,3
-DA:33,3
-DA:34,3
-DA:35,3
-DA:36,3
+DA:28,15
+DA:32,6
+DA:33,6
+DA:35,2
+DA:39,6
+DA:40,3
+DA:42,3
+DA:43,3
+DA:44,3
+DA:49,3
+DA:54,2
+DA:56,2
+DA:57,4
+DA:66,2
+DA:67,2
+DA:68,2
+DA:69,2
+DA:70,4
+DA:71,2
+DA:73,4
+DA:74,2
+DA:76,4
+DA:77,4
+DA:81,3
+DA:82,6
+DA:83,2
+DA:85,12
+DA:86,2
+DA:87,4
+DA:88,4
+DA:89,4
+DA:98,2
+DA:99,4
+DA:100,3
+DA:102,6
+DA:103,1
+DA:104,4
+DA:105,1
+DA:106,1
+DA:109,2
+DA:112,2
+DA:113,4
+DA:114,3
+DA:116,6
+DA:117,1
+DA:118,4
+DA:119,1
+DA:120,1
+DA:124,2
+DA:129,3
+DA:137,3
+DA:139,3
+DA:140,6
+DA:141,3
+DA:142,6
+DA:143,3
+DA:144,3
+DA:150,3
+DA:151,3
+DA:152,6
+DA:153,3
+DA:154,6
+DA:155,3
+DA:157,6
+DA:159,3
+DA:160,2
+DA:161,5
+DA:162,4
+DA:163,4
+DA:166,3
+DA:171,2
+DA:172,6
+DA:173,6
+DA:174,3
+DA:175,6
+DA:176,3
+DA:178,6
+DA:180,3
+DA:181,1
+DA:182,3
+DA:183,2
+DA:184,2
+DA:187,3
+DA:192,2
+DA:197,3
+DA:198,6
+DA:199,3
+DA:201,9
+DA:202,3
+DA:203,12
+DA:204,3
+DA:205,3
+DA:208,4
+DA:211,3
+DA:212,6
+DA:213,3
+DA:215,9
+DA:216,3
+DA:217,12
+DA:218,3
+DA:219,3
+DA:223,4
+DA:227,3
+DA:228,3
+DA:229,1
+DA:232,6
+DA:235,3
+DA:236,3
+DA:237,1
+DA:240,6
+LF:112
+LH:112
+end_of_record
+SF:lib/src/components/popup/_popup_layout.dart
+DA:7,4
+DA:26,4
+DA:27,8
+DA:30,4
+DA:33,4
+DA:34,4
+DA:35,4
+DA:36,4
 DA:37,3
 DA:38,3
 DA:39,3
 DA:40,3
 DA:41,3
-DA:44,3
-DA:45,3
-DA:46,6
+DA:44,4
+DA:45,4
+DA:46,8
 DA:47,2
 DA:48,2
 DA:49,2
 DA:50,2
-DA:56,2
-DA:57,2
-DA:58,2
-DA:59,2
+DA:56,3
+DA:57,3
+DA:58,3
+DA:59,3
 DA:64,1
 DA:65,2
 DA:66,1
@@ -9231,20 +9324,20 @@ DA:74,3
 DA:75,3
 DA:76,3
 DA:79,3
-DA:80,2
-DA:81,2
-DA:82,2
-DA:83,2
-DA:84,2
+DA:80,3
+DA:81,3
+DA:82,3
+DA:83,3
+DA:84,3
 DA:87,3
 DA:88,3
 DA:89,3
 DA:90,3
 DA:91,6
 DA:92,6
-DA:100,3
-DA:101,3
-DA:102,3
+DA:100,4
+DA:101,4
+DA:102,4
 DA:104,6
 DA:105,12
 DA:110,2
@@ -9254,12 +9347,12 @@ DA:114,2
 DA:116,2
 DA:118,2
 DA:120,1
-DA:125,3
-DA:126,3
-DA:127,3
+DA:125,4
+DA:126,4
+DA:127,4
 DA:128,6
-DA:129,3
-DA:130,6
+DA:129,4
+DA:130,8
 DA:131,2
 DA:132,4
 DA:133,2
@@ -9269,170 +9362,163 @@ LF:70
 LH:70
 end_of_record
 SF:lib/src/components/popup/_popup_route.dart
-DA:12,2
-DA:15,2
-DA:16,2
-DA:18,2
-DA:19,2
-DA:20,2
-DA:33,2
-DA:34,4
-DA:37,4
-DA:38,4
+DA:10,3
+DA:13,3
+DA:14,3
+DA:16,3
+DA:17,3
+DA:18,3
+DA:32,3
+DA:33,6
+DA:36,6
+DA:37,6
+DA:38,3
 DA:39,3
-DA:40,3
-DA:45,2
-DA:46,4
-DA:48,2
-DA:51,2
-DA:54,2
-DA:57,2
-DA:63,2
-DA:66,2
-DA:67,4
-DA:70,2
-DA:71,2
-DA:74,2
-DA:75,5
-DA:76,5
-DA:79,0
-DA:88,2
-DA:95,2
-DA:101,2
-DA:102,4
-DA:103,4
-DA:104,2
-DA:105,4
-DA:106,4
-DA:107,4
-DA:109,10
-DA:112,2
-DA:113,2
-DA:114,2
-DA:115,2
-DA:119,6
-DA:120,2
-DA:126,2
-DA:127,4
-DA:132,4
-DA:134,2
-DA:136,2
-DA:138,2
-DA:139,6
-DA:140,6
-DA:141,1
-DA:142,2
-DA:147,2
-DA:148,2
-DA:150,2
-DA:151,2
-DA:152,4
-DA:153,6
-DA:157,4
-DA:158,2
-DA:163,2
-DA:164,2
-DA:165,0
-DA:170,1
-DA:171,3
-DA:172,2
-DA:173,2
-DA:177,2
-DA:178,4
-DA:179,2
-DA:180,5
-DA:182,4
-DA:183,2
-DA:184,5
-DA:188,2
-DA:189,2
-DA:192,2
-DA:196,2
-DA:197,4
-DA:198,4
-DA:199,2
-DA:203,2
-DA:205,5
-DA:206,5
-DA:207,2
-DA:208,4
-DA:212,2
-DA:214,2
-DA:215,2
-DA:218,2
-DA:220,2
-DA:221,6
-DA:223,2
-LF:94
-LH:92
+DA:44,3
+DA:45,6
+DA:47,3
+DA:48,6
+DA:50,3
+DA:53,3
+DA:55,9
+DA:57,3
+DA:63,3
+DA:66,3
+DA:67,6
+DA:70,3
+DA:71,3
+DA:74,3
+DA:75,8
+DA:76,8
+DA:79,1
+DA:88,3
+DA:95,3
+DA:101,3
+DA:102,6
+DA:103,3
+DA:104,6
+DA:106,6
+DA:107,6
+DA:108,3
+DA:109,6
+DA:110,6
+DA:111,6
+DA:113,9
+DA:114,4
+DA:117,3
+DA:118,3
+DA:119,3
+DA:120,3
+DA:124,9
+DA:125,2
+DA:131,3
+DA:132,6
+DA:137,6
+DA:139,3
+DA:141,3
+DA:143,3
+DA:144,9
+DA:145,12
+DA:146,3
+DA:147,3
+DA:152,3
+DA:153,3
+DA:155,3
+DA:156,3
+DA:157,6
+DA:158,9
+DA:162,6
+DA:163,3
+DA:164,3
+DA:169,6
+DA:170,3
+DA:175,3
+DA:176,3
+DA:177,1
+DA:182,2
+DA:183,5
+DA:184,4
+DA:185,4
+DA:189,3
+DA:190,6
+DA:191,3
+DA:192,7
+DA:194,6
+DA:195,3
+DA:196,7
+DA:200,3
+DA:201,3
+DA:204,3
+DA:208,3
+DA:209,6
+DA:210,6
+DA:211,3
+DA:215,3
+DA:217,7
+DA:218,8
+DA:219,3
+DA:220,6
+DA:224,3
+DA:226,3
+DA:227,3
+DA:230,3
+DA:232,3
+DA:233,9
+DA:235,3
+LF:103
+LH:103
 end_of_record
 SF:lib/src/components/popup/_popup_shell.dart
-DA:12,2
-DA:21,2
-DA:23,2
-DA:24,6
-DA:26,6
-DA:27,6
-DA:29,4
-DA:31,6
-DA:32,4
-DA:33,2
+DA:13,3
+DA:22,3
+DA:24,3
+DA:25,9
+DA:27,9
+DA:28,9
+DA:30,6
+DA:32,9
+DA:33,4
 DA:34,2
 DA:35,2
-DA:36,2
-DA:38,2
+DA:37,2
+DA:42,2
 DA:43,2
-DA:46,2
-DA:47,2
-DA:48,4
-DA:49,4
-DA:50,2
-DA:51,2
+DA:45,2
+DA:48,2
+DA:49,2
+DA:50,4
+DA:51,4
+DA:52,2
 DA:53,2
-DA:62,6
-DA:63,6
-DA:64,4
-DA:66,2
-DA:67,2
-DA:72,6
-DA:73,2
-DA:77,2
-DA:78,2
-DA:79,2
-DA:80,2
-DA:82,4
-DA:86,1
-DA:89,2
-DA:97,2
-DA:99,2
-DA:100,4
-DA:101,2
+DA:55,2
+DA:64,9
+DA:65,9
+DA:66,6
+DA:68,3
+DA:69,3
+DA:74,9
+DA:75,3
+DA:79,3
+DA:80,3
+DA:81,3
+DA:82,3
+DA:84,5
+DA:88,1
+DA:91,2
+DA:99,3
+DA:101,3
 DA:102,4
-DA:103,2
-DA:104,2
+DA:103,3
+DA:104,6
 DA:105,2
 DA:106,2
 DA:107,2
 DA:108,2
+DA:109,2
+DA:110,2
 LF:47
 LH:47
 end_of_record
-SF:lib/src/components/popup/t_popup_tracker.dart
-DA:7,0
-DA:9,6
-DA:11,2
-DA:12,10
-DA:15,2
-DA:16,6
-DA:17,6
-DA:18,4
-DA:22,2
-DA:23,4
-DA:24,2
-DA:27,2
-LF:12
-LH:11
-end_of_record
 SF:lib/src/components/progress/t_progress.dart
 DA:13,0
 DA:17,0
@@ -10799,7 +10885,7 @@ LF:90
 LH:0
 end_of_record
 SF:lib/src/components/skeleton/t_skeleton_rowcol.dart
-DA:7,4
+DA:7,5
 DA:15,0
 DA:16,0
 DA:21,0
@@ -10813,11 +10899,11 @@ DA:41,0
 DA:42,0
 DA:43,0
 DA:44,0
-DA:50,4
-DA:56,4
-DA:60,4
-DA:64,4
-DA:68,4
+DA:50,5
+DA:56,5
+DA:60,5
+DA:64,5
+DA:68,5
 DA:79,0
 DA:80,0
 DA:83,0
@@ -10828,10 +10914,10 @@ DA:92,0
 DA:95,0
 DA:96,0
 DA:101,0
-DA:110,4
-DA:119,4
-DA:128,4
-DA:137,4
+DA:110,5
+DA:119,5
+DA:128,5
+DA:137,5
 DA:160,0
 LF:34
 LH:10
@@ -11190,7 +11276,7 @@ DA:523,0
 DA:524,0
 DA:525,0
 DA:526,0
-DA:532,4
+DA:532,5
 DA:534,0
 DA:539,0
 DA:561,0
@@ -12459,17 +12545,17 @@ LF:40
 LH:0
 end_of_record
 SF:lib/src/theme/basic.dart
-DA:9,2
-DA:10,4
-DA:11,6
-DA:14,2
-DA:15,8
-DA:17,2
-DA:18,2
-DA:19,4
-DA:28,2
-DA:30,2
-DA:31,6
+DA:9,3
+DA:10,6
+DA:11,9
+DA:14,3
+DA:15,12
+DA:17,3
+DA:18,3
+DA:19,6
+DA:28,3
+DA:30,3
+DA:31,9
 DA:36,0
 DA:38,0
 DA:39,0
@@ -12507,7 +12593,7 @@ DA:50,0
 DA:52,0
 DA:53,0
 DA:54,0
-DA:88,4
+DA:88,5
 DA:99,0
 DA:101,0
 DA:102,0
@@ -12548,7 +12634,7 @@ DA:158,0
 DA:163,0
 DA:164,0
 DA:166,0
-DA:199,4
+DA:199,5
 DA:211,0
 DA:213,0
 DA:214,0
@@ -12562,7 +12648,7 @@ DA:223,0
 DA:225,0
 DA:226,0
 DA:227,0
-DA:251,4
+DA:251,5
 DA:259,0
 DA:261,0
 DA:262,0
@@ -15623,19 +15709,19 @@ LF:176
 LH:0
 end_of_record
 SF:lib/src/theme/resource_delegate.dart
-DA:20,2
-DA:21,2
+DA:20,3
+DA:21,3
 DA:22,0
-DA:24,2
-DA:26,4
+DA:24,3
+DA:26,6
 DA:31,0
 DA:32,0
-DA:38,2
-DA:39,2
+DA:38,3
+DA:39,3
 DA:44,0
-DA:47,2
-DA:48,2
-DA:49,2
+DA:47,3
+DA:48,3
+DA:49,3
 DA:212,0
 DA:215,0
 DA:218,0
@@ -15710,17 +15796,17 @@ LF:8
 LH:0
 end_of_record
 SF:lib/src/util/string_util.dart
-DA:5,2
-DA:7,4
-DA:9,4
+DA:5,3
+DA:7,6
+DA:9,6
 DA:14,0
-DA:17,4
-DA:18,2
-DA:20,2
-DA:23,4
-DA:24,2
-DA:27,2
-DA:30,4
+DA:17,6
+DA:18,3
+DA:20,3
+DA:23,6
+DA:24,3
+DA:27,3
+DA:30,6
 LF:11
 LH:10
 end_of_record
diff --git a/tdesign-component/demo_tool/all_build.sh b/tdesign-component/demo_tool/all_build.sh
index 0a881ce83..5a24b520e 100644
--- a/tdesign-component/demo_tool/all_build.sh
+++ b/tdesign-component/demo_tool/all_build.sh
@@ -129,7 +129,7 @@ dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/compo
 # popover
 dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popover" --name TPopover,TPopoverWidget --folder-name popover --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments
 # popup
-dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popup" --name TSlidePopupRoute,TPopupBottomDisplayPanel,TPopupBottomConfirmPanel,TPopupCenterPanel --folder-name popup --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments
+dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popup" --name TPopup,TPopupPlacement,TPopupTrigger --folder-name popup --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments
 # refresh
 dart run tdesign_flutter_tools:main generate --file "$PARENT_DIR/lib/src/components/refresh/t_refresh_header.dart" --name TRefreshHeader --folder-name pull-down-refresh --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments
 # swipecell
diff --git a/tdesign-component/doc/popup_api.md b/tdesign-component/doc/popup_api.md
new file mode 100644
index 000000000..25c86ee2f
--- /dev/null
+++ b/tdesign-component/doc/popup_api.md
@@ -0,0 +1,300 @@
+# TPopup API 文档
+
+## 简介
+
+由其他控件触发,从屏幕边缘或中部滑出/弹出一块自定义内容区域。
+
+**对外类:** `TPopup`、`TPopupHandle`  
+**类型:** `TPopupPlacement`、`TPopupTrigger`
+
+```dart
+import 'package:tdesign_flutter/tdesign_flutter.dart';
+```
+
+---
+
+## 设计原则(按 placement)
+
+| placement | 内置头部 / 关闭 | 自定义入口 |
+|-----------|-----------------|------------|
+| **bottom** | 操作栏:**取消 \| 标题 \| 确认**(默认渲染) | `cancel`/`confirm` 传 `null` 隐藏对应侧;`cancelBuilder` / `confirmBuilder`;`headerBuilder` 整块覆盖 |
+| **top / left / right** | **无**内置头部 / 按钮区 | **仅 `child`** |
+| **center** | **内置**内容面板外下方关闭按钮 | `closeBuilder: null` 隐藏;未传 / 自定义 `closeBuilder` |
+
+**硬性约定:**
+
+- **bottom 不使用** center 关闭参数(`closeBuilder` 等)。
+- **center 不使用**底部操作栏(`onCancel` / `onConfirm` 等仅对 bottom 生效)。
+- **top / left / right 不使用** `title`、`headerBuilder` 及一切头部参数(面板内需要的标题请做在 `child` 里)。
+
+---
+
+## placement 一览
+
+### bottom(底部)
+
+| 项 | 说明 |
+|----|------|
+| 动画 | 自下而上滑入 |
+| 尺寸 | `height` 生效;`width` 忽略(横向铺满,`margin` 控制左右) |
+| margin | `top / left / right / bottom`;`margin.top` 可做日历式距顶留白 |
+| 圆角 | 仅上方两角 |
+
+**头部(操作栏)**
+
+`placement == bottom` 且未使用 `headerBuilder` 时 **默认渲染** 三栏(取消 \| 标题 \| 确认)。`onCancel` / `onConfirm` 可选;未传时点击仍默认关闭(`autoCloseOnCancel` / `autoCloseOnConfirm` 默认为 `true`)。
+
+| 区域 | 默认 | 自定义 / 隐藏 |
+|------|------|----------------|
+| 左 | 文案「取消」 | `cancel` 自定义 Widget;**`cancel: null` 隐藏左侧** |
+| 中 | `title` / `titleWidget`(可为空) | — |
+| 右 | 文案「确定」 | `confirm` 自定义 Widget;**`confirm: null` 隐藏右侧** |
+
+**无操作栏:** `cancel: null` 且 `confirm: null`(且无 `cancelBuilder` / `confirmBuilder`),例如 ActionSheet、Picker 自带工具栏的场景。
+
+> `cancel` / `confirm` 参数默认值为内部占位 `kPopupActionDefault`(未传即默认文案);与「未传参」不同,须显式写 `cancel: null` 才能隐藏。
+
+**无头部:** `headerBuilder: null`(与未传 `kPopupDefaultHeader` 不同)。
+
+**整块替换头部:** 自定义 `headerBuilder`(优先级最高,操作栏参数均不生效)。
+
+```dart
+TPopup.show(
+  context: context,
+  placement: TPopupPlacement.bottom,
+  height: 320,
+  title: '选择日期',
+  onCancel: () => TPopup.close(context),
+  onConfirm: () {
+    TPopup.close(context);
+  },
+  child: calendarBody,
+);
+```
+
+---
+
+### top(顶部)
+
+| 项 | 说明 |
+|----|------|
+| 动画 | 自上而下滑入 |
+| 尺寸 | `height` 生效;`width` 忽略 |
+| margin | `top / left / right` |
+| 头部 | **无**(不使用 `title` / `headerBuilder`) |
+
+---
+
+### left / right(侧栏)
+
+| 项 | left | right |
+|----|------|-------|
+| 动画 | 自左滑入 | 自右滑入 |
+| 尺寸 | `width` 生效(默认 280);`height` 忽略 | 同左 |
+| margin | `left / top / bottom` | `right / top / bottom` |
+| 头部 | **无**(仅 `child`) | 同左 |
+
+---
+
+### center(居中)
+
+| 项 | 说明 |
+|----|------|
+| 动画 | 缩放(非位移) |
+| 尺寸 | `width`、`height` 约束**白色内容区**;有关闭区时按钮在面板外下方额外占位 |
+| margin | 不参与定位 |
+
+**关闭区(三态 `closeBuilder`)**
+
+| 写法 | 含义 |
+|------|------|
+| 不传 `closeBuilder` | 默认 `kPopupDefaultClose` → 显示内置圆圈关闭图标 |
+| `closeBuilder: null` | **不显示**关闭区 |
+| 自定义 `closeBuilder: (ctx, close) => ...` | 自定义关闭控件;应调用 `close` 关闭(会触发 `onCloseBtn`) |
+
+| 参数 | 默认 | 说明 |
+|------|------|------|
+| `onCloseBtn` | null | 点击关闭控件时回调(在自动关闭前) |
+
+**内置布局(center 且 `closeBuilder` 非 null,无对外开关):**
+
+```text
+[上间距 40]
+[白底内容面板  ← width × height]
+[间距 24]
+[关闭按钮  ← 蒙层上,非面板内]
+```
+
+> 不提供「面板右上角 X」;若需要角标关闭,请在 `child` 内自建。
+
+```dart
+TPopup.show(
+  context: context,
+  placement: TPopupPlacement.center,
+  width: 280,
+  height: 200,
+  closeBuilder: (ctx, close) => IconButton(
+    icon: Icon(TIcons.close_circle, color: TTheme.of(ctx).fontWhColor1),
+    onPressed: close,
+  ),
+  child: dialogBody,
+);
+
+// 无关闭区
+TPopup.show(
+  context: context,
+  placement: TPopupPlacement.center,
+  closeBuilder: null,
+  child: alertBody,
+);
+```
+
+---
+
+## 生命周期回调顺序
+
+| 阶段 | 回调 | 说明 |
+|------|------|------|
+| 路由入栈 | `onOpen` | `didPush` 时 |
+| 变为可见 | `onVisibleChange(true, programmatic)` | 与打开方式无关,均为 `programmatic` |
+| 打开动画结束 | `onOpened` | 动画 `completed` |
+| 开始关闭 | `onClose` + `onVisibleChange(false, trigger)` | `trigger` 见 `TPopupTrigger` |
+| 关闭动画结束 | `onClosed` | 路由移除且动画 `dismissed` |
+
+关闭触发:`overlay` / `cancelBtn` / `confirmBtn` / `closeBtn` / `programmatic`(含系统返回、`handle.close`、`TPopup.close`)。
+
+---
+
+## Widget 与 Builder 怎么选
+
+| 类型 | 签名 | 何时创建 | 适用 |
+|------|------|----------|------|
+| **Widget** | `Widget? cancel` | 调用 `show` 时 | 完全静态 |
+| **Builder** | `WidgetBuilder? cancelBuilder` | Popup `build` 时 | **推荐**:主题、国际化 |
+
+**优先级(同一槽位):**
+
+```text
+xxxBuilder > xxx (Widget) > 内置默认 UI
+```
+
+| 槽位 | Builder(推荐) | Widget |
+|------|-----------------|--------|
+| 底部取消 | `cancelBuilder` | `cancel` |
+| 底部确认 | `confirmBuilder` | `confirm` |
+| 居中关闭 | `closeBuilder(ctx, close)` | — |
+| 整块头部(仅 bottom) | `headerBuilder` | — |
+
+---
+
+## TPopup.show(命令式)
+
+返回 [TPopupHandle];优先使用 `handle.close()`,或在 Popup 子树内使用 `TPopup.close(context)`。
+
+`cancel` / `confirm` 默认 `kPopupActionDefault` 表示默认文案;显式 `null` 隐藏对应侧。
+
+### 关闭行为
+
+- **`TPopup.close(context)`**:仅关闭当前 Navigator 上 Tracker **栈顶**且正在展示的 Popup;无 Popup 时**不操作**(不会 `maybePop` 当前页)。
+- **嵌套 Popup**:每次 `close` 只关最上层;`navigatorContext` / `useRootNavigator` 须与 `show` 一致。
+- **外层重复 `show`**:同一按钮在页面 context 上连点第二次无效(返回同一 handle);Popup 路由内可再 `show` 嵌套一层。
+
+### 动画
+
+`duration` 同时用于打开与关闭过渡(默认 240ms)。
+
+---
+
+## 参数表(摘要)
+
+### 通用
+
+| 名称 | 类型 | 默认值 | 描述 |
+|------|------|--------|------|
+| child | Widget | - | 浮层主体(必填) |
+| placement | TPopupPlacement | bottom | top / left / right / bottom / center |
+| width | double? | - | left / right / center |
+| height | double? | - | top / bottom;center 有关闭区时约束白底内容区 |
+| margin | EdgeInsets? | zero | center 忽略 |
+| showOverlay | bool | true | 半透明遮罩 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭 |
+| overlayColor | Color? | black54 | 蒙层颜色 |
+| overlayOpacity | double? | - | 与 `overlayColor` 的 alpha 相乘 |
+| preventScrollThrough | bool | true | 拦截底层滚动 |
+| destroyOnClose | bool | false | Popup 路由 `maintainState = false`;不销毁声明式 `TPopup` 的 State |
+| duration | Duration | 240ms | 开、关动画时长一致 |
+| onOpen / onOpened / onClose / onClosed | VoidCallback? | - | 生命周期 |
+| onVisibleChange | (bool, TPopupTrigger)? | - | 显隐及触发来源 |
+
+### bottom 专用
+
+`title`、`cancel`/`confirm`、`cancelBuilder`/`confirmBuilder`、`headerBuilder`(`null` 无头,未传默认操作栏)。
+
+### center 专用
+
+`closeBuilder`(三态)、`onCloseBtn`。
+
+---
+
+## Header / 关闭 优先级
+
+**bottom:**
+
+```text
+headerBuilder: null     →  无头部
+未传 headerBuilder     →  默认操作栏 / 仅标题行
+自定义 headerBuilder   →  整块自定义
+```
+
+**center:**
+
+```text
+closeBuilder: null           →  仅内容区
+未传 closeBuilder            →  默认面板外下方关闭(kPopupDefaultClose)
+自定义 closeBuilder          →  自定义控件,仍在面板外下方槽位
+```
+
+---
+
+## TPopupHandle
+
+```dart
+void close([Object? result]);
+bool get isShowing;
+```
+
+---
+
+## 声明式 TPopup
+
+```dart
+TPopup(
+  initialVisible: false,
+  placement: TPopupPlacement.bottom,
+  child: ...,
+  // 参数与 show 一致
+)
+```
+
+`build` 仅渲染 `child`;弹层在独立路由中。不支持受控 `visible`。
+
+---
+
+## 迁移对照
+
+| 旧 API | 新 API |
+|--------|--------|
+| TSlidePopupRoute | TPopup.show |
+| SlideTransitionFrom | TPopupPlacement |
+| TPopupBottomConfirmPanel | onCancel / onConfirm + title |
+| TPopupCenterPanel | placement: center + closeBuilder 三态 |
+| modalTop | margin.top(bottom) |
+
+---
+
+## 备注
+
+- 不提供键盘避让、`zIndex`、拖拽半屏、受控 `visible`。
+- 国际化:操作栏默认文案来自 `context.resource`。
+- 无障碍:蒙层语义标签;bottom 操作栏与 center 关闭带 `Semantics(button: true)`。
+- API 文档生成:源码 `///` 为唯一真相;运行 `demo_tool/all_build.sh` 中 popup 段生成 `example/assets/api/popup_api.md`。
diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md
index 414b436c9..9c455d425 100644
--- a/tdesign-component/example/assets/api/popup_api.md
+++ b/tdesign-component/example/assets/api/popup_api.md
@@ -1 +1,61 @@
-## API
\ No newline at end of file
+## API
+### TPopup
+#### 简介
+弹出层:支持五向滑入/居中弹出、蒙层、bottom 操作栏与 center 关闭区。
+
+ 命令式用法优先调用 [show];声明式将 [TPopup] 包裹业务子树并设 [initialVisible](弹层在独立路由中,[build] 仅渲染 [child])。
+ bottom 操作栏参数仅对 [TPopupPlacement.bottom] 生效;center 关闭参数仅对 center 生效;
+ top/left/right 仅使用 [child] 与布局参数。
+ 嵌套时 [close] 只关栈顶 Popup;无 Popup 时不操作当前页。
+#### 默认构造方法
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| autoCloseOnCancel | bool | true | 点击取消后是否自动关闭,默认 true。 |
+| autoCloseOnConfirm | bool | true | 点击确定后是否自动关闭,默认 true。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| cancel | Widget? | kPopupActionDefault | bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。 |
+| cancelBtn | String? | - | bottom 左侧按钮文案,覆盖默认「取消」。 |
+| cancelBuilder | WidgetBuilder? | - | bottom 左侧按钮构建器,优先级高于 [cancel]。 |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose] 默认圆圈图标; |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| confirm | Widget? | kPopupActionDefault | bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 |
+| confirmBtn | String? | - | bottom 右侧按钮文案,覆盖默认「确定」。 |
+| confirmBuilder | WidgetBuilder? | - | bottom 右侧按钮构建器,优先级高于 [confirm]。 |
+| destroyOnClose | bool | false | 为 true 时 Popup 路由 [Route.maintainState] 为 false,关闭后不保留路由内 State; |
+| duration | Duration | const Duration(milliseconds: 240) | 打开与关闭动画时长(一致)。 |
+| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader] 默认操作栏;自定义见 [TPopupHeaderBuilder]。 |
+| height | double? | - | 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 |
+| initialVisible | bool | false | 声明式:为 true 时在首帧后自动 [show]。 |
+| key |  | - |  |
+| margin | EdgeInsets? | - | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 |
+| navigatorContext | BuildContext? | - | 指定 Navigator 的 context,默认使用当前 context。 |
+| onCancel | VoidCallback? | - | 点击 bottom 左侧按钮回调。 |
+| onClose | VoidCallback? | - | 开始关闭时回调(含蒙层、按钮、程序化关闭)。 |
+| onCloseBtn | VoidCallback? | - | center 点击关闭控件前的回调。 |
+| onClosed | VoidCallback? | - | 关闭动画结束且路由移除后回调。 |
+| onConfirm | VoidCallback? | - | 点击 bottom 右侧按钮回调。 |
+| onOpen | VoidCallback? | - | 开始打开时回调(路由入栈)。 |
+| onOpened | VoidCallback? | - | 打开动画结束后回调。 |
+| onOverlayClick | VoidCallback? | - | 点击蒙层时回调(在是否关闭判断之前)。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化及触发来源。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| title | String? | - | bottom 操作栏中间标题文案。 |
+| titleAlignLeft | bool | false | bottom 仅标题行时是否左对齐,默认居中。 |
+| titleWidget | Widget? | - | bottom 操作栏中间标题组件,优先级高于 [title]。 |
+| useRootNavigator | bool | false | 是否使用根 Navigator。 |
+| width | double? | - | 宽度;对 left、right、center 生效。 |
+
+
+#### 静态方法
+
+| 名称 | 返回类型 | 参数 | 说明 |
+| --- | --- | --- | --- |
+| close |  |   required BuildContext context,  Object? result, | 关闭当前 Navigator 栈顶 [TPopup]。     仅关闭 Tracker 栈顶展示中的 Popup;无 Popup 时不操作(不会 pop 当前页)。 |
+| show |  |   required BuildContext context,  required Widget child,  TPopupPlacement placement,  double? width,  double? height,  EdgeInsets? margin,  double? radius,  Color? backgroundColor,  bool showOverlay,  bool closeOnOverlayClick,  Color? overlayColor,  double? overlayOpacity,  bool preventScrollThrough,  bool destroyOnClose,  Duration duration,  String? title,  Widget? titleWidget,  bool titleAlignLeft,  String? cancelBtn,  Widget? cancel,  WidgetBuilder? cancelBuilder,  VoidCallback? onCancel,  String? confirmBtn,  Widget? confirm,  WidgetBuilder? confirmBuilder,  VoidCallback? onConfirm,  bool autoCloseOnCancel,  bool autoCloseOnConfirm,  TPopupCloseBuilder? closeBuilder,  VoidCallback? onCloseBtn,  TPopupHeaderBuilder? headerBuilder,  VoidCallback? onOpen,  VoidCallback? onOpened,  VoidCallback? onClose,  VoidCallback? onClosed,  TPopupVisibleChangeCallback? onVisibleChange,  VoidCallback? onOverlayClick,  BuildContext? navigatorContext,  bool useRootNavigator, | 命令式打开浮层,参数与 [TPopup] 构造器一致。     返回 [TPopupHandle];优先 [TPopupHandle.close],或在 Popup 子树内 [close]。     [cancel]/[confirm] 默认 [kPopupActionDefault] 表示默认文案,显式 null 可隐藏操作栏侧。   [closeBuilder] 未传为 [kPopupDefaultClose](默认关闭图标),显式 null 不显示关闭区。 |
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt
index 2f03be28c..262fe914a 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt
@@ -10,7 +10,7 @@
         TPopup.show(
           context: context,
           placement: TPopupPlacement.center,
-          closeBtn: false,
+          closeBuilder: null,
           child: Container(
             decoration: BoxDecoration(
               color: TTheme.of(context).bgColorContainer,
diff --git a/tdesign-component/example/lib/component_test/popup_test.dart b/tdesign-component/example/lib/component_test/popup_test.dart
index c36ea65b9..5a826522b 100644
--- a/tdesign-component/example/lib/component_test/popup_test.dart
+++ b/tdesign-component/example/lib/component_test/popup_test.dart
@@ -31,7 +31,6 @@ class _TestPageState extends State {
       title: 'title',
       radius: 20,
       backgroundColor: const Color(0xFFFAFFFC),
-      closeBtn: true,
       onCloseBtn: () => TPopup.close(context),
       child: Container(
         padding: const EdgeInsets.only(left: 20, right: 20, bottom: 33),
diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart
index 2aa2328da..46553c639 100644
--- a/tdesign-component/example/lib/page/t_popup_page.dart
+++ b/tdesign-component/example/lib/page/t_popup_page.dart
@@ -12,10 +12,21 @@ class TPopupPage extends StatelessWidget {
 
   static const double _headerHeight = 58;
 
-  /// 底部标题 + 关闭(headerBuilder,非 closeBtn)。
-  static WidgetBuilder _bottomTitleCloseHeader({required String title}) {
-    return (BuildContext ctx) {
+  /// 底部标题 + 关闭(自定义 headerBuilder,使用 [TPopupHeaderData])。
+  static TPopupHeaderBuilder _bottomTitleCloseHeader({String? title}) {
+    return (BuildContext ctx, TPopupHeaderData data) {
       final theme = TTheme.of(ctx);
+      final headerTitle = data.title ??
+          (title != null && title.isNotEmpty
+              ? TText(
+                  title,
+                  textColor: theme.textColorPrimary,
+                  font: theme.fontTitleLarge,
+                  fontWeight: FontWeight.w700,
+                  maxLines: 1,
+                  overflow: TextOverflow.ellipsis,
+                )
+              : null);
       return SizedBox(
         height: _headerHeight,
         child: Padding(
@@ -23,16 +34,7 @@ class TPopupPage extends StatelessWidget {
           child: Row(
             children: [
               Expanded(
-                child: Center(
-                  child: TText(
-                    title,
-                    textColor: theme.textColorPrimary,
-                    font: theme.fontTitleLarge,
-                    fontWeight: FontWeight.w700,
-                    maxLines: 1,
-                    overflow: TextOverflow.ellipsis,
-                  ),
-                ),
+                child: Center(child: headerTitle ?? const SizedBox.shrink()),
               ),
               IconButton(
                 icon: Icon(TIcons.close, color: theme.textColorSecondary),
@@ -71,6 +73,7 @@ class TPopupPage extends StatelessWidget {
             ExampleItem(builder: _buildPopFromBottomWithCloseAndTitle),
             ExampleItem(builder: _buildPopFromCenterWithClose),
             ExampleItem(builder: _buildPopFromCenterWithUnderClose),
+            ExampleItem(builder: _buildNestedPopup),
           ],
         ),
         ExampleModule(
@@ -219,13 +222,13 @@ class TPopupPage extends StatelessWidget {
                       width: 240,
                       height: 240,
                       radius: 6,
-                      close: IconButton(
+                      closeBuilder: (_, close) => IconButton(
                         icon: Icon(
                           TIcons.close_circle,
                           color: TTheme.of(context).errorNormalColor,
                           size: 32,
                         ),
-                        onPressed: () => TPopup.close(context),
+                        onPressed: close,
                       ),
                       child: const SizedBox(height: 240, width: 240),
                     );
@@ -342,7 +345,7 @@ class TPopupPage extends StatelessWidget {
         TPopup.show(
           context: context,
           placement: TPopupPlacement.center,
-          closeBtn: false,
+          closeBuilder: null,
           child: Container(
             decoration: BoxDecoration(
               color: TTheme.of(context).bgColorContainer,
@@ -370,6 +373,7 @@ class TPopupPage extends StatelessWidget {
           context: context,
           placement: TPopupPlacement.bottom,
           height: 240,
+          headerBuilder: null,
           child: Container(
             color: TTheme.of(context).bgColorContainer,
             height: 240,
@@ -402,6 +406,71 @@ class TPopupPage extends StatelessWidget {
 
   // --- 02 组件示例 ---
 
+  /// 外层 Popup 的 child 内再 [TPopup.show]:Tracker 栈顶为内层,[TPopup.close] 先关内层。
+  @Demo(group: 'popup')
+  Widget _buildNestedPopup(BuildContext context) {
+    return TButton(
+      text: '内层再弹一层(嵌套叠加)',
+      isBlock: true,
+      theme: TButtonTheme.primary,
+      type: TButtonType.outline,
+      size: TButtonSize.large,
+      onTap: () {
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 360,
+          headerBuilder: null,
+          child: Builder(
+            builder: (innerContext) {
+              return Padding(
+                padding: const EdgeInsets.all(16),
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.stretch,
+                  children: [
+                    TText(
+                      '外层:headerBuilder: null,仅 child',
+                      textColor: TTheme.of(innerContext).textColorSecondary,
+                    ),
+                    const SizedBox(height: 16),
+                    TButton(
+                      text: '打开内层 Popup',
+                      isBlock: true,
+                      theme: TButtonTheme.primary,
+                      size: TButtonSize.large,
+                      onTap: () {
+                        TPopup.show(
+                          context: innerContext,
+                          placement: TPopupPlacement.bottom,
+                          height: 280,
+                          title: '内层标题',
+                          onCancel: () => TPopup.close(innerContext),
+                          onConfirm: () => TPopup.close(innerContext),
+                          child: Container(
+                            height: 160,
+                            color: TTheme.of(innerContext).bgColorSecondaryContainer,
+                          ),
+                        );
+                      },
+                    ),
+                    const SizedBox(height: 12),
+                    TButton(
+                      text: '关闭外层',
+                      isBlock: true,
+                      type: TButtonType.outline,
+                      size: TButtonSize.large,
+                      onTap: () => TPopup.close(innerContext),
+                    ),
+                  ],
+                ),
+              );
+            },
+          ),
+        );
+      },
+    );
+  }
+
   @Demo(group: 'popup')
   Widget _buildPopFromBottomWithOperationAndTitle(BuildContext context) {
     return TButton(
@@ -487,13 +556,13 @@ class TPopupPage extends StatelessWidget {
           closeOnOverlayClick: false,
           width: 240,
           height: 240,
-          close: IconButton(
+          closeBuilder: (_, close) => IconButton(
             icon: Icon(
               TIcons.close_circle,
               color: TTheme.of(context).fontWhColor1,
               size: 32,
             ),
-            onPressed: () => TPopup.close(context),
+            onPressed: close,
           ),
           child: const SizedBox(width: 240, height: 240),
         );
@@ -516,13 +585,13 @@ class TPopupPage extends StatelessWidget {
           closeOnOverlayClick: true,
           width: 240,
           height: 200,
-          close: IconButton(
+          closeBuilder: (_, close) => IconButton(
             icon: Icon(
               TIcons.poweroff,
               color: TTheme.of(context).fontWhColor1,
               size: 36,
             ),
-            onPressed: () => TPopup.close(context),
+            onPressed: close,
           ),
           child: Container(
             width: 240,
diff --git a/tdesign-component/lib/src/components/popup/_popup_center_close.dart b/tdesign-component/lib/src/components/popup/_popup_center_close.dart
new file mode 100644
index 000000000..b5e126fbc
--- /dev/null
+++ b/tdesign-component/lib/src/components/popup/_popup_center_close.dart
@@ -0,0 +1,77 @@
+import 'package:flutter/material.dart';
+
+import '../../theme/t_colors.dart';
+import '../../theme/t_theme.dart';
+import '../../util/context_extension.dart';
+import '../icon/t_icons.dart';
+import 't_popup_config.dart';
+import 't_popup_types.dart';
+
+/// 构建 center 面板下方关闭控件(默认图标或 [closeBuilder])。
+Widget buildPopupCenterCloseControl({
+  required BuildContext context,
+  required TPopupConfig config,
+  required VoidCallback onClose,
+}) {
+  if (isPopupDefaultClose(config.closeBuilder)) {
+    final theme = TTheme.of(context);
+    return IconButton(
+      tooltip: context.resource.close,
+      icon: Icon(
+        TIcons.close_circle,
+        color: theme.fontWhColor1,
+        size: 32,
+      ),
+      onPressed: onClose,
+    );
+  }
+  return config.closeBuilder!(context, onClose);
+}
+
+/// 居中浮层:白底内容区 + 面板**外下方**关闭控件(center 内置布局)。
+class PopupCenterUnderClose extends StatelessWidget {
+  const PopupCenterUnderClose({
+    super.key,
+    required this.config,
+    required this.content,
+    required this.onCloseWithTrigger,
+  });
+
+  final TPopupConfig config;
+  final Widget content;
+  final void Function(TPopupTrigger trigger) onCloseWithTrigger;
+
+  @override
+  Widget build(BuildContext context) {
+    Widget panel = content;
+    if (config.width != null || config.height != null) {
+      panel = SizedBox(
+        width: config.width,
+        height: config.height,
+        child: content,
+      );
+    }
+
+    void close() {
+      config.onCloseBtn?.call();
+      onCloseWithTrigger(TPopupTrigger.closeBtn);
+    }
+
+    final closeControl = buildPopupCenterCloseControl(
+      context: context,
+      config: config,
+      onClose: close,
+    );
+
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      crossAxisAlignment: CrossAxisAlignment.center,
+      children: [
+        const SizedBox(height: 40),
+        panel,
+        const SizedBox(height: 24),
+        closeControl,
+      ],
+    );
+  }
+}
diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart
index edabe0540..691ace70a 100644
--- a/tdesign-component/lib/src/components/popup/_popup_header.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_header.dart
@@ -1,9 +1,12 @@
 import 'package:flutter/material.dart';
 
-import '../../../tdesign_flutter.dart';
+import '../../theme/t_colors.dart';
+import '../../theme/t_fonts.dart';
+import '../../theme/t_spacers.dart';
+import '../../theme/t_theme.dart';
 import '../../util/context_extension.dart';
 import '../../util/t_toolbar_pressable.dart';
-import '../icon/t_icons.dart';
+import '../text/t_text.dart';
 import 't_popup_config.dart';
 import 't_popup_types.dart';
 
@@ -22,12 +25,15 @@ class PopupHeader extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    if (config.placement != TPopupPlacement.bottom) {
+    if (config.placement != TPopupPlacement.bottom || config.hasNoHeader) {
       return const SizedBox.shrink();
     }
 
-    if (config.headerBuilder != null) {
-      return config.headerBuilder!(context);
+    if (config.useCustomHeader) {
+      return config.headerBuilder!(
+        context,
+        _buildHeaderData(context),
+      );
     }
 
     if (config.useActionHeader) {
@@ -40,18 +46,7 @@ class PopupHeader extends StatelessWidget {
       );
     }
 
-    final title = config.titleWidget ??
-        (config.title != null && config.title!.isNotEmpty
-            ? TText(
-                config.title!,
-                textColor: TTheme.of(context).textColorPrimary,
-                font: TTheme.of(context).fontTitleLarge,
-                fontWeight: FontWeight.w700,
-                maxLines: 1,
-                overflow: TextOverflow.ellipsis,
-              )
-            : null);
-
+    final title = _buildTitleWidget(context);
     if (title == null) {
       return const SizedBox.shrink();
     }
@@ -67,6 +62,67 @@ class PopupHeader extends StatelessWidget {
       ),
     );
   }
+
+  TPopupHeaderData _buildHeaderData(BuildContext context) {
+    final theme = TTheme.of(context);
+    return TPopupHeaderData(
+      title: _buildTitleWidget(context),
+      cancel: config.showCancelSlot
+          ? _buildCancelWidget(context, theme)
+          : null,
+      confirm: config.showConfirmSlot
+          ? _buildConfirmWidget(context, theme)
+          : null,
+      onCancel: config.onCancel,
+      onConfirm: config.onConfirm,
+    );
+  }
+
+  Widget? _buildTitleWidget(BuildContext context) {
+    if (config.titleWidget != null) {
+      return config.titleWidget;
+    }
+    if (config.title != null && config.title!.isNotEmpty) {
+      return TText(
+        config.title!,
+        textColor: TTheme.of(context).textColorPrimary,
+        font: TTheme.of(context).fontTitleLarge,
+        fontWeight: FontWeight.w700,
+        maxLines: 1,
+        overflow: TextOverflow.ellipsis,
+      );
+    }
+    return null;
+  }
+
+  Widget _buildCancelWidget(BuildContext context, TThemeData theme) {
+    if (config.cancelBuilder != null) {
+      return config.cancelBuilder!(context);
+    }
+    if (TPopupConfig.isActionDefault(config.cancel)) {
+      return TText(
+        config.cancelBtn ?? context.resource.cancel,
+        textColor: theme.textColorSecondary,
+        font: theme.fontBodyLarge,
+      );
+    }
+    return config.cancel!;
+  }
+
+  Widget _buildConfirmWidget(BuildContext context, TThemeData theme) {
+    if (config.confirmBuilder != null) {
+      return config.confirmBuilder!(context);
+    }
+    if (TPopupConfig.isActionDefault(config.confirm)) {
+      return TText(
+        config.confirmBtn ?? context.resource.confirm,
+        textColor: theme.brandNormalColor,
+        font: theme.fontTitleMedium,
+        fontWeight: FontWeight.w600,
+      );
+    }
+    return config.confirm!;
+  }
 }
 
 class _ActionHeader extends StatelessWidget {
@@ -183,61 +239,3 @@ String _confirmSemanticsLabel(BuildContext context, TPopupConfig config) {
   }
   return context.resource.confirm;
 }
-
-/// 居中:关闭按钮在内容下方。
-class PopupCenterUnderClose extends StatelessWidget {
-  const PopupCenterUnderClose({
-    super.key,
-    required this.config,
-    required this.content,
-    required this.onCloseWithTrigger,
-  });
-
-  final TPopupConfig config;
-  final Widget content;
-  final void Function(TPopupTrigger trigger) onCloseWithTrigger;
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = TTheme.of(context);
-    Widget panel = content;
-    if (config.width != null || config.height != null) {
-      panel = SizedBox(
-        width: config.width,
-        height: config.height,
-        child: content,
-      );
-    }
-
-    Widget closeControl;
-    if (config.closeBuilder != null) {
-      closeControl = config.closeBuilder!(context);
-    } else if (config.close != null) {
-      closeControl = config.close!;
-    } else {
-      closeControl = IconButton(
-        tooltip: context.resource.close,
-        icon: Icon(
-          TIcons.close_circle,
-          color: theme.fontWhColor1,
-          size: 32,
-        ),
-        onPressed: () {
-          config.onCloseBtn?.call();
-          onCloseWithTrigger(TPopupTrigger.closeBtn);
-        },
-      );
-    }
-
-    return Column(
-      mainAxisSize: MainAxisSize.min,
-      crossAxisAlignment: CrossAxisAlignment.center,
-      children: [
-        const SizedBox(height: 40),
-        panel,
-        const SizedBox(height: 24),
-        closeControl,
-      ],
-    );
-  }
-}
diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart
index b1868eff8..bad5faed9 100644
--- a/tdesign-component/lib/src/components/popup/_popup_route.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_route.dart
@@ -5,8 +5,6 @@ import '_popup_shell.dart';
 import 't_popup_config.dart';
 import 't_popup_types.dart';
 
-const Duration _kReverseDuration = Duration(milliseconds: 200);
-
 /// 私有 Popup 路由。
 class TPopupNavigatorRoute extends PopupRoute {
   TPopupNavigatorRoute({
@@ -47,7 +45,7 @@ class TPopupNavigatorRoute extends PopupRoute {
   Duration get transitionDuration => config.duration;
 
   @override
-  Duration get reverseTransitionDuration => _kReverseDuration;
+  Duration get reverseTransitionDuration => config.duration;
 
   @override
   bool get barrierDismissible => false;
@@ -112,7 +110,8 @@ class TPopupNavigatorRoute extends PopupRoute {
       width: config.width,
       height: config.height,
       centerLooseHeight:
-          config.placement == TPopupPlacement.center && config.closeBtn,
+          config.placement == TPopupPlacement.center &&
+              config.closeBuilder != null,
     );
 
     final t = curved.value;
@@ -161,10 +160,8 @@ class TPopupNavigatorRoute extends PopupRoute {
       ),
     );
     if (config.showOverlay) {
-      final label = _barrierSemanticsLabel ??
-          MaterialLocalizations.of(context).modalBarrierDismissLabel;
       barrier = Semantics(
-        label: label,
+        label: _barrierSemanticsLabel!,
         button: true,
         child: barrier,
       );
diff --git a/tdesign-component/lib/src/components/popup/_popup_shell.dart b/tdesign-component/lib/src/components/popup/_popup_shell.dart
index d2e7ec0ec..66513d62a 100644
--- a/tdesign-component/lib/src/components/popup/_popup_shell.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_shell.dart
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
 import '../../theme/t_colors.dart';
 import '../../theme/t_radius.dart';
 import '../../theme/t_theme.dart';
+import '_popup_center_close.dart';
 import '_popup_header.dart';
 import 't_popup_config.dart';
 import 't_popup_types.dart';
@@ -29,17 +30,18 @@ class PopupShell extends StatelessWidget {
     Widget content = config.child;
 
     if (config.placement == TPopupPlacement.center) {
-      if (config.closeBtn) {
+      if (config.closeBuilder != null) {
+        final panel = Container(
+          decoration: BoxDecoration(
+            color: backgroundColor,
+            borderRadius: BorderRadius.circular(radius),
+          ),
+          clipBehavior: Clip.antiAlias,
+          child: content,
+        );
         return PopupCenterUnderClose(
           config: config,
-          content: Container(
-            decoration: BoxDecoration(
-              color: backgroundColor,
-              borderRadius: BorderRadius.circular(radius),
-            ),
-            clipBehavior: Clip.antiAlias,
-            child: content,
-          ),
+          content: panel,
           onCloseWithTrigger: onCloseWithTrigger,
         );
       }
diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart
index 5e899176c..14b34c3dd 100644
--- a/tdesign-component/lib/src/components/popup/t_popup.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup.dart
@@ -2,12 +2,19 @@ import 'package:flutter/material.dart';
 
 import '_popup_route.dart';
 import 't_popup_config.dart';
-import 't_popup_tracker.dart';
 import 't_popup_types.dart';
 
 export 't_popup_types.dart';
 
-/// 弹出层。
+part 't_popup_handle.dart';
+part 't_popup_tracker.dart';
+
+/// 弹出层:支持五向滑入/居中弹出、蒙层、bottom 操作栏与 center 关闭区。
+///
+/// 命令式用法优先调用 [show];声明式将 [TPopup] 包裹业务子树并设 [initialVisible](弹层在独立路由中,[build] 仅渲染 [child])。
+/// bottom 操作栏参数仅对 [TPopupPlacement.bottom] 生效;center 关闭参数仅对 center 生效;
+/// top/left/right 仅使用 [child] 与布局参数。
+/// 嵌套时 [close] 只关栈顶 Popup;无 Popup 时不操作当前页。
 class TPopup extends StatefulWidget {
   const TPopup({
     super.key,
@@ -24,8 +31,6 @@ class TPopup extends StatefulWidget {
     this.overlayColor,
     this.overlayOpacity,
     this.preventScrollThrough = true,
-    /// 关闭后 Popup 路由是否不再 [maintainState](不保留路由内 State)。
-    /// 不影响声明式 [TPopup] 自身 [State] 的存活。
     this.destroyOnClose = false,
     this.duration = const Duration(milliseconds: 240),
     this.title,
@@ -41,12 +46,9 @@ class TPopup extends StatefulWidget {
     this.onConfirm,
     this.autoCloseOnCancel = true,
     this.autoCloseOnConfirm = true,
-    this.closeBtn,
-    Widget? close,
-    this.closeBuilder,
-    this.closeBelowContent,
+    this.closeBuilder = kPopupDefaultClose,
     this.onCloseBtn,
-    this.headerBuilder,
+    this.headerBuilder = kPopupDefaultHeader,
     this.onOpen,
     this.onOpened,
     this.onClose,
@@ -55,66 +57,133 @@ class TPopup extends StatefulWidget {
     this.onOverlayClick,
     this.navigatorContext,
     this.useRootNavigator = false,
-  }) : closeWidget = close;
+  });
 
+  /// 浮层主体内容(必填)。
   final Widget child;
+
+  /// 声明式:为 true 时在首帧后自动 [show]。
   final bool initialVisible;
 
+  /// 出现位置,默认 [TPopupPlacement.bottom]。
   final TPopupPlacement placement;
+
+  /// 宽度;对 left、right、center 生效。
   final double? width;
+
+  /// 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。
   final double? height;
+
+  /// 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。
   final EdgeInsets? margin;
+
+  /// 内容区圆角,默认主题大圆角。
   final double? radius;
+
+  /// 内容区背景色,默认主题容器色。
   final Color? backgroundColor;
+
+  /// 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。
   final bool showOverlay;
+
+  /// 点击蒙层是否关闭(须 [showOverlay] 为 true)。
   final bool closeOnOverlayClick;
+
+  /// 蒙层颜色,默认 black54。
   final Color? overlayColor;
+
+  /// 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。
   final double? overlayOpacity;
+
+  /// 是否拦截底层滚动;无蒙层时用透明层吸收滚动。
   final bool preventScrollThrough;
 
-  /// 关闭后 Popup 路由是否不再 [maintainState](不保留路由内 State)。
-  /// 不影响声明式 [TPopup] 自身 [State] 的存活。
+  /// 为 true 时 Popup 路由 [Route.maintainState] 为 false,关闭后不保留路由内 State;
+  /// 不销毁包裹 [TPopup] 的 StatefulWidget State。
   final bool destroyOnClose;
+
+  /// 打开与关闭动画时长(一致)。
   final Duration duration;
 
+  /// bottom 操作栏中间标题文案。
   final String? title;
+
+  /// bottom 操作栏中间标题组件,优先级高于 [title]。
   final Widget? titleWidget;
+
+  /// bottom 仅标题行时是否左对齐,默认居中。
   final bool titleAlignLeft;
+
+  /// bottom 左侧按钮文案,覆盖默认「取消」。
   final String? cancelBtn;
+
+  /// bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。
   final Widget? cancel;
+
+  /// bottom 左侧按钮构建器,优先级高于 [cancel]。
   final WidgetBuilder? cancelBuilder;
+
+  /// 点击 bottom 左侧按钮回调。
   final VoidCallback? onCancel;
+
+  /// bottom 右侧按钮文案,覆盖默认「确定」。
   final String? confirmBtn;
+
+  /// bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。
   final Widget? confirm;
+
+  /// bottom 右侧按钮构建器,优先级高于 [confirm]。
   final WidgetBuilder? confirmBuilder;
+
+  /// 点击 bottom 右侧按钮回调。
   final VoidCallback? onConfirm;
+
+  /// 点击取消后是否自动关闭,默认 true。
   final bool autoCloseOnCancel;
+
+  /// 点击确定后是否自动关闭,默认 true。
   final bool autoCloseOnConfirm;
 
-  /// center 默认 true;bottom / 三边忽略。
-  final bool? closeBtn;
-  /// [TPopupPlacement.center] 自定义关闭控件;构造参数名为 [close](与 [show] 一致)。
-  final Widget? closeWidget;
-  final WidgetBuilder? closeBuilder;
+  /// center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose] 默认圆圈图标;
+  /// 自定义时通过 [close] 回调关闭。bottom 与三边忽略。
+  final TPopupCloseBuilder? closeBuilder;
 
-  /// center 且显示关闭时默认 true。
-  final bool? closeBelowContent;
+  /// center 点击关闭控件前的回调。
   final VoidCallback? onCloseBtn;
 
-  /// 仅 [TPopupPlacement.bottom] 生效。
-  final WidgetBuilder? headerBuilder;
+  /// bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader] 默认操作栏;自定义见 [TPopupHeaderBuilder]。
+  final TPopupHeaderBuilder? headerBuilder;
 
+  /// 开始打开时回调(路由入栈)。
   final VoidCallback? onOpen;
+
+  /// 打开动画结束后回调。
   final VoidCallback? onOpened;
+
+  /// 开始关闭时回调(含蒙层、按钮、程序化关闭)。
   final VoidCallback? onClose;
+
+  /// 关闭动画结束且路由移除后回调。
   final VoidCallback? onClosed;
+
+  /// 显隐变化及触发来源。
   final TPopupVisibleChangeCallback? onVisibleChange;
+
+  /// 点击蒙层时回调(在是否关闭判断之前)。
   final VoidCallback? onOverlayClick;
 
+  /// 指定 Navigator 的 context,默认使用当前 context。
   final BuildContext? navigatorContext;
+
+  /// 是否使用根 Navigator。
   final bool useRootNavigator;
 
-  /// 打开浮层。
+  /// 命令式打开浮层,参数与 [TPopup] 构造器一致。
+  ///
+  /// 返回 [TPopupHandle];优先 [TPopupHandle.close],或在 Popup 子树内 [close]。
+  ///
+  /// [cancel]/[confirm] 默认 [kPopupActionDefault] 表示默认文案,显式 null 可隐藏操作栏侧。
+  /// [closeBuilder] 未传为 [kPopupDefaultClose](默认关闭图标),显式 null 不显示关闭区。
   static TPopupHandle show({
     required BuildContext context,
     required Widget child,
@@ -129,7 +198,6 @@ class TPopup extends StatefulWidget {
     Color? overlayColor,
     double? overlayOpacity,
     bool preventScrollThrough = true,
-    /// 关闭后 Popup 路由是否不再 [maintainState](不保留路由内 State)。
     bool destroyOnClose = false,
     Duration duration = const Duration(milliseconds: 240),
     String? title,
@@ -145,12 +213,9 @@ class TPopup extends StatefulWidget {
     VoidCallback? onConfirm,
     bool autoCloseOnCancel = true,
     bool autoCloseOnConfirm = true,
-    bool? closeBtn,
-    Widget? close,
-    WidgetBuilder? closeBuilder,
-    bool? closeBelowContent,
+    TPopupCloseBuilder? closeBuilder = kPopupDefaultClose,
     VoidCallback? onCloseBtn,
-    WidgetBuilder? headerBuilder,
+    TPopupHeaderBuilder? headerBuilder = kPopupDefaultHeader,
     VoidCallback? onOpen,
     VoidCallback? onOpened,
     VoidCallback? onClose,
@@ -188,10 +253,7 @@ class TPopup extends StatefulWidget {
       onConfirm: onConfirm,
       autoCloseOnCancel: autoCloseOnCancel,
       autoCloseOnConfirm: autoCloseOnConfirm,
-      closeBtn: closeBtn,
-      close: close,
       closeBuilder: closeBuilder,
-      closeBelowContent: closeBelowContent,
       onCloseBtn: onCloseBtn,
       headerBuilder: headerBuilder,
       onOpen: onOpen,
@@ -209,14 +271,21 @@ class TPopup extends StatefulWidget {
       rootNavigator: useRootNavigator,
     );
 
+    final existing = TPopupTracker.top(navigator);
+    if (existing != null &&
+        existing.isShowing &&
+        ModalRoute.of(context) is! TPopupNavigatorRoute) {
+      return existing;
+    }
+
     TPopupNavigatorRoute? route;
     late TPopupHandle handle;
 
     void closeWithTrigger(TPopupTrigger trigger, [Object? result]) {
-      if (handle._isClosed) {
+      if (!handle.isShowing) {
         return;
       }
-      handle._isClosed = true;
+      handle._markClosing();
       route?.fireCloseStart(trigger);
       navigator.pop(result);
     }
@@ -227,7 +296,6 @@ class TPopup extends StatefulWidget {
     );
 
     handle = TPopupHandle._(
-      navigator: navigator,
       route: route,
       onCloseWithTrigger: closeWithTrigger,
     );
@@ -236,22 +304,21 @@ class TPopup extends StatefulWidget {
 
     navigator.push(route).whenComplete(() {
       TPopupTracker.remove(navigator, handle);
-      handle._isClosed = true;
-      handle._route = null;
+      handle._detachRoute();
     });
 
     return handle;
   }
 
-  /// 关闭当前 Navigator 栈顶 [TPopup];触发与 [TPopupHandle.close] 相同的生命周期回调。
+  /// 关闭当前 Navigator 栈顶 [TPopup]。
+  ///
+  /// 仅关闭 Tracker 栈顶展示中的 Popup;无 Popup 时不操作(不会 pop 当前页)。
   static void close(BuildContext context, [Object? result]) {
     final navigator = Navigator.of(context);
     final handle = TPopupTracker.top(navigator);
     if (handle?.isShowing == true) {
       handle!.close(result);
-      return;
     }
-    navigator.maybePop(result);
   }
 
   @override
@@ -310,10 +377,7 @@ class _TPopupState extends State {
       onConfirm: widget.onConfirm,
       autoCloseOnCancel: widget.autoCloseOnCancel,
       autoCloseOnConfirm: widget.autoCloseOnConfirm,
-      closeBtn: widget.closeBtn,
-      close: widget.closeWidget,
       closeBuilder: widget.closeBuilder,
-      closeBelowContent: widget.closeBelowContent,
       onCloseBtn: widget.onCloseBtn,
       headerBuilder: widget.headerBuilder,
       onOpen: widget.onOpen,
@@ -330,28 +394,3 @@ class _TPopupState extends State {
     return widget.child;
   }
 }
-
-/// [TPopup.show] 返回的句柄。
-class TPopupHandle {
-  TPopupHandle._({
-    required NavigatorState navigator,
-    required TPopupNavigatorRoute? route,
-    required void Function(TPopupTrigger trigger, [Object? result])
-        onCloseWithTrigger,
-  })  : _route = route,
-        _onCloseWithTrigger = onCloseWithTrigger;
-
-  TPopupNavigatorRoute? _route;
-  final void Function(TPopupTrigger trigger, [Object? result])
-      _onCloseWithTrigger;
-  bool _isClosed = false;
-
-  bool get isShowing => _route != null && !_isClosed;
-
-  void close([Object? result]) {
-    if (!isShowing) {
-      return;
-    }
-    _onCloseWithTrigger(TPopupTrigger.programmatic, result);
-  }
-}
diff --git a/tdesign-component/lib/src/components/popup/t_popup_config.dart b/tdesign-component/lib/src/components/popup/t_popup_config.dart
index a2a4894f5..385beae88 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_config.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_config.dart
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
 
 import 't_popup_types.dart';
 
-/// Popup 运行时配置(库内共享)。
+/// Popup 运行时配置(库内共享,由 [TPopup.show] 通过 [TPopupConfig.create] 构建)。
 class TPopupConfig {
   TPopupConfig({
     required this.child,
@@ -17,7 +17,6 @@ class TPopupConfig {
     this.overlayColor,
     this.overlayOpacity,
     this.preventScrollThrough = true,
-    /// 为 true 时路由 [maintainState] 为 false,关闭后丢弃 Popup 路由 State。
     this.destroyOnClose = false,
     this.duration = const Duration(milliseconds: 240),
     this.title,
@@ -33,10 +32,7 @@ class TPopupConfig {
     this.onConfirm,
     this.autoCloseOnCancel = true,
     this.autoCloseOnConfirm = true,
-    this.closeBtn = false,
-    this.close,
     this.closeBuilder,
-    this.closeBelowContent = false,
     this.onCloseBtn,
     this.headerBuilder,
     this.onOpen,
@@ -47,7 +43,7 @@ class TPopupConfig {
     this.onOverlayClick,
   });
 
-  /// 按 [placement] 归一化参数(bottom 无 closeBtn;center 默认关闭;三边仅 child)。
+  /// 按 [placement] 归一化参数(bottom/三边忽略 closeBuilder;center 默认 [kPopupDefaultClose])。
   factory TPopupConfig.create({
     required Widget child,
     TPopupPlacement placement = TPopupPlacement.bottom,
@@ -76,12 +72,9 @@ class TPopupConfig {
     VoidCallback? onConfirm,
     bool autoCloseOnCancel = true,
     bool autoCloseOnConfirm = true,
-    bool? closeBtn,
-    Widget? close,
-    WidgetBuilder? closeBuilder,
-    bool? closeBelowContent,
+    TPopupCloseBuilder? closeBuilder = kPopupDefaultClose,
     VoidCallback? onCloseBtn,
-    WidgetBuilder? headerBuilder,
+    TPopupHeaderBuilder? headerBuilder = kPopupDefaultHeader,
     VoidCallback? onOpen,
     VoidCallback? onOpened,
     VoidCallback? onClose,
@@ -91,9 +84,7 @@ class TPopupConfig {
   }) {
     final isBottom = placement == TPopupPlacement.bottom;
     final isCenter = placement == TPopupPlacement.center;
-    final effectiveCloseBtn = isCenter ? (closeBtn ?? true) : false;
-    final effectiveCloseBelow =
-        isCenter && effectiveCloseBtn ? (closeBelowContent ?? true) : false;
+    final effectiveCloseBuilder = isCenter ? closeBuilder : null;
 
     return TPopupConfig(
       child: child,
@@ -123,10 +114,7 @@ class TPopupConfig {
       onConfirm: isBottom ? onConfirm : null,
       autoCloseOnCancel: autoCloseOnCancel,
       autoCloseOnConfirm: autoCloseOnConfirm,
-      closeBtn: effectiveCloseBtn,
-      close: isCenter ? close : null,
-      closeBuilder: isCenter ? closeBuilder : null,
-      closeBelowContent: effectiveCloseBelow,
+      closeBuilder: effectiveCloseBuilder,
       onCloseBtn: isCenter ? onCloseBtn : null,
       headerBuilder: isBottom ? headerBuilder : null,
       onOpen: onOpen,
@@ -151,7 +139,7 @@ class TPopupConfig {
   final double? overlayOpacity;
   final bool preventScrollThrough;
 
-  /// 为 true 时路由 [maintainState] 为 false,关闭后丢弃 Popup 路由 State。
+  /// 为 true 时路由 maintainState 为 false,关闭后丢弃 Popup 路由 State。
   final bool destroyOnClose;
   final Duration duration;
 
@@ -169,13 +157,12 @@ class TPopupConfig {
   final bool autoCloseOnCancel;
   final bool autoCloseOnConfirm;
 
-  final bool closeBtn;
-  final Widget? close;
-  final WidgetBuilder? closeBuilder;
-  final bool closeBelowContent;
+  /// center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];自定义见 [TPopupCloseBuilder]。
+  final TPopupCloseBuilder? closeBuilder;
+
   final VoidCallback? onCloseBtn;
 
-  final WidgetBuilder? headerBuilder;
+  final TPopupHeaderBuilder? headerBuilder;
 
   final VoidCallback? onOpen;
   final VoidCallback? onOpened;
@@ -194,20 +181,36 @@ class TPopupConfig {
       placement == TPopupPlacement.bottom &&
       (confirmBuilder != null || confirm != null);
 
-  /// 底部操作栏(取消 | 标题 | 确认),仅 bottom 且未使用 [headerBuilder]。
+  /// 不渲染 bottom 头部(显式 [headerBuilder: null])。
+  bool get hasNoHeader =>
+      placement == TPopupPlacement.bottom && headerBuilder == null;
+
+  /// 使用内置操作栏(未传 headerBuilder,占位为 [kPopupDefaultHeader])。
   bool get useActionHeader =>
       placement == TPopupPlacement.bottom &&
-      headerBuilder == null &&
+      isPopupDefaultHeader(headerBuilder) &&
       (showCancelSlot || showConfirmSlot);
 
+  /// 自定义头部(非 null 且非默认占位)。
+  bool get useCustomHeader =>
+      placement == TPopupPlacement.bottom &&
+      headerBuilder != null &&
+      !isPopupDefaultHeader(headerBuilder);
+
   static bool isActionDefault(Widget? action) => action is TPopupActionDefault;
 
+  /// bottom 仅标题行(默认头部占位 + 无 cancel/confirm 槽 + 有 title)。
+  bool get useTitleOnlyHeader =>
+      placement == TPopupPlacement.bottom &&
+      isPopupDefaultHeader(headerBuilder) &&
+      !showCancelSlot &&
+      !showConfirmSlot &&
+      ((title != null && title!.isNotEmpty) || titleWidget != null);
+
   bool get hasBuiltInHeader =>
       placement == TPopupPlacement.bottom &&
-      (headerBuilder != null ||
-          useActionHeader ||
-          (title != null && title!.isNotEmpty) ||
-          titleWidget != null);
+      !hasNoHeader &&
+      (useCustomHeader || useActionHeader || useTitleOnlyHeader);
 
   void assertPlacementParams() {
     assert(() {
@@ -221,7 +224,7 @@ class TPopupConfig {
           }
           break;
         case TPopupPlacement.center:
-          if (height != null && !(closeBtn && closeBelowContent)) {
+          if (height != null && closeBuilder == null) {
             debugPrint(
               'TPopup: height is ignored for placement=$placement',
             );
@@ -236,7 +239,11 @@ class TPopupConfig {
           }
           break;
       }
-      if (placement != TPopupPlacement.bottom && useActionHeader) {
+      if (placement != TPopupPlacement.bottom &&
+          (cancel != null ||
+              confirm != null ||
+              cancelBuilder != null ||
+              confirmBuilder != null)) {
         debugPrint(
           'TPopup: cancel/confirm only applies to placement=bottom',
         );
diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
new file mode 100644
index 000000000..15787d56f
--- /dev/null
+++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
@@ -0,0 +1,38 @@
+part of 't_popup.dart';
+
+/// [TPopup.show] 返回的句柄,用于查询展示状态与程序化关闭。
+class TPopupHandle {
+  TPopupHandle._({
+    required void Function(TPopupTrigger trigger, [Object? result])
+        onCloseWithTrigger,
+    TPopupNavigatorRoute? route,
+  })  : _route = route,
+        _onCloseWithTrigger = onCloseWithTrigger;
+
+  TPopupNavigatorRoute? _route;
+  final void Function(TPopupTrigger trigger, [Object? result])
+      _onCloseWithTrigger;
+  bool _isClosed = false;
+
+  /// 浮层是否仍在展示(路由在栈中且未进入关闭流程)。
+  bool get isShowing => _route != null && !_isClosed;
+
+  /// 以 [TPopupTrigger.programmatic] 关闭浮层,可向 Navigator 传递 [result]。
+  ///
+  /// 优先于 [TPopup.close]:不依赖 context 的 Navigator 解析。
+  void close([Object? result]) {
+    if (!isShowing) {
+      return;
+    }
+    _onCloseWithTrigger(TPopupTrigger.programmatic, result);
+  }
+
+  void _markClosing() {
+    _isClosed = true;
+  }
+
+  void _detachRoute() {
+    _isClosed = true;
+    _route = null;
+  }
+}
diff --git a/tdesign-component/lib/src/components/popup/t_popup_tracker.dart b/tdesign-component/lib/src/components/popup/t_popup_tracker.dart
index 9b177475a..f8b461f2c 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_tracker.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_tracker.dart
@@ -1,11 +1,7 @@
-import 'package:flutter/material.dart';
-
-import 't_popup.dart';
-
-/// 按 Navigator 追踪当前展示的 [TPopupHandle] 栈。
-class TPopupTracker {
-  TPopupTracker._();
+part of 't_popup.dart';
 
+/// 按 Navigator 追踪已打开的 [TPopupHandle] 栈,供 [TPopup.close] 查找栈顶。
+abstract class TPopupTracker {
   static final Map> _stacks = {};
 
   static void push(NavigatorState navigator, TPopupHandle handle) {
diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart
index a3a3f11f8..b17e0339a 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_types.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart
@@ -1,12 +1,27 @@
 import 'package:flutter/widgets.dart';
 
 /// 浮层出现位置。
-enum TPopupPlacement { top, left, right, bottom, center }
+enum TPopupPlacement {
+  /// 自屏幕顶部滑入;height 与 margin 的 top/left/right 生效。
+  top,
 
-/// [TPopup.show] 未传 [cancel]/[confirm] 时的占位,表示使用默认「取消」「确定」文案。
+  /// 自屏幕左侧滑入;width 与 margin 的 left/top/bottom 生效。
+  left,
+
+  /// 自屏幕右侧滑入;width 与 margin 的 right/top/bottom 生效。
+  right,
+
+  /// 自屏幕底部滑入;默认带操作栏,height 与 margin 生效。
+  bottom,
+
+  /// 屏幕居中缩放弹出;默认内容下方关闭按钮。
+  center,
+}
+
+/// 未传 [cancel]/[confirm] 时的占位 Widget,表示使用默认「取消」「确定」文案。
 ///
-/// 传 `cancel: null` / `confirm: null` 可隐藏对应侧;两侧均为 `null` 且无
-/// [cancelBuilder]/[confirmBuilder] 时不渲染操作栏。
+/// 须显式传 `cancel: null` / `confirm: null` 才能隐藏对应侧;两侧均为 null 且无
+/// Builder 时不渲染 bottom 操作栏。
 class TPopupActionDefault extends StatelessWidget {
   const TPopupActionDefault({super.key});
 
@@ -14,18 +29,87 @@ class TPopupActionDefault extends StatelessWidget {
   Widget build(BuildContext context) => const SizedBox.shrink();
 }
 
-/// 与 [TPopupActionDefault] 同一实例,供默认参数使用。
+/// 与 [TPopupActionDefault] 同一实例,供 [TPopup.show] / [TPopup] 默认参数使用。
 const Widget kPopupActionDefault = TPopupActionDefault();
 
+/// 自定义 [headerBuilder] 时传入的标题栏数据(已按 cancel/confirm/title 参数组装好 Widget)。
+class TPopupHeaderData {
+  const TPopupHeaderData({
+    this.title,
+    this.cancel,
+    this.confirm,
+    this.onCancel,
+    this.onConfirm,
+  });
+
+  /// 中间标题区(可能为 null)。
+  final Widget? title;
+
+  /// 左侧按钮区(可能为 null,表示该侧已隐藏)。
+  final Widget? cancel;
+
+  /// 右侧按钮区(可能为 null)。
+  final Widget? confirm;
+
+  final VoidCallback? onCancel;
+  final VoidCallback? onConfirm;
+}
+
+/// bottom 自定义头部构建器。
+typedef TPopupHeaderBuilder = Widget Function(
+  BuildContext context,
+  TPopupHeaderData data,
+);
+
+/// 未传 [headerBuilder] 时的默认参数占位,表示使用内置 bottom 操作栏。
+///
+/// **勿直接调用**;仅作为默认参数值。
+/// 与 [headerBuilder: null](不渲染任何头部)不同,须显式区分。
+Widget kPopupDefaultHeader(BuildContext context, TPopupHeaderData data) {
+  return const SizedBox.shrink();
+}
+
+/// 是否为「使用默认操作栏」(未传 [headerBuilder] 时的默认参数值)。
+bool isPopupDefaultHeader(TPopupHeaderBuilder? builder) =>
+    builder == kPopupDefaultHeader;
+
+/// center 下方关闭区构建器;[close] 会触发 [onCloseBtn] 并关闭浮层。
+typedef TPopupCloseBuilder = Widget Function(
+  BuildContext context,
+  VoidCallback close,
+);
+
+/// 未传 [closeBuilder] 时的默认参数占位,表示使用内置圆圈关闭图标。
+///
+/// **勿直接调用**;仅作为 [TPopup.show] / [TPopup] 的默认参数值。
+/// 与 [closeBuilder: null](不渲染关闭区)不同,须显式区分。
+Widget kPopupDefaultClose(BuildContext context, VoidCallback close) {
+  return const SizedBox.shrink();
+}
+
+/// 是否为「使用默认关闭按钮」(未传 [closeBuilder] 时的默认参数值)。
+bool isPopupDefaultClose(TPopupCloseBuilder? builder) =>
+    builder == kPopupDefaultClose;
+
 /// 显隐变化触发来源。
 enum TPopupTrigger {
+  /// 点击半透明蒙层。
   overlay,
+
+  /// 点击 center 下方关闭控件。
   closeBtn,
+
+  /// 点击 bottom 操作栏左侧取消。
   cancelBtn,
+
+  /// 点击 bottom 操作栏右侧确定。
   confirmBtn,
+
+  /// [TPopupHandle.close]、[TPopup.close] 或系统返回等程序化关闭。
   programmatic,
 }
 
+/// 显隐回调:是否可见及触发来源。
 typedef TPopupVisibleChangeCallback = void Function(
   bool visible,
   TPopupTrigger trigger,
diff --git a/tdesign-component/test/t_popup_config_test.dart b/tdesign-component/test/t_popup_config_test.dart
index 41a815d3e..884defd1f 100644
--- a/tdesign-component/test/t_popup_config_test.dart
+++ b/tdesign-component/test/t_popup_config_test.dart
@@ -33,33 +33,32 @@ void main() {
       expect(config.showConfirmSlot, isFalse);
     });
 
-    test('create 忽略 bottom 的 closeBtn', () {
+    test('create 忽略 bottom 的 closeBuilder', () {
       final config = TPopupConfig.create(
         child: const SizedBox(),
         placement: TPopupPlacement.bottom,
-        closeBtn: true,
+        closeBuilder: (_, __) => const Text('x'),
         onCancel: () {},
       );
-      expect(config.closeBtn, isFalse);
+      expect(config.closeBuilder, isNull);
       expect(config.useActionHeader, isTrue);
     });
 
-    test('create center 默认 closeBtn 与 closeBelowContent', () {
+    test('create center 默认 closeBuilder', () {
       final config = TPopupConfig.create(
         child: const SizedBox(),
         placement: TPopupPlacement.center,
       );
-      expect(config.closeBtn, isTrue);
-      expect(config.closeBelowContent, isTrue);
+      expect(isPopupDefaultClose(config.closeBuilder), isTrue);
     });
 
-    test('create center closeBtn false', () {
+    test('create center closeBuilder null 无关闭区', () {
       final config = TPopupConfig.create(
         child: const SizedBox(),
         placement: TPopupPlacement.center,
-        closeBtn: false,
+        closeBuilder: null,
       );
-      expect(config.closeBtn, isFalse);
+      expect(config.closeBuilder, isNull);
     });
 
     test('create top 剥离 title 与 headerBuilder', () {
@@ -67,7 +66,7 @@ void main() {
         child: const SizedBox(),
         placement: TPopupPlacement.top,
         title: '标题',
-        headerBuilder: (_) => const Text('h'),
+        headerBuilder: (_, __) => const Text('h'),
         onCancel: () {},
       );
       expect(config.title, isNull);
@@ -76,6 +75,17 @@ void main() {
       expect(config.hasBuiltInHeader, isFalse);
     });
 
+    test('headerBuilder null 表示无头部', () {
+      final config = TPopupConfig.create(
+        child: const SizedBox(),
+        placement: TPopupPlacement.bottom,
+        title: '标题',
+        headerBuilder: null,
+      );
+      expect(config.hasNoHeader, isTrue);
+      expect(config.hasBuiltInHeader, isFalse);
+    });
+
     test('hasBuiltInHeader 识别 title 与 headerBuilder', () {
       expect(
         TPopupConfig.create(
@@ -99,7 +109,7 @@ void main() {
         TPopupConfig.create(
           child: const SizedBox(),
           placement: TPopupPlacement.bottom,
-          headerBuilder: (_) => const Text('h'),
+          headerBuilder: (_, __) => const Text('h'),
         ).hasBuiltInHeader,
         isTrue,
       );
@@ -112,6 +122,37 @@ void main() {
       );
     });
 
+    test('left/right 剥离 closeBuilder 与 onCloseBtn', () {
+      final left = TPopupConfig.create(
+        child: const SizedBox(),
+        placement: TPopupPlacement.left,
+        closeBuilder: (_, __) => const Text('x'),
+        onCloseBtn: () {},
+      );
+      expect(left.closeBuilder, isNull);
+      expect(left.onCloseBtn, isNull);
+    });
+
+    test('useCustomHeader 为 true 时 hasBuiltInHeader', () {
+      final config = TPopupConfig.create(
+        child: const SizedBox(),
+        placement: TPopupPlacement.bottom,
+        headerBuilder: (_, __) => const SizedBox(),
+      );
+      expect(config.useCustomHeader, isTrue);
+      expect(config.hasBuiltInHeader, isTrue);
+    });
+
+    test('assertPlacementParams 非 bottom 带 cancel 槽位提示', () {
+      final config = TPopupConfig(
+        child: const SizedBox(),
+        placement: TPopupPlacement.center,
+        cancel: kPopupActionDefault,
+        confirm: kPopupActionDefault,
+      );
+      expect(() => config.assertPlacementParams(), returnsNormally);
+    });
+
     test('assertPlacementParams 在 debug 模式不抛错', () {
       final config = TPopupConfig.create(
         child: const SizedBox(),
diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart
index c54165357..3b07c29f1 100644
--- a/tdesign-component/test/t_popup_coverage_test.dart
+++ b/tdesign-component/test/t_popup_coverage_test.dart
@@ -106,8 +106,8 @@ void main() {
             placement: TPopupPlacement.center,
             width: 160,
             height: 120,
-            closeBuilder: (ctx) => TextButton(
-              onPressed: () => TPopup.close(ctx),
+            closeBuilder: (_, close) => TextButton(
+              onPressed: close,
               child: const Text('builder关闭'),
             ),
             child: const SizedBox(height: 80, width: 120),
@@ -184,7 +184,7 @@ void main() {
             placement: TPopupPlacement.center,
             width: 100,
             height: 100,
-            closeBtn: false,
+            closeBuilder: null,
             child: const SizedBox(height: 80, width: 80),
           );
         },
@@ -286,6 +286,51 @@ void main() {
       expect(TPopupConfig.isActionDefault(null), isFalse);
     });
 
+    test('useCustomHeader 与 useTitleOnlyHeader 互斥于 useActionHeader', () {
+      final custom = TPopupConfig.create(
+        child: const SizedBox(),
+        placement: TPopupPlacement.bottom,
+        headerBuilder: (_, __) => const Text('h'),
+      );
+      expect(custom.useCustomHeader, isTrue);
+      expect(custom.useActionHeader, isFalse);
+
+      final titleOnly = TPopupConfig.create(
+        child: const SizedBox(),
+        placement: TPopupPlacement.bottom,
+        title: '仅标题',
+        cancel: null,
+        confirm: null,
+      );
+      expect(titleOnly.useTitleOnlyHeader, isTrue);
+      expect(titleOnly.useActionHeader, isFalse);
+    });
+
+    test('isPopupDefaultHeader / isPopupDefaultClose 哨兵识别', () {
+      expect(isPopupDefaultHeader(kPopupDefaultHeader), isTrue);
+      expect(isPopupDefaultHeader(null), isFalse);
+      expect(isPopupDefaultClose(kPopupDefaultClose), isTrue);
+      expect(isPopupDefaultClose(null), isFalse);
+    });
+
+    testWidgets('kPopupDefaultHeader / kPopupDefaultClose 占位函数可调用', (tester) async {
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Builder(
+            builder: (context) {
+              final header = kPopupDefaultHeader(
+                context,
+                const TPopupHeaderData(),
+              );
+              final close = kPopupDefaultClose(context, () {});
+              return Column(children: [header, close]);
+            },
+          ),
+        ),
+      );
+      expect(find.byType(SizedBox), findsWidgets);
+    });
+
     test('assertPlacementParams 覆盖 width 与 top 操作栏提示', () {
       expect(
         () => TPopupConfig.create(
@@ -308,13 +353,294 @@ void main() {
           child: const SizedBox(),
           placement: TPopupPlacement.center,
           height: 100,
-          closeBtn: false,
+          closeBuilder: null,
+        ).assertPlacementParams(),
+        returnsNormally,
+      );
+      expect(
+        () => TPopupConfig.create(
+          child: const SizedBox(),
+          placement: TPopupPlacement.center,
+          onCancel: () {},
         ).assertPlacementParams(),
         returnsNormally,
       );
     });
   });
 
+  group('TPopup 覆盖率深化', () {
+    testWidgets('headerBuilder 透传 titleWidget 与 cancelBuilder 槽位', (tester) async {
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.bottom,
+            height: 160,
+            titleWidget: const Text('头Widget'),
+            cancelBuilder: (_) => const Text('builder左'),
+            confirmBuilder: (_) => const Text('builder右'),
+            headerBuilder: (_, data) => Column(
+              children: [
+                if (data.title != null) data.title!,
+                Row(
+                  children: [
+                    if (data.cancel != null) data.cancel!,
+                    if (data.confirm != null) data.confirm!,
+                  ],
+                ),
+              ],
+            ),
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(find.text('头Widget'), findsOneWidget);
+      expect(find.text('builder左'), findsOneWidget);
+      expect(find.text('builder右'), findsOneWidget);
+    });
+
+    testWidgets('headerBuilder 使用自定义 cancel/confirm Widget 槽位', (tester) async {
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.bottom,
+            height: 160,
+            cancel: const Text('左槽Widget'),
+            confirm: const Text('右槽Widget'),
+            headerBuilder: (_, data) => Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: [
+                if (data.cancel != null) data.cancel!,
+                if (data.confirm != null) data.confirm!,
+              ],
+            ),
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(find.text('左槽Widget'), findsOneWidget);
+      expect(find.text('右槽Widget'), findsOneWidget);
+    });
+
+    testWidgets('操作栏使用自定义 cancel/confirm Widget(非 Builder)', (tester) async {
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.bottom,
+            height: 160,
+            cancel: const Text('自定义左'),
+            confirm: const Text('自定义右'),
+            onCancel: () {},
+            onConfirm: () {},
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(find.text('自定义左'), findsOneWidget);
+      expect(find.text('自定义右'), findsOneWidget);
+    });
+
+    testWidgets('onVisibleChange 记录各关闭触发源', (tester) async {
+      final hideTriggers = [];
+      late BuildContext hostContext;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          hostContext = tester.element(find.text('open'));
+          TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 160,
+            title: '标题',
+            onVisibleChange: (visible, trigger) {
+              if (!visible) {
+                hideTriggers.add(trigger);
+              }
+            },
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+
+      await tester.tap(find.text('取消'));
+      await tester.pumpAndSettle();
+      expect(hideTriggers.last, TPopupTrigger.cancelBtn);
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 160,
+            onVisibleChange: (visible, trigger) {
+              if (!visible) {
+                hideTriggers.add(trigger);
+              }
+            },
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      await tester.tapAt(const Offset(10, 10));
+      await tester.pumpAndSettle();
+      expect(hideTriggers.last, TPopupTrigger.overlay);
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.center,
+            width: 120,
+            height: 120,
+            onVisibleChange: (visible, trigger) {
+              if (!visible) {
+                hideTriggers.add(trigger);
+              }
+            },
+            child: const SizedBox(height: 80, width: 80),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      await tester.tap(find.byIcon(TIcons.close_circle));
+      await tester.pumpAndSettle();
+      expect(hideTriggers.last, TPopupTrigger.closeBtn);
+    });
+
+    testWidgets('Popup 内嵌套 show 可再开一层且先关内层', (tester) async {
+      late BuildContext outerContext;
+      late BuildContext innerContext;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          outerContext = tester.element(find.text('open'));
+          TPopup.show(
+            context: outerContext,
+            placement: TPopupPlacement.bottom,
+            height: 200,
+            cancel: null,
+            confirm: null,
+            child: Builder(
+              builder: (ctx) {
+                return ElevatedButton(
+                  onPressed: () {
+                    innerContext = ctx;
+                    TPopup.show(
+                      context: innerContext,
+                      placement: TPopupPlacement.bottom,
+                      height: 120,
+                      cancel: null,
+                      confirm: null,
+                      child: const Text('内层'),
+                    );
+                  },
+                  child: const Text('开内层'),
+                );
+              },
+            ),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(find.text('开内层'), findsOneWidget);
+
+      await tester.tap(find.text('开内层'));
+      await tester.pumpAndSettle();
+      expect(find.text('内层'), findsOneWidget);
+
+      TPopup.close(innerContext);
+      await tester.pumpAndSettle();
+      expect(find.text('内层'), findsNothing);
+      expect(find.text('开内层'), findsOneWidget);
+
+      TPopup.close(outerContext);
+      await tester.pumpAndSettle();
+    });
+
+    testWidgets('preventScrollThrough 为 false 仍可打开关闭', (tester) async {
+      late BuildContext hostContext;
+      await openPopup(
+        tester,
+        onPressed: () {
+          hostContext = tester.element(find.text('open'));
+          TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 120,
+            preventScrollThrough: false,
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      TPopup.close(hostContext);
+      await tester.pumpAndSettle();
+    });
+
+    testWidgets('center 自定义 radius 与 backgroundColor', (tester) async {
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.center,
+            width: 100,
+            height: 100,
+            radius: 4,
+            backgroundColor: Colors.red,
+            child: const SizedBox(height: 60, width: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      final hasRedPanel = tester
+          .widgetList(find.byType(Container))
+          .any((c) {
+        final d = c.decoration;
+        return d is BoxDecoration && d.color == Colors.red;
+      });
+      expect(hasRedPanel, isTrue);
+    });
+
+    testWidgets('bottom margin.bottom 与无 overlay 仍可关闭', (tester) async {
+      late BuildContext hostContext;
+      await openPopup(
+        tester,
+        onPressed: () {
+          hostContext = tester.element(find.text('open'));
+          TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 100,
+            margin: const EdgeInsets.only(bottom: 16),
+            showOverlay: false,
+            closeOnOverlayClick: false,
+            cancel: null,
+            confirm: null,
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      TPopup.close(hostContext);
+      await tester.pumpAndSettle();
+    });
+  });
+
   group('PopupLayout 覆盖率补充', () {
     const screen = Size(400, 800);
 
@@ -350,6 +676,27 @@ void main() {
       );
     });
 
+    testWidgets('right 默认 drawer 宽度与 margin', (tester) async {
+      final layout = PopupLayout(
+        placement: TPopupPlacement.right,
+        screenSize: screen,
+        margin: const EdgeInsets.only(top: 8, bottom: 8, right: 4),
+      );
+      await tester.pumpWidget(
+        MaterialApp(
+          home: Scaffold(
+            body: Stack(
+              children: [layout.wrapPositioned(child: const SizedBox())],
+            ),
+          ),
+        ),
+      );
+      final positioned = tester.widget(find.byType(Positioned));
+      expect(positioned.width, PopupLayout.defaultDrawerWidth);
+      expect(positioned.right, 4);
+      expect(positioned.top, 8);
+    });
+
     testWidgets('left 默认 drawer 宽度', (tester) async {
       final layout = PopupLayout(
         placement: TPopupPlacement.left,
diff --git a/tdesign-component/test/t_popup_route_test.dart b/tdesign-component/test/t_popup_route_test.dart
new file mode 100644
index 000000000..d26000c97
--- /dev/null
+++ b/tdesign-component/test/t_popup_route_test.dart
@@ -0,0 +1,128 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:tdesign_flutter/src/components/popup/_popup_route.dart';
+import 'package:tdesign_flutter/src/components/popup/t_popup_config.dart';
+import 'package:tdesign_flutter/tdesign_flutter.dart';
+
+import 'helpers/popup_test_helpers.dart';
+import 'helpers/popup_test_resource.dart';
+
+void main() {
+  tearDown(resetPopupTestResource);
+
+  group('TPopupNavigatorRoute', () {
+    testWidgets('buildPage 返回占位', (tester) async {
+      late TPopupNavigatorRoute route;
+      await tester.pumpWidget(
+        wrapPopupTest(
+          Builder(
+            builder: (context) {
+              route = TPopupNavigatorRoute(
+                config: TPopupConfig.create(
+                  child: const SizedBox(),
+                  placement: TPopupPlacement.bottom,
+                ),
+                onCloseWithTrigger: (_, [__]) {},
+              );
+              return route.buildPage(
+                context,
+                kAlwaysCompleteAnimation,
+                kAlwaysCompleteAnimation,
+              );
+            },
+          ),
+        ),
+      );
+      expect(find.byType(SizedBox), findsWidgets);
+    });
+
+    testWidgets('蒙层 ScrollNotification 被拦截', (tester) async {
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.bottom,
+            height: 120,
+            preventScrollThrough: true,
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+
+      var intercepted = false;
+      for (final element in tester.elementList(
+        find.byType(NotificationListener),
+      )) {
+        final widget =
+            element.widget as NotificationListener;
+        if (widget.onNotification?.call(
+              ScrollStartNotification(
+                metrics: FixedScrollMetrics(
+                  minScrollExtent: 0,
+                  maxScrollExtent: 100,
+                  pixels: 0,
+                  viewportDimension: 100,
+                  axisDirection: AxisDirection.down,
+                  devicePixelRatio: 1,
+                ),
+                context: element,
+              ),
+            ) ==
+            true) {
+          intercepted = true;
+          break;
+        }
+      }
+      expect(intercepted, isTrue);
+    });
+
+    testWidgets('无蒙层时透明层拦截滚动', (tester) async {
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.bottom,
+            height: 120,
+            showOverlay: false,
+            preventScrollThrough: true,
+            cancel: null,
+            confirm: null,
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(
+        find.byType(NotificationListener),
+        findsWidgets,
+      );
+    });
+
+    testWidgets('fireCloseStart 仅触发一次 onClose', (tester) async {
+      var closeCount = 0;
+      late BuildContext hostContext;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          hostContext = tester.element(find.text('open'));
+          TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 100,
+            onClose: () => closeCount++,
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      TPopup.close(hostContext);
+      await tester.pumpAndSettle();
+      expect(closeCount, 1);
+    });
+  });
+}
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index 051f71bcb..6dfe52a9e 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -304,6 +304,25 @@ void main() {
       expect(find.text('确定'), findsOneWidget);
     });
 
+    testWidgets('bottom headerBuilder null 无头部', (tester) async {
+      await openPopup(
+        tester,
+        onPressed: () {
+          TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.bottom,
+            height: 180,
+            title: '不应出现',
+            headerBuilder: null,
+            child: const SizedBox(height: 80),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(find.text('不应出现'), findsNothing);
+      expect(find.text('取消'), findsNothing);
+    });
+
     testWidgets('bottom 仅标题无操作栏', (tester) async {
       await openPopup(
         tester,
@@ -334,15 +353,14 @@ void main() {
             context: tester.element(find.text('open')),
             placement: TPopupPlacement.bottom,
             height: 160,
-            title: '被忽略',
-            headerBuilder: (_) => const Text('自定义头部'),
+            title: '传入标题',
+            headerBuilder: (_, data) => Text('自定义:${data.title}'),
             child: const SizedBox(height: 60),
           );
         },
       );
       await tester.pumpAndSettle();
-      expect(find.text('自定义头部'), findsOneWidget);
-      expect(find.text('被忽略'), findsNothing);
+      expect(find.textContaining('自定义'), findsOneWidget);
     });
 
     testWidgets('居中关闭在内容与下方', (tester) async {
@@ -384,7 +402,7 @@ void main() {
       expect(find.byIcon(TIcons.close_circle), findsOneWidget);
     });
 
-    testWidgets('center 默认显示下方关闭 closeBtn false 隐藏', (tester) async {
+    testWidgets('center 默认显示下方关闭 closeBuilder null 隐藏', (tester) async {
       await openPopup(
         tester,
         onPressed: () {
@@ -393,7 +411,7 @@ void main() {
             placement: TPopupPlacement.center,
             width: 120,
             height: 120,
-            closeBtn: false,
+            closeBuilder: null,
             child: const SizedBox(height: 80, width: 80),
           );
         },
@@ -565,7 +583,7 @@ void main() {
   });
 
   group('TPopupHandle / Tracker', () {
-    testWidgets('连续打开仅保留一个(第二次无效)', (tester) async {
+    testWidgets('外层重复 show 第二次无效(返回同一 handle)', (tester) async {
       TPopupHandle? first;
       await openPopup(
         tester,
@@ -584,7 +602,7 @@ void main() {
             child: const SizedBox(height: 40),
           );
           expect(second.isShowing, isTrue);
-          expect(identical(first, second), isFalse);
+          expect(identical(first, second), isTrue);
         },
       );
       await tester.pumpAndSettle();
@@ -692,7 +710,10 @@ void main() {
             placement: TPopupPlacement.center,
             width: 140,
             destroyOnClose: true,
-            close: const Text('关'),
+            closeBuilder: (_, close) => GestureDetector(
+              onTap: close,
+              child: const Text('关'),
+            ),
             onCloseBtn: () {},
             child: const SizedBox(height: 60, width: 120),
           );
@@ -725,7 +746,7 @@ void main() {
       expect(overlayClick, 1);
     });
 
-    testWidgets('TPopup.close 无 handle 时 maybePop', (tester) async {
+    testWidgets('TPopup.close 无 Popup 时不 pop 当前页', (tester) async {
       await tester.pumpWidget(
         MaterialApp(
           home: TTheme(
@@ -745,6 +766,7 @@ void main() {
       );
       await tester.tap(find.text('close'));
       await tester.pump();
+      expect(find.text('close'), findsOneWidget);
     });
 
     testWidgets('show 返回的 handle 关闭后 isShowing 为 false', (tester) async {
@@ -768,6 +790,106 @@ void main() {
     });
   });
 
+  group('TPopup 触发源与配置', () {
+    testWidgets('handle.close 触发 programmatic', (tester) async {
+      TPopupTrigger? hideTrigger;
+      TPopupHandle? handle;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          handle = TPopup.show(
+            context: tester.element(find.text('open')),
+            placement: TPopupPlacement.bottom,
+            height: 100,
+            onVisibleChange: (v, t) {
+              if (!v) {
+                hideTrigger = t;
+              }
+            },
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      handle!.close();
+      await tester.pumpAndSettle();
+      expect(hideTrigger, TPopupTrigger.programmatic);
+    });
+
+    testWidgets('confirm 点击触发 confirmBtn', (tester) async {
+      TPopupTrigger? hideTrigger;
+      late BuildContext hostContext;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          hostContext = tester.element(find.text('open'));
+          TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 120,
+            onVisibleChange: (v, t) {
+              if (!v) {
+                hideTrigger = t;
+              }
+            },
+            child: const SizedBox(height: 60),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      await tester.tap(find.text('确定'));
+      await tester.pumpAndSettle();
+      expect(hideTrigger, TPopupTrigger.confirmBtn);
+    });
+
+    testWidgets('destroyOnClose 路由关闭后可再次 show', (tester) async {
+      late BuildContext hostContext;
+      TPopupHandle? first;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          hostContext = tester.element(find.text('open'));
+          first = TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 80,
+            destroyOnClose: true,
+            cancel: null,
+            confirm: null,
+            child: const SizedBox(height: 40),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      first!.close();
+      await tester.pumpAndSettle();
+      expect(first!.isShowing, isFalse);
+
+      TPopupHandle? second;
+      await openPopup(
+        tester,
+        onPressed: () {
+          second = TPopup.show(
+            context: hostContext,
+            placement: TPopupPlacement.bottom,
+            height: 80,
+            destroyOnClose: true,
+            cancel: null,
+            confirm: null,
+            child: const SizedBox(height: 40),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(second!.isShowing, isTrue);
+      second!.close();
+      await tester.pumpAndSettle();
+    });
+  });
+
   group('TPopup 自定义控件', () {
     testWidgets('自定义 cancel / confirm / close 组件', (tester) async {
       await openPopup(

From 6bd530ba26b95c372d12aeef2b5909d66bcf0ad7 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Thu, 21 May 2026 19:50:41 +0000
Subject: [PATCH 04/27] [autofix.ci] apply automated fixes

---
 .../assets/code/popup._buildNestedPopup.txt   |  63 ++++++++
 .../assets/code/popup._buildPopFromBottom.txt |   1 +
 .../popup._buildPopFromCenterWithClose.txt    |   4 +-
 ...opup._buildPopFromCenterWithUnderClose.txt |   4 +-
 tdesign-site/src/popup/README.md              | 143 +++++++++++++++++-
 5 files changed, 206 insertions(+), 9 deletions(-)
 create mode 100644 tdesign-component/example/assets/code/popup._buildNestedPopup.txt

diff --git a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt
new file mode 100644
index 000000000..9a6f3e30f
--- /dev/null
+++ b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt
@@ -0,0 +1,63 @@
+
+  Widget _buildNestedPopup(BuildContext context) {
+    return TButton(
+      text: '内层再弹一层(嵌套叠加)',
+      isBlock: true,
+      theme: TButtonTheme.primary,
+      type: TButtonType.outline,
+      size: TButtonSize.large,
+      onTap: () {
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 360,
+          headerBuilder: null,
+          child: Builder(
+            builder: (innerContext) {
+              return Padding(
+                padding: const EdgeInsets.all(16),
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.stretch,
+                  children: [
+                    TText(
+                      '外层:headerBuilder: null,仅 child',
+                      textColor: TTheme.of(innerContext).textColorSecondary,
+                    ),
+                    const SizedBox(height: 16),
+                    TButton(
+                      text: '打开内层 Popup',
+                      isBlock: true,
+                      theme: TButtonTheme.primary,
+                      size: TButtonSize.large,
+                      onTap: () {
+                        TPopup.show(
+                          context: innerContext,
+                          placement: TPopupPlacement.bottom,
+                          height: 280,
+                          title: '内层标题',
+                          onCancel: () => TPopup.close(innerContext),
+                          onConfirm: () => TPopup.close(innerContext),
+                          child: Container(
+                            height: 160,
+                            color: TTheme.of(innerContext).bgColorSecondaryContainer,
+                          ),
+                        );
+                      },
+                    ),
+                    const SizedBox(height: 12),
+                    TButton(
+                      text: '关闭外层',
+                      isBlock: true,
+                      type: TButtonType.outline,
+                      size: TButtonSize.large,
+                      onTap: () => TPopup.close(innerContext),
+                    ),
+                  ],
+                ),
+              );
+            },
+          ),
+        );
+      },
+    );
+  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt
index 850617fca..0669277c1 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt
@@ -11,6 +11,7 @@
           context: context,
           placement: TPopupPlacement.bottom,
           height: 240,
+          headerBuilder: null,
           child: Container(
             color: TTheme.of(context).bgColorContainer,
             height: 240,
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt
index 3e6bd6907..91fd4f1f0 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt
@@ -13,13 +13,13 @@
           closeOnOverlayClick: false,
           width: 240,
           height: 240,
-          close: IconButton(
+          closeBuilder: (_, close) => IconButton(
             icon: Icon(
               TIcons.close_circle,
               color: TTheme.of(context).fontWhColor1,
               size: 32,
             ),
-            onPressed: () => TPopup.close(context),
+            onPressed: close,
           ),
           child: const SizedBox(width: 240, height: 240),
         );
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt
index 255768cba..5fabf5187 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt
@@ -13,13 +13,13 @@
           closeOnOverlayClick: true,
           width: 240,
           height: 200,
-          close: IconButton(
+          closeBuilder: (_, close) => IconButton(
             icon: Icon(
               TIcons.poweroff,
               color: TTheme.of(context).fontWhColor1,
               size: 36,
             ),
-            onPressed: () => TPopup.close(context),
+            onPressed: close,
           ),
           child: Container(
             width: 240,
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 15a631a13..f95ff8abf 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -95,7 +95,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
         TPopup.show(
           context: context,
           placement: TPopupPlacement.center,
-          closeBtn: false,
+          closeBuilder: null,
           child: Container(
             decoration: BoxDecoration(
               color: TTheme.of(context).bgColorContainer,
@@ -130,6 +130,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           context: context,
           placement: TPopupPlacement.bottom,
           height: 240,
+          headerBuilder: null,
           child: Container(
             color: TTheme.of(context).bgColorContainer,
             height: 240,
@@ -274,13 +275,13 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           closeOnOverlayClick: false,
           width: 240,
           height: 240,
-          close: IconButton(
+          closeBuilder: (_, close) => IconButton(
             icon: Icon(
               TIcons.close_circle,
               color: TTheme.of(context).fontWhColor1,
               size: 32,
             ),
-            onPressed: () => TPopup.close(context),
+            onPressed: close,
           ),
           child: const SizedBox(width: 240, height: 240),
         );
@@ -310,13 +311,13 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           closeOnOverlayClick: true,
           width: 240,
           height: 200,
-          close: IconButton(
+          closeBuilder: (_, close) => IconButton(
             icon: Icon(
               TIcons.poweroff,
               color: TTheme.of(context).fontWhColor1,
               size: 36,
             ),
-            onPressed: () => TPopup.close(context),
+            onPressed: close,
           ),
           child: Container(
             width: 240,
@@ -330,6 +331,77 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 
                                   
+
+
+            
+
+
+  
+  Widget _buildNestedPopup(BuildContext context) {
+    return TButton(
+      text: '内层再弹一层(嵌套叠加)',
+      isBlock: true,
+      theme: TButtonTheme.primary,
+      type: TButtonType.outline,
+      size: TButtonSize.large,
+      onTap: () {
+        TPopup.show(
+          context: context,
+          placement: TPopupPlacement.bottom,
+          height: 360,
+          headerBuilder: null,
+          child: Builder(
+            builder: (innerContext) {
+              return Padding(
+                padding: const EdgeInsets.all(16),
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.stretch,
+                  children: [
+                    TText(
+                      '外层:headerBuilder: null,仅 child',
+                      textColor: TTheme.of(innerContext).textColorSecondary,
+                    ),
+                    const SizedBox(height: 16),
+                    TButton(
+                      text: '打开内层 Popup',
+                      isBlock: true,
+                      theme: TButtonTheme.primary,
+                      size: TButtonSize.large,
+                      onTap: () {
+                        TPopup.show(
+                          context: innerContext,
+                          placement: TPopupPlacement.bottom,
+                          height: 280,
+                          title: '内层标题',
+                          onCancel: () => TPopup.close(innerContext),
+                          onConfirm: () => TPopup.close(innerContext),
+                          child: Container(
+                            height: 160,
+                            color: TTheme.of(innerContext).bgColorSecondaryContainer,
+                          ),
+                        );
+                      },
+                    ),
+                    const SizedBox(height: 12),
+                    TButton(
+                      text: '关闭外层',
+                      isBlock: true,
+                      type: TButtonType.outline,
+                      size: TButtonSize.large,
+                      onTap: () => TPopup.close(innerContext),
+                    ),
+                  ],
+                ),
+              );
+            },
+          ),
+        );
+      },
+    );
+  }
+ +
+ ### 1 更多 API @@ -462,5 +534,66 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ## API +### TPopup +#### 简介 +弹出层:支持五向滑入/居中弹出、蒙层、bottom 操作栏与 center 关闭区。 + + 命令式用法优先调用 [show];声明式将 [TPopup] 包裹业务子树并设 [initialVisible](弹层在独立路由中,[build] 仅渲染 [child])。 + bottom 操作栏参数仅对 [TPopupPlacement.bottom] 生效;center 关闭参数仅对 center 生效; + top/left/right 仅使用 [child] 与布局参数。 + 嵌套时 [close] 只关栈顶 Popup;无 Popup 时不操作当前页。 +#### 默认构造方法 + +| 参数 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| autoCloseOnCancel | bool | true | 点击取消后是否自动关闭,默认 true。 | +| autoCloseOnConfirm | bool | true | 点击确定后是否自动关闭,默认 true。 | +| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 | +| cancel | Widget? | kPopupActionDefault | bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。 | +| cancelBtn | String? | - | bottom 左侧按钮文案,覆盖默认「取消」。 | +| cancelBuilder | WidgetBuilder? | - | bottom 左侧按钮构建器,优先级高于 [cancel]。 | +| child | Widget | - | 浮层主体内容(必填)。 | +| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose] 默认圆圈图标; | +| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 | +| confirm | Widget? | kPopupActionDefault | bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 | +| confirmBtn | String? | - | bottom 右侧按钮文案,覆盖默认「确定」。 | +| confirmBuilder | WidgetBuilder? | - | bottom 右侧按钮构建器,优先级高于 [confirm]。 | +| destroyOnClose | bool | false | 为 true 时 Popup 路由 [Route.maintainState] 为 false,关闭后不保留路由内 State; | +| duration | Duration | const Duration(milliseconds: 240) | 打开与关闭动画时长(一致)。 | +| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader] 默认操作栏;自定义见 [TPopupHeaderBuilder]。 | +| height | double? | - | 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 | +| initialVisible | bool | false | 声明式:为 true 时在首帧后自动 [show]。 | +| key | | - | | +| margin | EdgeInsets? | - | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 | +| navigatorContext | BuildContext? | - | 指定 Navigator 的 context,默认使用当前 context。 | +| onCancel | VoidCallback? | - | 点击 bottom 左侧按钮回调。 | +| onClose | VoidCallback? | - | 开始关闭时回调(含蒙层、按钮、程序化关闭)。 | +| onCloseBtn | VoidCallback? | - | center 点击关闭控件前的回调。 | +| onClosed | VoidCallback? | - | 关闭动画结束且路由移除后回调。 | +| onConfirm | VoidCallback? | - | 点击 bottom 右侧按钮回调。 | +| onOpen | VoidCallback? | - | 开始打开时回调(路由入栈)。 | +| onOpened | VoidCallback? | - | 打开动画结束后回调。 | +| onOverlayClick | VoidCallback? | - | 点击蒙层时回调(在是否关闭判断之前)。 | +| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化及触发来源。 | +| overlayColor | Color? | - | 蒙层颜色,默认 black54。 | +| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 | +| placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 | +| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 | +| radius | double? | - | 内容区圆角,默认主题大圆角。 | +| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 | +| title | String? | - | bottom 操作栏中间标题文案。 | +| titleAlignLeft | bool | false | bottom 仅标题行时是否左对齐,默认居中。 | +| titleWidget | Widget? | - | bottom 操作栏中间标题组件,优先级高于 [title]。 | +| useRootNavigator | bool | false | 是否使用根 Navigator。 | +| width | double? | - | 宽度;对 left、right、center 生效。 | + + +#### 静态方法 + +| 名称 | 返回类型 | 参数 | 说明 | +| --- | --- | --- | --- | +| close | | required BuildContext context, Object? result, | 关闭当前 Navigator 栈顶 [TPopup]。 仅关闭 Tracker 栈顶展示中的 Popup;无 Popup 时不操作(不会 pop 当前页)。 | +| show | | required BuildContext context, required Widget child, TPopupPlacement placement, double? width, double? height, EdgeInsets? margin, double? radius, Color? backgroundColor, bool showOverlay, bool closeOnOverlayClick, Color? overlayColor, double? overlayOpacity, bool preventScrollThrough, bool destroyOnClose, Duration duration, String? title, Widget? titleWidget, bool titleAlignLeft, String? cancelBtn, Widget? cancel, WidgetBuilder? cancelBuilder, VoidCallback? onCancel, String? confirmBtn, Widget? confirm, WidgetBuilder? confirmBuilder, VoidCallback? onConfirm, bool autoCloseOnCancel, bool autoCloseOnConfirm, TPopupCloseBuilder? closeBuilder, VoidCallback? onCloseBtn, TPopupHeaderBuilder? headerBuilder, VoidCallback? onOpen, VoidCallback? onOpened, VoidCallback? onClose, VoidCallback? onClosed, TPopupVisibleChangeCallback? onVisibleChange, VoidCallback? onOverlayClick, BuildContext? navigatorContext, bool useRootNavigator, | 命令式打开浮层,参数与 [TPopup] 构造器一致。 返回 [TPopupHandle];优先 [TPopupHandle.close],或在 Popup 子树内 [close]。 [cancel]/[confirm] 默认 [kPopupActionDefault] 表示默认文案,显式 null 可隐藏操作栏侧。 [closeBuilder] 未传为 [kPopupDefaultClose](默认关闭图标),显式 null 不显示关闭区。 | + \ No newline at end of file From e16e6fad827af7ed4f8a09eed8d84cdc42bbc79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Fri, 22 May 2026 04:46:03 +0800 Subject: [PATCH 05/27] refactor(popup): update TPopup implementation and remove deprecated documentation --- tdesign-component/demo_tool/all_build.sh | 4 +- tdesign-component/doc/popup_api.md | 300 -------- .../example/assets/api/popup_api.md | 101 ++- .../lib/component_test/popup_test.dart | 33 +- .../example/lib/page/t_indexes_page.dart | 120 ++-- .../example/lib/page/t_picker_page.dart | 135 ++-- .../example/lib/page/t_popup_page.dart | 646 +++++++++-------- .../action_sheet/t_action_sheet.dart | 26 +- .../components/calendar/t_calendar_popup.dart | 43 +- .../lib/src/components/drawer/t_drawer.dart | 49 +- .../components/popup/_popup_center_close.dart | 22 +- .../src/components/popup/_popup_header.dart | 115 ++- .../src/components/popup/_popup_layout.dart | 1 + .../src/components/popup/_popup_route.dart | 73 +- .../src/components/popup/_popup_shell.dart | 38 +- .../lib/src/components/popup/t_popup.dart | 339 ++------- .../src/components/popup/t_popup_handle.dart | 17 +- ...popup_config.dart => t_popup_options.dart} | 236 ++++--- .../src/components/popup/t_popup_tracker.dart | 2 +- .../src/components/popup/t_popup_types.dart | 65 +- .../test/t_bottom_tab_bar_test.dart | 42 +- tdesign-component/test/t_picker_test.dart | 121 ++-- .../test/t_popup_coverage_test.dart | 484 +++++++------ .../test/t_popup_layout_test.dart | 21 +- ...ig_test.dart => t_popup_options_test.dart} | 117 ++- .../test/t_popup_route_test.dart | 56 +- tdesign-component/test/t_popup_test.dart | 665 +++++++++--------- 27 files changed, 1734 insertions(+), 2137 deletions(-) delete mode 100644 tdesign-component/doc/popup_api.md rename tdesign-component/lib/src/components/popup/{t_popup_config.dart => t_popup_options.dart} (60%) rename tdesign-component/test/{t_popup_config_test.dart => t_popup_options_test.dart} (55%) diff --git a/tdesign-component/demo_tool/all_build.sh b/tdesign-component/demo_tool/all_build.sh index 5a24b520e..67e6d86ad 100644 --- a/tdesign-component/demo_tool/all_build.sh +++ b/tdesign-component/demo_tool/all_build.sh @@ -128,8 +128,8 @@ dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/compo # overlay # popover dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popover" --name TPopover,TPopoverWidget --folder-name popover --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments -# popup -dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popup" --name TPopup,TPopupPlacement,TPopupTrigger --folder-name popup --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments +# popup(API 由源码 /// 生成 example/assets/api/popup_api.md;Handle 见 TPopup.show 返回值说明) +dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popup" --name TPopup,TPopupOptions,TPopupHeaderData,TPopupPlacement,TPopupTrigger --folder-name popup --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments # refresh dart run tdesign_flutter_tools:main generate --file "$PARENT_DIR/lib/src/components/refresh/t_refresh_header.dart" --name TRefreshHeader --folder-name pull-down-refresh --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments # swipecell diff --git a/tdesign-component/doc/popup_api.md b/tdesign-component/doc/popup_api.md deleted file mode 100644 index 25c86ee2f..000000000 --- a/tdesign-component/doc/popup_api.md +++ /dev/null @@ -1,300 +0,0 @@ -# TPopup API 文档 - -## 简介 - -由其他控件触发,从屏幕边缘或中部滑出/弹出一块自定义内容区域。 - -**对外类:** `TPopup`、`TPopupHandle` -**类型:** `TPopupPlacement`、`TPopupTrigger` - -```dart -import 'package:tdesign_flutter/tdesign_flutter.dart'; -``` - ---- - -## 设计原则(按 placement) - -| placement | 内置头部 / 关闭 | 自定义入口 | -|-----------|-----------------|------------| -| **bottom** | 操作栏:**取消 \| 标题 \| 确认**(默认渲染) | `cancel`/`confirm` 传 `null` 隐藏对应侧;`cancelBuilder` / `confirmBuilder`;`headerBuilder` 整块覆盖 | -| **top / left / right** | **无**内置头部 / 按钮区 | **仅 `child`** | -| **center** | **内置**内容面板外下方关闭按钮 | `closeBuilder: null` 隐藏;未传 / 自定义 `closeBuilder` | - -**硬性约定:** - -- **bottom 不使用** center 关闭参数(`closeBuilder` 等)。 -- **center 不使用**底部操作栏(`onCancel` / `onConfirm` 等仅对 bottom 生效)。 -- **top / left / right 不使用** `title`、`headerBuilder` 及一切头部参数(面板内需要的标题请做在 `child` 里)。 - ---- - -## placement 一览 - -### bottom(底部) - -| 项 | 说明 | -|----|------| -| 动画 | 自下而上滑入 | -| 尺寸 | `height` 生效;`width` 忽略(横向铺满,`margin` 控制左右) | -| margin | `top / left / right / bottom`;`margin.top` 可做日历式距顶留白 | -| 圆角 | 仅上方两角 | - -**头部(操作栏)** - -`placement == bottom` 且未使用 `headerBuilder` 时 **默认渲染** 三栏(取消 \| 标题 \| 确认)。`onCancel` / `onConfirm` 可选;未传时点击仍默认关闭(`autoCloseOnCancel` / `autoCloseOnConfirm` 默认为 `true`)。 - -| 区域 | 默认 | 自定义 / 隐藏 | -|------|------|----------------| -| 左 | 文案「取消」 | `cancel` 自定义 Widget;**`cancel: null` 隐藏左侧** | -| 中 | `title` / `titleWidget`(可为空) | — | -| 右 | 文案「确定」 | `confirm` 自定义 Widget;**`confirm: null` 隐藏右侧** | - -**无操作栏:** `cancel: null` 且 `confirm: null`(且无 `cancelBuilder` / `confirmBuilder`),例如 ActionSheet、Picker 自带工具栏的场景。 - -> `cancel` / `confirm` 参数默认值为内部占位 `kPopupActionDefault`(未传即默认文案);与「未传参」不同,须显式写 `cancel: null` 才能隐藏。 - -**无头部:** `headerBuilder: null`(与未传 `kPopupDefaultHeader` 不同)。 - -**整块替换头部:** 自定义 `headerBuilder`(优先级最高,操作栏参数均不生效)。 - -```dart -TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 320, - title: '选择日期', - onCancel: () => TPopup.close(context), - onConfirm: () { - TPopup.close(context); - }, - child: calendarBody, -); -``` - ---- - -### top(顶部) - -| 项 | 说明 | -|----|------| -| 动画 | 自上而下滑入 | -| 尺寸 | `height` 生效;`width` 忽略 | -| margin | `top / left / right` | -| 头部 | **无**(不使用 `title` / `headerBuilder`) | - ---- - -### left / right(侧栏) - -| 项 | left | right | -|----|------|-------| -| 动画 | 自左滑入 | 自右滑入 | -| 尺寸 | `width` 生效(默认 280);`height` 忽略 | 同左 | -| margin | `left / top / bottom` | `right / top / bottom` | -| 头部 | **无**(仅 `child`) | 同左 | - ---- - -### center(居中) - -| 项 | 说明 | -|----|------| -| 动画 | 缩放(非位移) | -| 尺寸 | `width`、`height` 约束**白色内容区**;有关闭区时按钮在面板外下方额外占位 | -| margin | 不参与定位 | - -**关闭区(三态 `closeBuilder`)** - -| 写法 | 含义 | -|------|------| -| 不传 `closeBuilder` | 默认 `kPopupDefaultClose` → 显示内置圆圈关闭图标 | -| `closeBuilder: null` | **不显示**关闭区 | -| 自定义 `closeBuilder: (ctx, close) => ...` | 自定义关闭控件;应调用 `close` 关闭(会触发 `onCloseBtn`) | - -| 参数 | 默认 | 说明 | -|------|------|------| -| `onCloseBtn` | null | 点击关闭控件时回调(在自动关闭前) | - -**内置布局(center 且 `closeBuilder` 非 null,无对外开关):** - -```text -[上间距 40] -[白底内容面板 ← width × height] -[间距 24] -[关闭按钮 ← 蒙层上,非面板内] -``` - -> 不提供「面板右上角 X」;若需要角标关闭,请在 `child` 内自建。 - -```dart -TPopup.show( - context: context, - placement: TPopupPlacement.center, - width: 280, - height: 200, - closeBuilder: (ctx, close) => IconButton( - icon: Icon(TIcons.close_circle, color: TTheme.of(ctx).fontWhColor1), - onPressed: close, - ), - child: dialogBody, -); - -// 无关闭区 -TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeBuilder: null, - child: alertBody, -); -``` - ---- - -## 生命周期回调顺序 - -| 阶段 | 回调 | 说明 | -|------|------|------| -| 路由入栈 | `onOpen` | `didPush` 时 | -| 变为可见 | `onVisibleChange(true, programmatic)` | 与打开方式无关,均为 `programmatic` | -| 打开动画结束 | `onOpened` | 动画 `completed` | -| 开始关闭 | `onClose` + `onVisibleChange(false, trigger)` | `trigger` 见 `TPopupTrigger` | -| 关闭动画结束 | `onClosed` | 路由移除且动画 `dismissed` | - -关闭触发:`overlay` / `cancelBtn` / `confirmBtn` / `closeBtn` / `programmatic`(含系统返回、`handle.close`、`TPopup.close`)。 - ---- - -## Widget 与 Builder 怎么选 - -| 类型 | 签名 | 何时创建 | 适用 | -|------|------|----------|------| -| **Widget** | `Widget? cancel` | 调用 `show` 时 | 完全静态 | -| **Builder** | `WidgetBuilder? cancelBuilder` | Popup `build` 时 | **推荐**:主题、国际化 | - -**优先级(同一槽位):** - -```text -xxxBuilder > xxx (Widget) > 内置默认 UI -``` - -| 槽位 | Builder(推荐) | Widget | -|------|-----------------|--------| -| 底部取消 | `cancelBuilder` | `cancel` | -| 底部确认 | `confirmBuilder` | `confirm` | -| 居中关闭 | `closeBuilder(ctx, close)` | — | -| 整块头部(仅 bottom) | `headerBuilder` | — | - ---- - -## TPopup.show(命令式) - -返回 [TPopupHandle];优先使用 `handle.close()`,或在 Popup 子树内使用 `TPopup.close(context)`。 - -`cancel` / `confirm` 默认 `kPopupActionDefault` 表示默认文案;显式 `null` 隐藏对应侧。 - -### 关闭行为 - -- **`TPopup.close(context)`**:仅关闭当前 Navigator 上 Tracker **栈顶**且正在展示的 Popup;无 Popup 时**不操作**(不会 `maybePop` 当前页)。 -- **嵌套 Popup**:每次 `close` 只关最上层;`navigatorContext` / `useRootNavigator` 须与 `show` 一致。 -- **外层重复 `show`**:同一按钮在页面 context 上连点第二次无效(返回同一 handle);Popup 路由内可再 `show` 嵌套一层。 - -### 动画 - -`duration` 同时用于打开与关闭过渡(默认 240ms)。 - ---- - -## 参数表(摘要) - -### 通用 - -| 名称 | 类型 | 默认值 | 描述 | -|------|------|--------|------| -| child | Widget | - | 浮层主体(必填) | -| placement | TPopupPlacement | bottom | top / left / right / bottom / center | -| width | double? | - | left / right / center | -| height | double? | - | top / bottom;center 有关闭区时约束白底内容区 | -| margin | EdgeInsets? | zero | center 忽略 | -| showOverlay | bool | true | 半透明遮罩 | -| closeOnOverlayClick | bool | true | 点击蒙层是否关闭 | -| overlayColor | Color? | black54 | 蒙层颜色 | -| overlayOpacity | double? | - | 与 `overlayColor` 的 alpha 相乘 | -| preventScrollThrough | bool | true | 拦截底层滚动 | -| destroyOnClose | bool | false | Popup 路由 `maintainState = false`;不销毁声明式 `TPopup` 的 State | -| duration | Duration | 240ms | 开、关动画时长一致 | -| onOpen / onOpened / onClose / onClosed | VoidCallback? | - | 生命周期 | -| onVisibleChange | (bool, TPopupTrigger)? | - | 显隐及触发来源 | - -### bottom 专用 - -`title`、`cancel`/`confirm`、`cancelBuilder`/`confirmBuilder`、`headerBuilder`(`null` 无头,未传默认操作栏)。 - -### center 专用 - -`closeBuilder`(三态)、`onCloseBtn`。 - ---- - -## Header / 关闭 优先级 - -**bottom:** - -```text -headerBuilder: null → 无头部 -未传 headerBuilder → 默认操作栏 / 仅标题行 -自定义 headerBuilder → 整块自定义 -``` - -**center:** - -```text -closeBuilder: null → 仅内容区 -未传 closeBuilder → 默认面板外下方关闭(kPopupDefaultClose) -自定义 closeBuilder → 自定义控件,仍在面板外下方槽位 -``` - ---- - -## TPopupHandle - -```dart -void close([Object? result]); -bool get isShowing; -``` - ---- - -## 声明式 TPopup - -```dart -TPopup( - initialVisible: false, - placement: TPopupPlacement.bottom, - child: ..., - // 参数与 show 一致 -) -``` - -`build` 仅渲染 `child`;弹层在独立路由中。不支持受控 `visible`。 - ---- - -## 迁移对照 - -| 旧 API | 新 API | -|--------|--------| -| TSlidePopupRoute | TPopup.show | -| SlideTransitionFrom | TPopupPlacement | -| TPopupBottomConfirmPanel | onCancel / onConfirm + title | -| TPopupCenterPanel | placement: center + closeBuilder 三态 | -| modalTop | margin.top(bottom) | - ---- - -## 备注 - -- 不提供键盘避让、`zIndex`、拖拽半屏、受控 `visible`。 -- 国际化:操作栏默认文案来自 `context.resource`。 -- 无障碍:蒙层语义标签;bottom 操作栏与 center 关闭带 `Semantics(button: true)`。 -- API 文档生成:源码 `///` 为唯一真相;运行 `demo_tool/all_build.sh` 中 popup 段生成 `example/assets/api/popup_api.md`。 diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md index 9c455d425..91cf76640 100644 --- a/tdesign-component/example/assets/api/popup_api.md +++ b/tdesign-component/example/assets/api/popup_api.md @@ -1,12 +1,70 @@ ## API ### TPopup #### 简介 -弹出层:支持五向滑入/居中弹出、蒙层、bottom 操作栏与 center 关闭区。 +弹出层:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 - 命令式用法优先调用 [show];声明式将 [TPopup] 包裹业务子树并设 [initialVisible](弹层在独立路由中,[build] 仅渲染 [child])。 - bottom 操作栏参数仅对 [TPopupPlacement.bottom] 生效;center 关闭参数仅对 center 生效; - top/left/right 仅使用 [child] 与布局参数。 - 嵌套时 [close] 只关栈顶 Popup;无 Popup 时不操作当前页。 + ## 怎么用 + + **命令式(推荐)** — 先组配置,再 `show`,用返回的 [TPopupHandle] 关闭: + + ```dart + final handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + title: '标题', + child: MyPanel(), + ), + ).show(context); + + // 关闭这一层(须保留 handle,不要用 context 猜栈顶) + handle.close(); + ``` + + **声明式** — 包住子树,`initialVisible: true` 时首帧自动 [show];[build] 只渲染 [options.child]: + + ```dart + TPopup( + options: TPopupOptions(child: body), + initialVisible: true, + ) + ``` + + 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 + [TPopupOptions.normalized] 中裁掉)。 +#### 默认构造方法 + +| 参数 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| initialVisible | bool | false | 为 true 时,挂载后首帧自动调用 [show](仅声明式)。 | +| key | | - | | +| navigatorContext | BuildContext? | - | 指定使用哪个 [Navigator];默认 [show] 传入的 `context` 所在 Navigator。 | +| options | TPopupOptions | - | 浮层内容与行为配置,见 [TPopupOptions]。 | +| useRootNavigator | bool | false | 为 true 时使用根 [Navigator](嵌套导航场景)。 | + +``` +``` + +### TPopupOptions +#### 简介 +浮层配置:[TPopup] 构造与 [TPopup.show] 的唯一参数来源。 + + ## 按 [placement] 用哪些字段 + + | placement | 常用字段 | + |-----------|----------| + | [TPopupPlacement.bottom] | `title` / `cancel` / `confirm` / `headerBuilder`、`height`、`margin` | + | [TPopupPlacement.center] | `closeBuilder`、`width`、`height`(有下方关闭时) | + | [TPopupPlacement.top] / [left] / [right] | 主要 `child`、`margin`、方向对应 `width` 或 `height` | + + 传给其它 placement 的 bottom / center 专用字段会在 [normalized] 里裁掉。 + + ## 三态占位(bottom / center) + + - **未传参数**:使用默认 UI(如默认取消/确定文案、默认关闭图标)。 + - **显式 `null`**:隐藏该槽位(如 `cancel: null` 隐藏左侧;`closeBuilder: null` 无关闭按钮)。 + - **自定义 Widget / Builder**:完全自定义该区域。 + + [TPopup.show] 内部会先 [normalized] 再绘制。 #### 默认构造方法 | 参数 | 类型 | 默认值 | 说明 | @@ -18,21 +76,18 @@ | cancelBtn | String? | - | bottom 左侧按钮文案,覆盖默认「取消」。 | | cancelBuilder | WidgetBuilder? | - | bottom 左侧按钮构建器,优先级高于 [cancel]。 | | child | Widget | - | 浮层主体内容(必填)。 | -| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose] 默认圆圈图标; | +| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];bottom 与三边忽略。 | | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 | | confirm | Widget? | kPopupActionDefault | bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 | | confirmBtn | String? | - | bottom 右侧按钮文案,覆盖默认「确定」。 | | confirmBuilder | WidgetBuilder? | - | bottom 右侧按钮构建器,优先级高于 [confirm]。 | -| destroyOnClose | bool | false | 为 true 时 Popup 路由 [Route.maintainState] 为 false,关闭后不保留路由内 State; | +| destroyOnClose | bool | false | 为 true 时 Popup 路由 maintainState 为 false,关闭后不保留路由内 State。 | | duration | Duration | const Duration(milliseconds: 240) | 打开与关闭动画时长(一致)。 | -| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader] 默认操作栏;自定义见 [TPopupHeaderBuilder]。 | +| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader];自定义见 [TPopupHeaderBuilder]。 | | height | double? | - | 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 | -| initialVisible | bool | false | 声明式:为 true 时在首帧后自动 [show]。 | -| key | | - | | -| margin | EdgeInsets? | - | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 | -| navigatorContext | BuildContext? | - | 指定 Navigator 的 context,默认使用当前 context。 | +| margin | EdgeInsets | EdgeInsets.zero | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 | | onCancel | VoidCallback? | - | 点击 bottom 左侧按钮回调。 | -| onClose | VoidCallback? | - | 开始关闭时回调(含蒙层、按钮、程序化关闭)。 | +| onClose | VoidCallback? | - | 开始关闭时回调。 | | onCloseBtn | VoidCallback? | - | center 点击关闭控件前的回调。 | | onClosed | VoidCallback? | - | 关闭动画结束且路由移除后回调。 | | onConfirm | VoidCallback? | - | 点击 bottom 右侧按钮回调。 | @@ -49,7 +104,6 @@ | title | String? | - | bottom 操作栏中间标题文案。 | | titleAlignLeft | bool | false | bottom 仅标题行时是否左对齐,默认居中。 | | titleWidget | Widget? | - | bottom 操作栏中间标题组件,优先级高于 [title]。 | -| useRootNavigator | bool | false | 是否使用根 Navigator。 | | width | double? | - | 宽度;对 left、right、center 生效。 | @@ -57,5 +111,20 @@ | 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | -| close | | required BuildContext context, Object? result, | 关闭当前 Navigator 栈顶 [TPopup]。 仅关闭 Tracker 栈顶展示中的 Popup;无 Popup 时不操作(不会 pop 当前页)。 | -| show | | required BuildContext context, required Widget child, TPopupPlacement placement, double? width, double? height, EdgeInsets? margin, double? radius, Color? backgroundColor, bool showOverlay, bool closeOnOverlayClick, Color? overlayColor, double? overlayOpacity, bool preventScrollThrough, bool destroyOnClose, Duration duration, String? title, Widget? titleWidget, bool titleAlignLeft, String? cancelBtn, Widget? cancel, WidgetBuilder? cancelBuilder, VoidCallback? onCancel, String? confirmBtn, Widget? confirm, WidgetBuilder? confirmBuilder, VoidCallback? onConfirm, bool autoCloseOnCancel, bool autoCloseOnConfirm, TPopupCloseBuilder? closeBuilder, VoidCallback? onCloseBtn, TPopupHeaderBuilder? headerBuilder, VoidCallback? onOpen, VoidCallback? onOpened, VoidCallback? onClose, VoidCallback? onClosed, TPopupVisibleChangeCallback? onVisibleChange, VoidCallback? onOverlayClick, BuildContext? navigatorContext, bool useRootNavigator, | 命令式打开浮层,参数与 [TPopup] 构造器一致。 返回 [TPopupHandle];优先 [TPopupHandle.close],或在 Popup 子树内 [close]。 [cancel]/[confirm] 默认 [kPopupActionDefault] 表示默认文案,显式 null 可隐藏操作栏侧。 [closeBuilder] 未传为 [kPopupDefaultClose](默认关闭图标),显式 null 不显示关闭区。 | +| isActionDefault | | required Widget? action, | | + +``` +``` + +### TPopupHeaderData +#### 简介 +传给自定义 [TPopupOptions.headerBuilder] 的标题栏数据(库内已组装好各槽 Widget)。 +#### 默认构造方法 + +| 参数 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| cancel | Widget? | - | 左侧区域 Widget(null 表示该侧已隐藏)。 | +| confirm | Widget? | - | 右侧区域 Widget(null 表示该侧已隐藏)。 | +| onCancel | VoidCallback? | - | 点击左侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnCancel] 决定)。 | +| onConfirm | VoidCallback? | - | 点击右侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnConfirm] 决定)。 | +| title | Widget? | - | 中间标题(可为 null)。 | diff --git a/tdesign-component/example/lib/component_test/popup_test.dart b/tdesign-component/example/lib/component_test/popup_test.dart index 5a826522b..e85184870 100644 --- a/tdesign-component/example/lib/component_test/popup_test.dart +++ b/tdesign-component/example/lib/component_test/popup_test.dart @@ -25,23 +25,22 @@ class TestPage extends StatefulWidget { class _TestPageState extends State { void _showProblemDialog() { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - title: 'title', - radius: 20, - backgroundColor: const Color(0xFFFAFFFC), - onCloseBtn: () => TPopup.close(context), - child: Container( - padding: const EdgeInsets.only(left: 20, right: 20, bottom: 33), - decoration: const BoxDecoration(color: Colors.white), - child: const Column( - children: [ - Center(child: Text('立即拨打')), - ], - ), - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + title: 'title', + radius: 20, + backgroundColor: const Color(0xFFFAFFFC), + child: Container( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 33), + decoration: const BoxDecoration(color: Colors.white), + child: const Column( + children: [ + Center(child: Text('立即拨打')), + ], + ), + )), + ).show(context); } @override diff --git a/tdesign-component/example/lib/page/t_indexes_page.dart b/tdesign-component/example/lib/page/t_indexes_page.dart index b889d5603..774fafb8e 100644 --- a/tdesign-component/example/lib/page/t_indexes_page.dart +++ b/tdesign-component/example/lib/page/t_indexes_page.dart @@ -157,23 +157,23 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); } @@ -189,24 +189,24 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - capsuleTheme: true, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + capsuleTheme: true, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); } @@ -222,31 +222,31 @@ Widget _buildCustomIndexes(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - builderIndex: (context, index, isActive) { - return TText( - '自定义 $index', - textColor: isActive - ? TTheme.of(context).brandNormalColor - : TTheme.of(context).textColorPrimary, - ); - }, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderIndex: (context, index, isActive) { + return TText( + '自定义 $index', + textColor: isActive + ? TTheme.of(context).brandNormalColor + : TTheme.of(context).textColorPrimary, + ); + }, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); } diff --git a/tdesign-component/example/lib/page/t_picker_page.dart b/tdesign-component/example/lib/page/t_picker_page.dart index 3eba316c8..04fbc2bb1 100644 --- a/tdesign-component/example/lib/page/t_picker_page.dart +++ b/tdesign-component/example/lib/page/t_picker_page.dart @@ -33,13 +33,16 @@ class _TPickerPageState extends State { final timeItems = TPickerColumns([ [ - for (int i = 0; i < 24; i++) TPickerOption(label: '${i.toString().padLeft(2, '0')}时', value: i), + for (int i = 0; i < 24; i++) + TPickerOption(label: '${i.toString().padLeft(2, '0')}时', value: i), ], [ - for (int i = 0; i < 60; i++) TPickerOption(label: '${i.toString().padLeft(2, '0')}分', value: i), + for (int i = 0; i < 60; i++) + TPickerOption(label: '${i.toString().padLeft(2, '0')}分', value: i), ], [ - for (int i = 0; i < 60; i++) TPickerOption(label: '${i.toString().padLeft(2, '0')}秒', value: i), + for (int i = 0; i < 60; i++) + TPickerOption(label: '${i.toString().padLeft(2, '0')}秒', value: i), ], ]); @@ -101,18 +104,18 @@ class _TPickerPageState extends State { ], // 第2列:开头 + 中间 + 结尾 各 1 个禁用(稀疏分布,留足操作空间) [ - TPickerOption(label: 'A排1座', value: 'A1', disabled: true), // 开头禁用 + TPickerOption(label: 'A排1座', value: 'A1', disabled: true), // 开头禁用 TPickerOption(label: 'A排2座', value: 'A2'), TPickerOption(label: 'A排3座', value: 'A3'), TPickerOption(label: 'A排4座', value: 'A4'), TPickerOption(label: 'A排5座', value: 'A5'), TPickerOption(label: 'A排6座', value: 'A6', disabled: true), - TPickerOption(label: 'A排7座', value: 'A7', disabled: true), // 中间偏后禁用 - TPickerOption(label: 'A排8座', value: 'A8', disabled: true), // 新增禁用 + TPickerOption(label: 'A排7座', value: 'A7', disabled: true), // 中间偏后禁用 + TPickerOption(label: 'A排8座', value: 'A8', disabled: true), // 新增禁用 TPickerOption(label: 'A排9座', value: 'A9'), TPickerOption(label: 'A排10座', value: 'A10'), TPickerOption(label: 'A排11座', value: 'A11'), - TPickerOption(label: 'A排12座', value: 'A12', disabled: true), // 结尾禁用 + TPickerOption(label: 'A排12座', value: 'A12', disabled: true), // 结尾禁用 ], ]); @@ -157,7 +160,8 @@ class _TPickerPageState extends State { ]), ExampleModule(title: '禁用状态', children: [ ExampleItem(desc: '项级 disabled(部分选项不可选)', builder: buildItemDisabled), - ExampleItem(desc: '全局 disabled(整组不可操作)', builder: buildGlobalDisabled), + ExampleItem( + desc: '全局 disabled(整组不可操作)', builder: buildGlobalDisabled), ]), ExampleModule(title: '弹窗模式(TPopup)', children: [ ExampleItem(desc: '弹窗-联动选择(省市区)', builder: buildPopupLinked), @@ -167,11 +171,13 @@ class _TPickerPageState extends State { ExampleItem(desc: '自定义按钮(图标 / 文字)', builder: buildCustomSlot), ]), ExampleModule(title: '自定义字段映射(keys)', children: [ - ExampleItem(desc: '数据字段非 label/value 时,用 keys 映射', builder: buildCustomKeys), + ExampleItem( + desc: '数据字段非 label/value 时,用 keys 映射', builder: buildCustomKeys), ]), ExampleModule(title: '尺寸与样式', children: [ ExampleItem(desc: '自定义高度和每屏显示数量', builder: buildCustomSize), - ExampleItem(desc: '自定义子项渲染(itemBuilder)', builder: buildCustomItemBuilder), + ExampleItem( + desc: '自定义子项渲染(itemBuilder)', builder: buildCustomItemBuilder), ]), ], ); @@ -184,19 +190,19 @@ class _TPickerPageState extends State { /// TPicker 自带「取消 / 标题 / 确认」工具栏,业务方在 onCancel/onConfirm /// 中自行决定是否调用 Navigator.pop 关闭弹窗。 void _showPickerPopup(BuildContext context, {required Widget picker}) { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, - child: Material( - color: TTheme.of(context).bgColorContainer, - child: SafeArea( - top: false, - child: picker, - ), - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + child: Material( + color: TTheme.of(context).bgColorContainer, + child: SafeArea( + top: false, + child: picker, + ), + )), + ).show(context); } // ========== 嵌入式容器 ========== @@ -220,11 +226,13 @@ class _TPickerPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中城市: ${selectedCity.isEmpty ? "未选择" : selectedCity}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: cityItems, + child: TPicker( + items: cityItems, onChange: (v) => setState(() => selectedCity = v.labels.first)), ), ], @@ -237,13 +245,16 @@ class _TPickerPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中时间: ${selectedTime.isEmpty ? "未选择" : selectedTime}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: timeItems, itemCount: 5, - onChange: (v) => setState(() => - selectedTime = '${v.values[0]}:${v.values[1].toString().padLeft(2, '0')}:${v.values[2].toString().padLeft(2, '0')}')), + child: TPicker( + items: timeItems, + itemCount: 5, + onChange: (v) => setState(() => selectedTime = + '${v.values[0]}:${v.values[1].toString().padLeft(2, '0')}:${v.values[2].toString().padLeft(2, '0')}')), ), ], ); @@ -255,12 +266,16 @@ class _TPickerPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中地区: ${selectedLinked.isEmpty ? "未选择" : selectedLinked}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: linkedItems, initialValue: const ['GD', 'SZ', 'NS'], - onChange: (v) => setState(() => selectedLinked = v.labels.join(' / '))), + child: TPicker( + items: linkedItems, + initialValue: const ['GD', 'SZ', 'NS'], + onChange: (v) => + setState(() => selectedLinked = v.labels.join(' / '))), ), ], ); @@ -273,15 +288,20 @@ class _TPickerPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('选中: ${selectedItemDisabled.isEmpty ? "未选择" : selectedItemDisabled}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + Text( + '选中: ${selectedItemDisabled.isEmpty ? "未选择" : selectedItemDisabled}', + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 4), Text('提示: 标灰的选项不可选(第1列「保密」、第2列「A排1座/A排6座/A排7座/A排8座/A排12座」)', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: itemDisabledItems, initialValue: const ['M', 'A5'], + child: TPicker( + items: itemDisabledItems, + initialValue: const ['M', 'A5'], onChange: (v) => setState(() => selectedItemDisabled = '${v.labels.first} ${v.labels.last}')), ), @@ -312,13 +332,16 @@ class _TPickerPageState extends State { const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: cityItems, initialValue: const ['GZ'], + child: TPicker( + items: cityItems, + initialValue: const ['GZ'], onChange: (v) => debugPrint('选中: $v'), disabled: globalDisabled), ), const SizedBox(height: 4), Text('切换开关可控制整个选择器的禁用/启用状态', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), ], ); } @@ -373,8 +396,7 @@ class _TPickerPageState extends State { titleWidget: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(TIcons.location, - size: 18, color: theme.brandNormalColor), + Icon(TIcons.location, size: 18, color: theme.brandNormalColor), const SizedBox(width: 4), Text('选择地区', style: TextStyle( @@ -385,7 +407,8 @@ class _TPickerPageState extends State { ], ), cancel: Icon(TIcons.close, size: 22, color: theme.fontGyColor2), - confirm: Icon(TIcons.check, size: 22, color: theme.brandNormalColor), + confirm: + Icon(TIcons.check, size: 22, color: theme.brandNormalColor), ), ), ], @@ -503,7 +526,7 @@ class _TPickerPageState extends State { {'code': 'BJ', 'city': '北京', 'readonly': false}, {'code': 'SH', 'city': '上海', 'readonly': false}, {'code': 'GZ', 'city': '广州', 'readonly': false}, - {'code': 'SZ', 'city': '深圳', 'readonly': true}, // 演示禁用映射 + {'code': 'SZ', 'city': '深圳', 'readonly': true}, // 演示禁用映射 {'code': 'CD', 'city': '成都', 'readonly': false}, {'code': 'HZ', 'city': '杭州', 'readonly': false}, ], @@ -516,19 +539,22 @@ class _TPickerPageState extends State { @Demo(group: 'picker') Widget buildCustomKeys(BuildContext context) { // 用 keys 告诉组件「city 映射为 label,code 是 value,readonly 是 disabled」 - const keys = TPickerKeys(label: 'city', value: 'code', disabled: 'readonly'); + const keys = + TPickerKeys(label: 'city', value: 'code', disabled: 'readonly'); final label = _customKeysValue?.labels.join() ?? ''; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '后端原始字段:city / code / readonly。通过 keys(label: "city") 映射为 label', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder), ), const SizedBox(height: 4), Text( '当前选中:${label.isEmpty ? "未选择" : label}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary), ), const SizedBox(height: 8), _pickerCard( @@ -552,7 +578,8 @@ class _TPickerPageState extends State { children: [ Text( '示例:height(300) + itemCount(7),每屏显示 7 项', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder), ), const SizedBox(height: 8), _pickerCard( @@ -577,12 +604,14 @@ class _TPickerPageState extends State { children: [ Text( '示例:itemBuilder 自定义子项渲染,可添加图标、背景色等', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder), ), const SizedBox(height: 4), Text( '选中: ${_customItemBuilderValue.isEmpty ? "未选择" : _customItemBuilderValue}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary), ), const SizedBox(height: 8), _pickerCard( @@ -608,15 +637,19 @@ class _TPickerPageState extends State { content, style: TextStyle( fontSize: 16, - fontWeight: selected ? FontWeight.w600 : FontWeight.normal, - color: selected ? theme.brandNormalColor : theme.fontGyColor1, + fontWeight: + selected ? FontWeight.w600 : FontWeight.normal, + color: selected + ? theme.brandNormalColor + : theme.fontGyColor1, ), ), ], ), ); }, - onChange: (v) => setState(() => _customItemBuilderValue = v.labels.first), + onChange: (v) => + setState(() => _customItemBuilderValue = v.labels.first), ), ), ], diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart index 46553c639..6af27f73f 100644 --- a/tdesign-component/example/lib/page/t_popup_page.dart +++ b/tdesign-component/example/lib/page/t_popup_page.dart @@ -13,7 +13,10 @@ class TPopupPage extends StatelessWidget { static const double _headerHeight = 58; /// 底部标题 + 关闭(自定义 headerBuilder,使用 [TPopupHeaderData])。 - static TPopupHeaderBuilder _bottomTitleCloseHeader({String? title}) { + static TPopupHeaderBuilder _bottomTitleCloseHeader({ + String? title, + required VoidCallback onClose, + }) { return (BuildContext ctx, TPopupHeaderData data) { final theme = TTheme.of(ctx); final headerTitle = data.title ?? @@ -38,7 +41,7 @@ class TPopupPage extends StatelessWidget { ), IconButton( icon: Icon(TIcons.close, color: theme.textColorSecondary), - onPressed: () => TPopup.close(ctx), + onPressed: onClose, ), ], ), @@ -97,28 +100,24 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - cancel: TText( - '点这里确认!', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), - confirm: TText( - '关闭', - textColor: TTheme.of(context).errorNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), - onCancel: () { - TToast.showText('确认', context: context); - TPopup.close(context); - }, - onConfirm: () => TPopup.close(context), - child: Container(height: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', + cancel: TText( + '点这里确认!', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), + confirm: TText( + '关闭', + textColor: TTheme.of(context).errorNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), + onCancel: () => TToast.showText('确认', context: context), + child: Container(height: 200)), + ).show(context); }, ); }, @@ -133,16 +132,17 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - headerBuilder: _bottomTitleCloseHeader( - title: - '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - ), - child: Container(height: 200), - ); + TPopupHandle? handle; + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + headerBuilder: _bottomTitleCloseHeader( + title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', + onClose: () => handle?.close(), + ), + child: Container(height: 200)), + ).show(context); }, ); }, @@ -161,17 +161,18 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - radius: 6, - headerBuilder: _bottomTitleCloseHeader( - title: - '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - ), - child: Container(height: 200), - ); + TPopupHandle? handle; + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + radius: 6, + headerBuilder: _bottomTitleCloseHeader( + title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', + onClose: () => handle?.close(), + ), + child: Container(height: 200)), + ).show(context); }, ), const SizedBox(height: 16), @@ -182,30 +183,26 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - radius: 6, - title: - '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - cancel: TText( - '点这里确认!', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), - confirm: TText( - '关闭', - textColor: TTheme.of(context).errorNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), - onCancel: () { - TToast.showText('确认', context: context); - TPopup.close(context); - }, - onConfirm: () => TPopup.close(context), - child: Container(height: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + radius: 6, + title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', + cancel: TText( + '点这里确认!', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), + confirm: TText( + '关闭', + textColor: TTheme.of(context).errorNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), + onCancel: () => + TToast.showText('确认', context: context), + child: Container(height: 200)), + ).show(context); }, ), const SizedBox(height: 16), @@ -216,22 +213,22 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - width: 240, - height: 240, - radius: 6, - closeBuilder: (_, close) => IconButton( - icon: Icon( - TIcons.close_circle, - color: TTheme.of(context).errorNormalColor, - size: 32, - ), - onPressed: close, - ), - child: const SizedBox(height: 240, width: 240), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 240, + height: 240, + radius: 6, + closeBuilder: (_, close) => IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).errorNormalColor, + size: 32, + ), + onPressed: close, + ), + child: const SizedBox(height: 240, width: 240)), + ).show(context); }, ), const SizedBox(height: 16), @@ -242,15 +239,14 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - width: 240, - height: 240, - radius: 6, - onCloseBtn: () => TPopup.close(context), - child: const SizedBox(height: 240, width: 240), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 240, + height: 240, + radius: 6, + child: const SizedBox(height: 240, width: 240)), + ).show(context); }, ), ], @@ -269,15 +265,15 @@ class TPopupPage extends StatelessWidget { onTap: () { final renderBox = navBarkey.currentContext!.findRenderObject() as RenderBox; - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox.size.height), - child: Container( - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox.size.height), + child: Container( + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); }, @@ -297,17 +293,17 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.top, - height: 240, - onOpen: () => print('open'), - onOpened: () => print('opened'), - child: Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.top, + height: 240, + onOpen: () => print('open'), + onOpened: () => print('opened'), + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + )), + ).show(context); }, ); } @@ -321,14 +317,14 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.left, - width: 280, - child: Container( - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.left, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -342,20 +338,20 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeBuilder: null, - child: Container( - decoration: BoxDecoration( - color: TTheme.of(context).bgColorContainer, - borderRadius: - BorderRadius.circular(TTheme.of(context).radiusLarge), - ), - width: 240, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeBuilder: null, + child: Container( + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: + BorderRadius.circular(TTheme.of(context).radiusLarge), + ), + width: 240, + height: 240, + )), + ).show(context); }, ); } @@ -369,16 +365,16 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 240, - headerBuilder: null, - child: Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 240, + headerBuilder: null, + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + )), + ).show(context); }, ); } @@ -392,21 +388,21 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - child: Container( - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } // --- 02 组件示例 --- - /// 外层 Popup 的 child 内再 [TPopup.show]:Tracker 栈顶为内层,[TPopup.close] 先关内层。 + /// 外层 Popup 的 child 内再 `TPopup(options: …).show`:用各自 [TPopupHandle] 关闭。 @Demo(group: 'popup') Widget _buildNestedPopup(BuildContext context) { return TButton( @@ -416,57 +412,58 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 360, - headerBuilder: null, - child: Builder( - builder: (innerContext) { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TText( - '外层:headerBuilder: null,仅 child', - textColor: TTheme.of(innerContext).textColorSecondary, - ), - const SizedBox(height: 16), - TButton( - text: '打开内层 Popup', - isBlock: true, - theme: TButtonTheme.primary, - size: TButtonSize.large, - onTap: () { - TPopup.show( - context: innerContext, - placement: TPopupPlacement.bottom, - height: 280, - title: '内层标题', - onCancel: () => TPopup.close(innerContext), - onConfirm: () => TPopup.close(innerContext), - child: Container( - height: 160, - color: TTheme.of(innerContext).bgColorSecondaryContainer, - ), - ); - }, - ), - const SizedBox(height: 12), - TButton( - text: '关闭外层', - isBlock: true, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () => TPopup.close(innerContext), + TPopupHandle? outerHandle; + outerHandle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 360, + headerBuilder: null, + child: Builder( + builder: (innerContext) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TText( + '外层:headerBuilder: null,仅 child', + textColor: TTheme.of(innerContext).textColorSecondary, + ), + const SizedBox(height: 16), + TButton( + text: '打开内层 Popup', + isBlock: true, + theme: TButtonTheme.primary, + size: TButtonSize.large, + onTap: () { + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + title: '内层标题', + child: Container( + height: 160, + color: TTheme.of(innerContext) + .bgColorSecondaryContainer, + ), + ), + ).show(innerContext); + }, + ), + const SizedBox(height: 12), + TButton( + text: '关闭外层', + isBlock: true, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => outerHandle?.close(), + ), + ], ), - ], - ), - ); - }, - ), - ); + ); + }, + )), + ).show(context); }, ); } @@ -480,18 +477,14 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - title: '标题文字', - onCancel: () => TPopup.close(context), - onConfirm: () { - TToast.showText('确定', context: context); - TPopup.close(context); - }, - child: Container(height: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + onConfirm: () => TToast.showText('确定', context: context), + child: Container(height: 200)), + ).show(context); }, ); } @@ -505,38 +498,36 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - cancel: TText( - '关闭', - textColor: TTheme.of(context).textColorSecondary, - font: TTheme.of(context).fontBodyLarge, - ), - titleWidget: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(TIcons.info_circle, - color: TTheme.of(context).brandNormalColor, size: 18), - const SizedBox(width: 4), - TText( - '自定义标题', + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + cancel: TText( + '关闭', + textColor: TTheme.of(context).textColorSecondary, + font: TTheme.of(context).fontBodyLarge, + ), + titleWidget: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(TIcons.info_circle, + color: TTheme.of(context).brandNormalColor, size: 18), + const SizedBox(width: 4), + TText( + '自定义标题', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + ), + ], + ), + confirm: TText( + '完成', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontTitleMedium, + fontWeight: FontWeight.w600, ), - ], - ), - confirm: TText( - '完成', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontTitleMedium, - fontWeight: FontWeight.w600, - ), - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container(height: 200), - ); + child: Container(height: 200)), + ).show(context); }, ); } @@ -550,22 +541,22 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeOnOverlayClick: false, - width: 240, - height: 240, - closeBuilder: (_, close) => IconButton( - icon: Icon( - TIcons.close_circle, - color: TTheme.of(context).fontWhColor1, - size: 32, - ), - onPressed: close, - ), - child: const SizedBox(width: 240, height: 240), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeOnOverlayClick: false, + width: 240, + height: 240, + closeBuilder: (_, close) => IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).fontWhColor1, + size: 32, + ), + onPressed: close, + ), + child: const SizedBox(width: 240, height: 240)), + ).show(context); }, ); } @@ -579,26 +570,26 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeOnOverlayClick: true, - width: 240, - height: 200, - closeBuilder: (_, close) => IconButton( - icon: Icon( - TIcons.poweroff, - color: TTheme.of(context).fontWhColor1, - size: 36, - ), - onPressed: close, - ), - child: Container( - width: 240, - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeOnOverlayClick: true, + width: 240, + height: 200, + closeBuilder: (_, close) => IconButton( + icon: Icon( + TIcons.poweroff, + color: TTheme.of(context).fontWhColor1, + size: 36, + ), + onPressed: close, + ), + child: Container( + width: 240, + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -614,19 +605,17 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 320, - margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - title: '日历式留白', - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container( - height: 240, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 320, + margin: const EdgeInsets.only(top: 120, left: 16, right: 16), + title: '日历式留白', + child: Container( + height: 240, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -640,20 +629,18 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - showOverlay: false, - // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) - title: '无蒙层', - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + showOverlay: false, + // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) + title: '无蒙层', + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -667,17 +654,16 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 260, - onOverlayClick: () => - TToast.showText('点击蒙层', context: context), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 260, + onOverlayClick: () => TToast.showText('点击蒙层', context: context), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -691,16 +677,16 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 240, - duration: const Duration(milliseconds: 600), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 240, + duration: const Duration(milliseconds: 600), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } diff --git a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart index f48700fe1..f8722f0e1 100644 --- a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart +++ b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart @@ -9,7 +9,8 @@ import 't_action_sheet_list.dart'; export 't_action_sheet_item.dart'; -typedef TActionSheetItemCallback = void Function(TActionSheetItem item, int index); +typedef TActionSheetItemCallback = void Function( + TActionSheetItem item, int index); enum TActionSheetTheme { list, grid, group } @@ -331,16 +332,17 @@ class TActionSheet { break; } - _actionSheetHandle = TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, - showOverlay: showOverlay, - closeOnOverlayClick: showOverlay && closeOnOverlayClick, - overlayColor: showOverlay ? null : Colors.transparent, - onClosed: onClose, - child: sheetChild, - ); + _actionSheetHandle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + showOverlay: showOverlay, + closeOnOverlayClick: showOverlay && closeOnOverlayClick, + overlayColor: showOverlay ? null : Colors.transparent, + onClosed: onClose, + child: sheetChild, + ), + ).show(context); } } diff --git a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart index 9d09dfefe..05844f2ae 100644 --- a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart +++ b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart @@ -69,28 +69,29 @@ class TCalendarPopup { return; } final childWidget = builder?.call(context) ?? child; - _calendarHandle = TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, - margin: EdgeInsets.only(top: top ?? 0), - closeOnOverlayClick: false, - onOverlayClick: () { - if (_autoClose) { - close(); - } - }, - onClosed: _deleteRouter, - child: TCalendarInherited( - selected: _selected, - usePopup: true, - confirmBtn: confirmBtn, - onClose: _onClose, - onConfirm: _onConfirm, - child: childWidget!, + _calendarHandle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + margin: EdgeInsets.only(top: top ?? 0), + closeOnOverlayClick: false, + onOverlayClick: () { + if (_autoClose) { + close(); + } + }, + onClosed: _deleteRouter, + child: TCalendarInherited( + selected: _selected, + usePopup: true, + confirmBtn: confirmBtn, + onClose: _onClose, + onConfirm: _onConfirm, + child: childWidget!, + ), ), - ); + ).show(context); } void _onClose() { diff --git a/tdesign-component/lib/src/components/drawer/t_drawer.dart b/tdesign-component/lib/src/components/drawer/t_drawer.dart index 534183af9..5c85ffe6d 100644 --- a/tdesign-component/lib/src/components/drawer/t_drawer.dart +++ b/tdesign-component/lib/src/components/drawer/t_drawer.dart @@ -108,32 +108,33 @@ class TDrawer { final overlayEnabled = showOverlay ?? true; final dismissible = overlayEnabled && (closeOnOverlayClick ?? true); - _drawerHandle = TPopup.show( - context: context, - placement: placement == TDrawerPlacement.right - ? TPopupPlacement.right - : TPopupPlacement.left, - width: width, - margin: EdgeInsets.only(top: drawerTop ?? 0), - showOverlay: overlayEnabled, - closeOnOverlayClick: dismissible, - overlayColor: overlayEnabled ? null : Colors.transparent, - onClosed: _deleteRouter, - child: TDrawerWidget( - footer: footer, - items: items, - contentWidget: contentWidget, - title: title, - titleWidget: titleWidget, - onItemClick: onItemClick, + _drawerHandle = TPopup( + options: TPopupOptions( + placement: placement == TDrawerPlacement.right + ? TPopupPlacement.right + : TPopupPlacement.left, width: width, - style: style, - hover: hover, - backgroundColor: backgroundColor, - bordered: bordered, - isShowLastBordered: isShowLastBordered, + margin: EdgeInsets.only(top: drawerTop ?? 0), + showOverlay: overlayEnabled, + closeOnOverlayClick: dismissible, + overlayColor: overlayEnabled ? null : Colors.transparent, + onClosed: _deleteRouter, + child: TDrawerWidget( + footer: footer, + items: items, + contentWidget: contentWidget, + title: title, + titleWidget: titleWidget, + onItemClick: onItemClick, + width: width, + style: style, + hover: hover, + backgroundColor: backgroundColor, + bordered: bordered, + isShowLastBordered: isShowLastBordered, + ), ), - ); + ).show(context); } void open() { diff --git a/tdesign-component/lib/src/components/popup/_popup_center_close.dart b/tdesign-component/lib/src/components/popup/_popup_center_close.dart index b5e126fbc..af8a67926 100644 --- a/tdesign-component/lib/src/components/popup/_popup_center_close.dart +++ b/tdesign-component/lib/src/components/popup/_popup_center_close.dart @@ -4,16 +4,16 @@ import '../../theme/t_colors.dart'; import '../../theme/t_theme.dart'; import '../../util/context_extension.dart'; import '../icon/t_icons.dart'; -import 't_popup_config.dart'; +import 't_popup_options.dart'; import 't_popup_types.dart'; /// 构建 center 面板下方关闭控件(默认图标或 [closeBuilder])。 Widget buildPopupCenterCloseControl({ required BuildContext context, - required TPopupConfig config, + required TPopupOptions options, required VoidCallback onClose, }) { - if (isPopupDefaultClose(config.closeBuilder)) { + if (isPopupDefaultClose(options.closeBuilder)) { final theme = TTheme.of(context); return IconButton( tooltip: context.resource.close, @@ -25,41 +25,41 @@ Widget buildPopupCenterCloseControl({ onPressed: onClose, ); } - return config.closeBuilder!(context, onClose); + return options.closeBuilder!(context, onClose); } /// 居中浮层:白底内容区 + 面板**外下方**关闭控件(center 内置布局)。 class PopupCenterUnderClose extends StatelessWidget { const PopupCenterUnderClose({ super.key, - required this.config, + required this.options, required this.content, required this.onCloseWithTrigger, }); - final TPopupConfig config; + final TPopupOptions options; final Widget content; final void Function(TPopupTrigger trigger) onCloseWithTrigger; @override Widget build(BuildContext context) { Widget panel = content; - if (config.width != null || config.height != null) { + if (options.width != null || options.height != null) { panel = SizedBox( - width: config.width, - height: config.height, + width: options.width, + height: options.height, child: content, ); } void close() { - config.onCloseBtn?.call(); + options.onCloseBtn?.call(); onCloseWithTrigger(TPopupTrigger.closeBtn); } final closeControl = buildPopupCenterCloseControl( context: context, - config: config, + options: options, onClose: close, ); diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart index 691ace70a..4454e9971 100644 --- a/tdesign-component/lib/src/components/popup/_popup_header.dart +++ b/tdesign-component/lib/src/components/popup/_popup_header.dart @@ -7,40 +7,40 @@ import '../../theme/t_theme.dart'; import '../../util/context_extension.dart'; import '../../util/t_toolbar_pressable.dart'; import '../text/t_text.dart'; -import 't_popup_config.dart'; +import 't_popup_options.dart'; import 't_popup_types.dart'; /// 内置标题栏区域(仅 [TPopupPlacement.bottom])。 class PopupHeader extends StatelessWidget { const PopupHeader({ super.key, - required this.config, + required this.options, required this.onCloseWithTrigger, }); - final TPopupConfig config; + final TPopupOptions options; final void Function(TPopupTrigger trigger) onCloseWithTrigger; static const double headerHeight = 58; @override Widget build(BuildContext context) { - if (config.placement != TPopupPlacement.bottom || config.hasNoHeader) { + if (options.placement != TPopupPlacement.bottom || options.hasNoHeader) { return const SizedBox.shrink(); } - if (config.useCustomHeader) { - return config.headerBuilder!( + if (options.useCustomHeader) { + return options.headerBuilder!( context, _buildHeaderData(context), ); } - if (config.useActionHeader) { + if (options.useActionHeader) { return SizedBox( height: headerHeight, child: _ActionHeader( - config: config, + options: options, onCloseWithTrigger: onCloseWithTrigger, ), ); @@ -54,9 +54,8 @@ class PopupHeader extends StatelessWidget { return SizedBox( height: headerHeight, child: Container( - alignment: config.titleAlignLeft - ? Alignment.centerLeft - : Alignment.center, + alignment: + options.titleAlignLeft ? Alignment.centerLeft : Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 16), child: title, ), @@ -67,24 +66,22 @@ class PopupHeader extends StatelessWidget { final theme = TTheme.of(context); return TPopupHeaderData( title: _buildTitleWidget(context), - cancel: config.showCancelSlot - ? _buildCancelWidget(context, theme) - : null, - confirm: config.showConfirmSlot - ? _buildConfirmWidget(context, theme) - : null, - onCancel: config.onCancel, - onConfirm: config.onConfirm, + cancel: + options.showCancelSlot ? _buildCancelWidget(context, theme) : null, + confirm: + options.showConfirmSlot ? _buildConfirmWidget(context, theme) : null, + onCancel: options.onCancel, + onConfirm: options.onConfirm, ); } Widget? _buildTitleWidget(BuildContext context) { - if (config.titleWidget != null) { - return config.titleWidget; + if (options.titleWidget != null) { + return options.titleWidget; } - if (config.title != null && config.title!.isNotEmpty) { + if (options.title != null && options.title!.isNotEmpty) { return TText( - config.title!, + options.title!, textColor: TTheme.of(context).textColorPrimary, font: TTheme.of(context).fontTitleLarge, fontWeight: FontWeight.w700, @@ -96,50 +93,50 @@ class PopupHeader extends StatelessWidget { } Widget _buildCancelWidget(BuildContext context, TThemeData theme) { - if (config.cancelBuilder != null) { - return config.cancelBuilder!(context); + if (options.cancelBuilder != null) { + return options.cancelBuilder!(context); } - if (TPopupConfig.isActionDefault(config.cancel)) { + if (TPopupOptions.isActionDefault(options.cancel)) { return TText( - config.cancelBtn ?? context.resource.cancel, + options.cancelBtn ?? context.resource.cancel, textColor: theme.textColorSecondary, font: theme.fontBodyLarge, ); } - return config.cancel!; + return options.cancel!; } Widget _buildConfirmWidget(BuildContext context, TThemeData theme) { - if (config.confirmBuilder != null) { - return config.confirmBuilder!(context); + if (options.confirmBuilder != null) { + return options.confirmBuilder!(context); } - if (TPopupConfig.isActionDefault(config.confirm)) { + if (TPopupOptions.isActionDefault(options.confirm)) { return TText( - config.confirmBtn ?? context.resource.confirm, + options.confirmBtn ?? context.resource.confirm, textColor: theme.brandNormalColor, font: theme.fontTitleMedium, fontWeight: FontWeight.w600, ); } - return config.confirm!; + return options.confirm!; } } class _ActionHeader extends StatelessWidget { const _ActionHeader({ - required this.config, + required this.options, required this.onCloseWithTrigger, }); - final TPopupConfig config; + final TPopupOptions options; final void Function(TPopupTrigger trigger) onCloseWithTrigger; @override Widget build(BuildContext context) { final theme = TTheme.of(context); - final title = config.titleWidget ?? + final title = options.titleWidget ?? TText( - config.title ?? '', + options.title ?? '', textColor: theme.textColorPrimary, font: theme.fontTitleLarge, fontWeight: FontWeight.w700, @@ -149,17 +146,17 @@ class _ActionHeader extends StatelessWidget { return Row( children: [ - if (config.showCancelSlot) + if (options.showCancelSlot) Padding( padding: EdgeInsets.only(left: theme.spacer8), child: Semantics( button: true, - label: _cancelSemanticsLabel(context, config), + label: _cancelSemanticsLabel(context, options), excludeSemantics: true, child: TToolbarPressable( onTap: () { - config.onCancel?.call(); - if (config.autoCloseOnCancel) { + options.onCancel?.call(); + if (options.autoCloseOnCancel) { onCloseWithTrigger(TPopupTrigger.cancelBtn); } }, @@ -170,17 +167,17 @@ class _ActionHeader extends StatelessWidget { else SizedBox(width: theme.spacer16), Expanded(child: Center(child: title)), - if (config.showConfirmSlot) + if (options.showConfirmSlot) Padding( padding: EdgeInsets.only(right: theme.spacer8), child: Semantics( button: true, - label: _confirmSemanticsLabel(context, config), + label: _confirmSemanticsLabel(context, options), excludeSemantics: true, child: TToolbarPressable( onTap: () { - config.onConfirm?.call(); - if (config.autoCloseOnConfirm) { + options.onConfirm?.call(); + if (options.autoCloseOnConfirm) { onCloseWithTrigger(TPopupTrigger.confirmBtn); } }, @@ -195,45 +192,45 @@ class _ActionHeader extends StatelessWidget { } Widget _buildCancel(BuildContext context, TThemeData theme) { - if (config.cancelBuilder != null) { - return config.cancelBuilder!(context); + if (options.cancelBuilder != null) { + return options.cancelBuilder!(context); } - if (TPopupConfig.isActionDefault(config.cancel)) { + if (TPopupOptions.isActionDefault(options.cancel)) { return TText( - config.cancelBtn ?? context.resource.cancel, + options.cancelBtn ?? context.resource.cancel, textColor: theme.textColorSecondary, font: theme.fontBodyLarge, ); } - return config.cancel!; + return options.cancel!; } Widget _buildConfirm(BuildContext context, TThemeData theme) { - if (config.confirmBuilder != null) { - return config.confirmBuilder!(context); + if (options.confirmBuilder != null) { + return options.confirmBuilder!(context); } - if (TPopupConfig.isActionDefault(config.confirm)) { + if (TPopupOptions.isActionDefault(options.confirm)) { return TText( - config.confirmBtn ?? context.resource.confirm, + options.confirmBtn ?? context.resource.confirm, textColor: theme.brandNormalColor, font: theme.fontTitleMedium, fontWeight: FontWeight.w600, ); } - return config.confirm!; + return options.confirm!; } } -String _cancelSemanticsLabel(BuildContext context, TPopupConfig config) { - final btn = config.cancelBtn; +String _cancelSemanticsLabel(BuildContext context, TPopupOptions options) { + final btn = options.cancelBtn; if (btn != null && btn.isNotEmpty) { return btn; } return context.resource.cancel; } -String _confirmSemanticsLabel(BuildContext context, TPopupConfig config) { - final btn = config.confirmBtn; +String _confirmSemanticsLabel(BuildContext context, TPopupOptions options) { + final btn = options.confirmBtn; if (btn != null && btn.isNotEmpty) { return btn; } diff --git a/tdesign-component/lib/src/components/popup/_popup_layout.dart b/tdesign-component/lib/src/components/popup/_popup_layout.dart index 3e2550b6e..8bba1efe6 100644 --- a/tdesign-component/lib/src/components/popup/_popup_layout.dart +++ b/tdesign-component/lib/src/components/popup/_popup_layout.dart @@ -18,6 +18,7 @@ class PopupLayout { final EdgeInsets margin; final double? width; final double? height; + /// 居中且关闭按钮在内容下方时,不限制总高度(含下方关闭区)。 final bool centerLooseHeight; diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart index bad5faed9..2b384de19 100644 --- a/tdesign-component/lib/src/components/popup/_popup_route.dart +++ b/tdesign-component/lib/src/components/popup/_popup_route.dart @@ -2,23 +2,23 @@ import 'package:flutter/material.dart'; import '_popup_layout.dart'; import '_popup_shell.dart'; -import 't_popup_config.dart'; +import 't_popup_options.dart'; import 't_popup_types.dart'; /// 私有 Popup 路由。 class TPopupNavigatorRoute extends PopupRoute { TPopupNavigatorRoute({ - required this.config, + required this.options, required this.onCloseWithTrigger, }) : _layout = PopupLayout( - placement: config.placement, + placement: options.placement, screenSize: Size.zero, - margin: config.margin, - width: config.width, - height: config.height, + margin: options.margin, + width: options.width, + height: options.height, ); - final TPopupConfig config; + final TPopupOptions options; final void Function(TPopupTrigger trigger, [Object? result]) onCloseWithTrigger; @@ -30,29 +30,29 @@ class TPopupNavigatorRoute extends PopupRoute { String? _barrierSemanticsLabel; Color get _barrierColor { - if (!config.showOverlay) { + if (!options.showOverlay) { return Colors.transparent; } - final base = config.overlayColor ?? Colors.black54; - if (config.overlayOpacity != null) { - final opacity = config.overlayOpacity!.clamp(0.0, 1.0); + final base = options.overlayColor ?? Colors.black54; + if (options.overlayOpacity != null) { + final opacity = options.overlayOpacity!.clamp(0.0, 1.0); return base.withValues(alpha: base.a * opacity); } return base; } @override - Duration get transitionDuration => config.duration; + Duration get transitionDuration => options.duration; @override - Duration get reverseTransitionDuration => config.duration; + Duration get reverseTransitionDuration => options.duration; @override bool get barrierDismissible => false; @override String? get barrierLabel => - config.showOverlay ? _barrierSemanticsLabel : null; + options.showOverlay ? _barrierSemanticsLabel : null; @override Color get barrierColor => Colors.transparent; @@ -64,7 +64,7 @@ class TPopupNavigatorRoute extends PopupRoute { bool get opaque => false; @override - bool get maintainState => !config.destroyOnClose; + bool get maintainState => !options.destroyOnClose; /// 关闭动画开始前回调(系统返回 / handle.close / 蒙层等统一入口)。 void fireCloseStart(TPopupTrigger trigger) { @@ -72,8 +72,8 @@ class TPopupNavigatorRoute extends PopupRoute { return; } _closeStartFired = true; - config.onVisibleChange?.call(false, trigger); - config.onClose?.call(); + options.onVisibleChange?.call(false, trigger); + options.onClose?.call(); } @override @@ -99,29 +99,28 @@ class TPopupNavigatorRoute extends PopupRoute { ); final mediaQuery = MediaQuery.of(context); - if (config.showOverlay) { + if (options.showOverlay) { _barrierSemanticsLabel ??= MaterialLocalizations.of(context).modalBarrierDismissLabel; } _layout = PopupLayout( - placement: config.placement, + placement: options.placement, screenSize: mediaQuery.size, - margin: config.margin, - width: config.width, - height: config.height, - centerLooseHeight: - config.placement == TPopupPlacement.center && - config.closeBuilder != null, + margin: options.margin, + width: options.width, + height: options.height, + centerLooseHeight: options.placement == TPopupPlacement.center && + options.closeBuilder != null, ); final t = curved.value; final shell = PopupShell( - config: config, + options: options, onCloseWithTrigger: onCloseWithTrigger, ); Widget popupContent; - if (config.placement == TPopupPlacement.center) { + if (options.placement == TPopupPlacement.center) { popupContent = Transform.scale( scale: t, alignment: Alignment.center, @@ -141,8 +140,8 @@ class TPopupNavigatorRoute extends PopupRoute { return Stack( fit: StackFit.expand, children: [ - if (config.showOverlay) barrier, - if (!config.showOverlay && config.preventScrollThrough) + if (options.showOverlay) barrier, + if (!options.showOverlay && options.preventScrollThrough) _scrollBlocker(child: const SizedBox.expand()), positioned, ], @@ -159,14 +158,14 @@ class TPopupNavigatorRoute extends PopupRoute { ), ), ); - if (config.showOverlay) { + if (options.showOverlay) { barrier = Semantics( label: _barrierSemanticsLabel!, button: true, child: barrier, ); } - if (config.preventScrollThrough) { + if (options.preventScrollThrough) { barrier = _scrollBlocker(child: barrier); } return barrier; @@ -180,8 +179,8 @@ class TPopupNavigatorRoute extends PopupRoute { } void _handleOverlayTap() { - config.onOverlayClick?.call(); - if (config.closeOnOverlayClick) { + options.onOverlayClick?.call(); + if (options.closeOnOverlayClick) { onCloseWithTrigger(TPopupTrigger.overlay); } } @@ -189,11 +188,11 @@ class TPopupNavigatorRoute extends PopupRoute { void _onAnimationStatus(AnimationStatus status) { if (status == AnimationStatus.completed && !_openedFired) { _openedFired = true; - config.onOpened?.call(); + options.onOpened?.call(); } if (status == AnimationStatus.dismissed && !_closedFired) { _closedFired = true; - config.onClosed?.call(); + options.onClosed?.call(); } } @@ -214,8 +213,8 @@ class TPopupNavigatorRoute extends PopupRoute { @override TickerFuture didPush() { - config.onOpen?.call(); - config.onVisibleChange?.call(true, TPopupTrigger.programmatic); + options.onOpen?.call(); + options.onVisibleChange?.call(true, TPopupTrigger.programmatic); final future = super.didPush(); future.whenComplete(_attachAnimationListener); return future; diff --git a/tdesign-component/lib/src/components/popup/_popup_shell.dart b/tdesign-component/lib/src/components/popup/_popup_shell.dart index 66513d62a..bc66873e0 100644 --- a/tdesign-component/lib/src/components/popup/_popup_shell.dart +++ b/tdesign-component/lib/src/components/popup/_popup_shell.dart @@ -5,32 +5,31 @@ import '../../theme/t_radius.dart'; import '../../theme/t_theme.dart'; import '_popup_center_close.dart'; import '_popup_header.dart'; -import 't_popup_config.dart'; +import 't_popup_options.dart'; import 't_popup_types.dart'; /// 浮层内容外壳:圆角、Header(仅 bottom)、child。 class PopupShell extends StatelessWidget { const PopupShell({ super.key, - required this.config, + required this.options, required this.onCloseWithTrigger, }); - final TPopupConfig config; + final TPopupOptions options; final void Function(TPopupTrigger trigger) onCloseWithTrigger; @override Widget build(BuildContext context) { final theme = TTheme.of(context); - final radius = config.radius ?? theme.radiusExtraLarge; - final backgroundColor = - config.backgroundColor ?? theme.bgColorContainer; - final borderRadius = _borderRadius(config.placement, radius); + final radius = options.radius ?? theme.radiusExtraLarge; + final backgroundColor = options.backgroundColor ?? theme.bgColorContainer; + final borderRadius = _borderRadius(options.placement, radius); - Widget content = config.child; + Widget content = options.child; - if (config.placement == TPopupPlacement.center) { - if (config.closeBuilder != null) { + if (options.placement == TPopupPlacement.center) { + if (options.closeBuilder != null) { final panel = Container( decoration: BoxDecoration( color: backgroundColor, @@ -40,15 +39,15 @@ class PopupShell extends StatelessWidget { child: content, ); return PopupCenterUnderClose( - config: config, + options: options, content: panel, onCloseWithTrigger: onCloseWithTrigger, ); } return Center( child: SizedBox( - width: config.width, - height: config.height, + width: options.width, + height: options.height, child: Container( decoration: BoxDecoration( color: backgroundColor, @@ -61,9 +60,9 @@ class PopupShell extends StatelessWidget { ); } - final useExpanded = config.placement == TPopupPlacement.left || - config.placement == TPopupPlacement.right || - config.height != null; + final useExpanded = options.placement == TPopupPlacement.left || + options.placement == TPopupPlacement.right || + options.height != null; Widget panel = Container( decoration: BoxDecoration( @@ -71,14 +70,13 @@ class PopupShell extends StatelessWidget { borderRadius: borderRadius, ), clipBehavior: Clip.antiAlias, - child: config.placement == TPopupPlacement.bottom + child: options.placement == TPopupPlacement.bottom ? Column( - mainAxisSize: - useExpanded ? MainAxisSize.max : MainAxisSize.min, + mainAxisSize: useExpanded ? MainAxisSize.max : MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ PopupHeader( - config: config, + options: options, onCloseWithTrigger: onCloseWithTrigger, ), if (useExpanded) Expanded(child: content) else content, diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart index 14b34c3dd..bd1a5bdf6 100644 --- a/tdesign-component/lib/src/components/popup/t_popup.dart +++ b/tdesign-component/lib/src/components/popup/t_popup.dart @@ -1,269 +1,75 @@ import 'package:flutter/material.dart'; import '_popup_route.dart'; -import 't_popup_config.dart'; +import 't_popup_options.dart'; import 't_popup_types.dart'; +export 't_popup_options.dart'; export 't_popup_types.dart'; part 't_popup_handle.dart'; part 't_popup_tracker.dart'; -/// 弹出层:支持五向滑入/居中弹出、蒙层、bottom 操作栏与 center 关闭区。 +/// 弹出层:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 /// -/// 命令式用法优先调用 [show];声明式将 [TPopup] 包裹业务子树并设 [initialVisible](弹层在独立路由中,[build] 仅渲染 [child])。 -/// bottom 操作栏参数仅对 [TPopupPlacement.bottom] 生效;center 关闭参数仅对 center 生效; -/// top/left/right 仅使用 [child] 与布局参数。 -/// 嵌套时 [close] 只关栈顶 Popup;无 Popup 时不操作当前页。 +/// ## 怎么用 +/// +/// **命令式(推荐)** — 先组配置,再 `show`,用返回的 [TPopupHandle] 关闭: +/// +/// ```dart +/// final handle = TPopup( +/// options: TPopupOptions( +/// placement: TPopupPlacement.bottom, +/// title: '标题', +/// child: MyPanel(), +/// ), +/// ).show(context); +/// +/// // 关闭这一层(须保留 handle,不要用 context 猜栈顶) +/// handle.close(); +/// ``` +/// +/// **声明式** — 包住子树,`initialVisible: true` 时首帧自动 [show];[build] 只渲染 [options.child]: +/// +/// ```dart +/// TPopup( +/// options: TPopupOptions(child: body), +/// initialVisible: true, +/// ) +/// ``` +/// +/// 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 +/// [TPopupOptions.normalized] 中裁掉)。 class TPopup extends StatefulWidget { const TPopup({ super.key, - required this.child, + required this.options, this.initialVisible = false, - this.placement = TPopupPlacement.bottom, - this.width, - this.height, - this.margin, - this.radius, - this.backgroundColor, - this.showOverlay = true, - this.closeOnOverlayClick = true, - this.overlayColor, - this.overlayOpacity, - this.preventScrollThrough = true, - this.destroyOnClose = false, - this.duration = const Duration(milliseconds: 240), - this.title, - this.titleWidget, - this.titleAlignLeft = false, - this.cancelBtn, - this.cancel = kPopupActionDefault, - this.cancelBuilder, - this.onCancel, - this.confirmBtn, - this.confirm = kPopupActionDefault, - this.confirmBuilder, - this.onConfirm, - this.autoCloseOnCancel = true, - this.autoCloseOnConfirm = true, - this.closeBuilder = kPopupDefaultClose, - this.onCloseBtn, - this.headerBuilder = kPopupDefaultHeader, - this.onOpen, - this.onOpened, - this.onClose, - this.onClosed, - this.onVisibleChange, - this.onOverlayClick, this.navigatorContext, this.useRootNavigator = false, }); - /// 浮层主体内容(必填)。 - final Widget child; + /// 浮层内容与行为配置,见 [TPopupOptions]。 + final TPopupOptions options; - /// 声明式:为 true 时在首帧后自动 [show]。 + /// 为 true 时,挂载后首帧自动调用 [show](仅声明式)。 final bool initialVisible; - /// 出现位置,默认 [TPopupPlacement.bottom]。 - final TPopupPlacement placement; - - /// 宽度;对 left、right、center 生效。 - final double? width; - - /// 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 - final double? height; - - /// 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 - final EdgeInsets? margin; - - /// 内容区圆角,默认主题大圆角。 - final double? radius; - - /// 内容区背景色,默认主题容器色。 - final Color? backgroundColor; - - /// 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 - final bool showOverlay; - - /// 点击蒙层是否关闭(须 [showOverlay] 为 true)。 - final bool closeOnOverlayClick; - - /// 蒙层颜色,默认 black54。 - final Color? overlayColor; - - /// 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 - final double? overlayOpacity; - - /// 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 - final bool preventScrollThrough; - - /// 为 true 时 Popup 路由 [Route.maintainState] 为 false,关闭后不保留路由内 State; - /// 不销毁包裹 [TPopup] 的 StatefulWidget State。 - final bool destroyOnClose; - - /// 打开与关闭动画时长(一致)。 - final Duration duration; - - /// bottom 操作栏中间标题文案。 - final String? title; - - /// bottom 操作栏中间标题组件,优先级高于 [title]。 - final Widget? titleWidget; - - /// bottom 仅标题行时是否左对齐,默认居中。 - final bool titleAlignLeft; - - /// bottom 左侧按钮文案,覆盖默认「取消」。 - final String? cancelBtn; - - /// bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。 - final Widget? cancel; - - /// bottom 左侧按钮构建器,优先级高于 [cancel]。 - final WidgetBuilder? cancelBuilder; - - /// 点击 bottom 左侧按钮回调。 - final VoidCallback? onCancel; - - /// bottom 右侧按钮文案,覆盖默认「确定」。 - final String? confirmBtn; - - /// bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 - final Widget? confirm; - - /// bottom 右侧按钮构建器,优先级高于 [confirm]。 - final WidgetBuilder? confirmBuilder; - - /// 点击 bottom 右侧按钮回调。 - final VoidCallback? onConfirm; - - /// 点击取消后是否自动关闭,默认 true。 - final bool autoCloseOnCancel; - - /// 点击确定后是否自动关闭,默认 true。 - final bool autoCloseOnConfirm; - - /// center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose] 默认圆圈图标; - /// 自定义时通过 [close] 回调关闭。bottom 与三边忽略。 - final TPopupCloseBuilder? closeBuilder; - - /// center 点击关闭控件前的回调。 - final VoidCallback? onCloseBtn; - - /// bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader] 默认操作栏;自定义见 [TPopupHeaderBuilder]。 - final TPopupHeaderBuilder? headerBuilder; - - /// 开始打开时回调(路由入栈)。 - final VoidCallback? onOpen; - - /// 打开动画结束后回调。 - final VoidCallback? onOpened; - - /// 开始关闭时回调(含蒙层、按钮、程序化关闭)。 - final VoidCallback? onClose; - - /// 关闭动画结束且路由移除后回调。 - final VoidCallback? onClosed; - - /// 显隐变化及触发来源。 - final TPopupVisibleChangeCallback? onVisibleChange; - - /// 点击蒙层时回调(在是否关闭判断之前)。 - final VoidCallback? onOverlayClick; - - /// 指定 Navigator 的 context,默认使用当前 context。 + /// 指定使用哪个 [Navigator];默认 [show] 传入的 `context` 所在 Navigator。 final BuildContext? navigatorContext; - /// 是否使用根 Navigator。 + /// 为 true 时使用根 [Navigator](嵌套导航场景)。 final bool useRootNavigator; - /// 命令式打开浮层,参数与 [TPopup] 构造器一致。 + /// 打开浮层并压入独立路由。 /// - /// 返回 [TPopupHandle];优先 [TPopupHandle.close],或在 Popup 子树内 [close]。 + /// 返回 [TPopupHandle]:用 [TPopupHandle.close] 关闭**本次**打开的层; + /// [TPopupHandle.isShowing] 可查询是否仍在展示。 /// - /// [cancel]/[confirm] 默认 [kPopupActionDefault] 表示默认文案,显式 null 可隐藏操作栏侧。 - /// [closeBuilder] 未传为 [kPopupDefaultClose](默认关闭图标),显式 null 不显示关闭区。 - static TPopupHandle show({ - required BuildContext context, - required Widget child, - TPopupPlacement placement = TPopupPlacement.bottom, - double? width, - double? height, - EdgeInsets? margin, - double? radius, - Color? backgroundColor, - bool showOverlay = true, - bool closeOnOverlayClick = true, - Color? overlayColor, - double? overlayOpacity, - bool preventScrollThrough = true, - bool destroyOnClose = false, - Duration duration = const Duration(milliseconds: 240), - String? title, - Widget? titleWidget, - bool titleAlignLeft = false, - String? cancelBtn, - Widget? cancel = kPopupActionDefault, - WidgetBuilder? cancelBuilder, - VoidCallback? onCancel, - String? confirmBtn, - Widget? confirm = kPopupActionDefault, - WidgetBuilder? confirmBuilder, - VoidCallback? onConfirm, - bool autoCloseOnCancel = true, - bool autoCloseOnConfirm = true, - TPopupCloseBuilder? closeBuilder = kPopupDefaultClose, - VoidCallback? onCloseBtn, - TPopupHeaderBuilder? headerBuilder = kPopupDefaultHeader, - VoidCallback? onOpen, - VoidCallback? onOpened, - VoidCallback? onClose, - VoidCallback? onClosed, - TPopupVisibleChangeCallback? onVisibleChange, - VoidCallback? onOverlayClick, - BuildContext? navigatorContext, - bool useRootNavigator = false, - }) { - final config = TPopupConfig.create( - child: child, - placement: placement, - width: width, - height: height, - margin: margin ?? EdgeInsets.zero, - radius: radius, - backgroundColor: backgroundColor, - showOverlay: showOverlay, - closeOnOverlayClick: closeOnOverlayClick, - overlayColor: overlayColor, - overlayOpacity: overlayOpacity, - preventScrollThrough: preventScrollThrough, - destroyOnClose: destroyOnClose, - duration: duration, - title: title, - titleWidget: titleWidget, - titleAlignLeft: titleAlignLeft, - cancelBtn: cancelBtn, - cancel: cancel, - cancelBuilder: cancelBuilder, - onCancel: onCancel, - confirmBtn: confirmBtn, - confirm: confirm, - confirmBuilder: confirmBuilder, - onConfirm: onConfirm, - autoCloseOnCancel: autoCloseOnCancel, - autoCloseOnConfirm: autoCloseOnConfirm, - closeBuilder: closeBuilder, - onCloseBtn: onCloseBtn, - headerBuilder: headerBuilder, - onOpen: onOpen, - onOpened: onOpened, - onClose: onClose, - onClosed: onClosed, - onVisibleChange: onVisibleChange, - onOverlayClick: onOverlayClick, - ); - config.assertPlacementParams(); + /// 同一按钮在页面 context 上重复调用时,若已有展示中的 Popup 会返回已有 handle(防连点)。 + TPopupHandle show(BuildContext context) { + final normalized = options.normalized(); + normalized.assertPlacementParams(); final navContext = navigatorContext ?? context; final navigator = Navigator.of( @@ -291,7 +97,7 @@ class TPopup extends StatefulWidget { } route = TPopupNavigatorRoute( - config: config, + options: normalized, onCloseWithTrigger: closeWithTrigger, ); @@ -310,17 +116,6 @@ class TPopup extends StatefulWidget { return handle; } - /// 关闭当前 Navigator 栈顶 [TPopup]。 - /// - /// 仅关闭 Tracker 栈顶展示中的 Popup;无 Popup 时不操作(不会 pop 当前页)。 - static void close(BuildContext context, [Object? result]) { - final navigator = Navigator.of(context); - final handle = TPopupTracker.top(navigator); - if (handle?.isShowing == true) { - handle!.close(result); - } - } - @override State createState() => _TPopupState(); } @@ -346,51 +141,11 @@ class _TPopupState extends State { if (_handle?.isShowing == true) { return; } - _handle = TPopup.show( - context: context, - navigatorContext: widget.navigatorContext ?? context, - useRootNavigator: widget.useRootNavigator, - child: widget.child, - placement: widget.placement, - width: widget.width, - height: widget.height, - margin: widget.margin, - radius: widget.radius, - backgroundColor: widget.backgroundColor, - showOverlay: widget.showOverlay, - closeOnOverlayClick: widget.closeOnOverlayClick, - overlayColor: widget.overlayColor, - overlayOpacity: widget.overlayOpacity, - preventScrollThrough: widget.preventScrollThrough, - destroyOnClose: widget.destroyOnClose, - duration: widget.duration, - title: widget.title, - titleWidget: widget.titleWidget, - titleAlignLeft: widget.titleAlignLeft, - cancelBtn: widget.cancelBtn, - cancel: widget.cancel, - cancelBuilder: widget.cancelBuilder, - onCancel: widget.onCancel, - confirmBtn: widget.confirmBtn, - confirm: widget.confirm, - confirmBuilder: widget.confirmBuilder, - onConfirm: widget.onConfirm, - autoCloseOnCancel: widget.autoCloseOnCancel, - autoCloseOnConfirm: widget.autoCloseOnConfirm, - closeBuilder: widget.closeBuilder, - onCloseBtn: widget.onCloseBtn, - headerBuilder: widget.headerBuilder, - onOpen: widget.onOpen, - onOpened: widget.onOpened, - onClose: widget.onClose, - onClosed: widget.onClosed, - onVisibleChange: widget.onVisibleChange, - onOverlayClick: widget.onOverlayClick, - ); + _handle = widget.show(context); } @override Widget build(BuildContext context) { - return widget.child; + return widget.options.child; } } diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart index 15787d56f..4987452f9 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart @@ -1,6 +1,15 @@ part of 't_popup.dart'; -/// [TPopup.show] 返回的句柄,用于查询展示状态与程序化关闭。 +/// [TPopup.show] 的返回值,表示**一次**打开操作。 +/// +/// 保存此对象并在需要时调用 [close];不要依赖 `context` 推断要关哪一层。 +/// +/// ```dart +/// final handle = TPopup(options: opts).show(context); +/// if (handle.isShowing) { +/// handle.close('result'); // 可选 result 传给 Navigator.pop +/// } +/// ``` class TPopupHandle { TPopupHandle._({ required void Function(TPopupTrigger trigger, [Object? result]) @@ -14,12 +23,12 @@ class TPopupHandle { _onCloseWithTrigger; bool _isClosed = false; - /// 浮层是否仍在展示(路由在栈中且未进入关闭流程)。 + /// 本次 [TPopup.show] 对应的浮层是否仍在展示。 bool get isShowing => _route != null && !_isClosed; - /// 以 [TPopupTrigger.programmatic] 关闭浮层,可向 Navigator 传递 [result]。 + /// 关闭本次 [TPopup.show] 打开的浮层([TPopupTrigger.programmatic])。 /// - /// 优先于 [TPopup.close]:不依赖 context 的 Navigator 解析。 + /// 已关闭或未展示时调用无副作用。嵌套多层时须用**对应层**的 handle 关闭。 void close([Object? result]) { if (!isShowing) { return; diff --git a/tdesign-component/lib/src/components/popup/t_popup_config.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart similarity index 60% rename from tdesign-component/lib/src/components/popup/t_popup_config.dart rename to tdesign-component/lib/src/components/popup/t_popup_options.dart index 385beae88..00b536f8e 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_config.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart @@ -2,11 +2,29 @@ import 'package:flutter/material.dart'; import 't_popup_types.dart'; -/// Popup 运行时配置(库内共享,由 [TPopup.show] 通过 [TPopupConfig.create] 构建)。 -class TPopupConfig { - TPopupConfig({ +/// 浮层配置:[TPopup] 构造与 [TPopup.show] 的唯一参数来源。 +/// +/// ## 按 [placement] 用哪些字段 +/// +/// | placement | 常用字段 | +/// |-----------|----------| +/// | [TPopupPlacement.bottom] | `title` / `cancel` / `confirm` / `headerBuilder`、`height`、`margin` | +/// | [TPopupPlacement.center] | `closeBuilder`、`width`、`height`(有下方关闭时) | +/// | [TPopupPlacement.top] / [left] / [right] | 主要 `child`、`margin`、方向对应 `width` 或 `height` | +/// +/// 传给其它 placement 的 bottom / center 专用字段会在 [normalized] 里裁掉。 +/// +/// ## 三态占位(bottom / center) +/// +/// - **未传参数**:使用默认 UI(如默认取消/确定文案、默认关闭图标)。 +/// - **显式 `null`**:隐藏该槽位(如 `cancel: null` 隐藏左侧;`closeBuilder: null` 无关闭按钮)。 +/// - **自定义 Widget / Builder**:完全自定义该区域。 +/// +/// [TPopup.show] 内部会先 [normalized] 再绘制。 +class TPopupOptions { + const TPopupOptions({ required this.child, - required this.placement, + this.placement = TPopupPlacement.bottom, this.width, this.height, this.margin = EdgeInsets.zero, @@ -23,18 +41,18 @@ class TPopupConfig { this.titleWidget, this.titleAlignLeft = false, this.cancelBtn, - this.cancel, + this.cancel = kPopupActionDefault, this.cancelBuilder, this.onCancel, this.confirmBtn, - this.confirm, + this.confirm = kPopupActionDefault, this.confirmBuilder, this.onConfirm, this.autoCloseOnCancel = true, this.autoCloseOnConfirm = true, - this.closeBuilder, + this.closeBuilder = kPopupDefaultClose, this.onCloseBtn, - this.headerBuilder, + this.headerBuilder = kPopupDefaultHeader, this.onOpen, this.onOpened, this.onClose, @@ -43,155 +61,175 @@ class TPopupConfig { this.onOverlayClick, }); - /// 按 [placement] 归一化参数(bottom/三边忽略 closeBuilder;center 默认 [kPopupDefaultClose])。 - factory TPopupConfig.create({ - required Widget child, - TPopupPlacement placement = TPopupPlacement.bottom, - double? width, - double? height, - EdgeInsets margin = EdgeInsets.zero, - double? radius, - Color? backgroundColor, - bool showOverlay = true, - bool closeOnOverlayClick = true, - Color? overlayColor, - double? overlayOpacity, - bool preventScrollThrough = true, - bool destroyOnClose = false, - Duration duration = const Duration(milliseconds: 240), - String? title, - Widget? titleWidget, - bool titleAlignLeft = false, - String? cancelBtn, - Widget? cancel = kPopupActionDefault, - WidgetBuilder? cancelBuilder, - VoidCallback? onCancel, - String? confirmBtn, - Widget? confirm = kPopupActionDefault, - WidgetBuilder? confirmBuilder, - VoidCallback? onConfirm, - bool autoCloseOnCancel = true, - bool autoCloseOnConfirm = true, - TPopupCloseBuilder? closeBuilder = kPopupDefaultClose, - VoidCallback? onCloseBtn, - TPopupHeaderBuilder? headerBuilder = kPopupDefaultHeader, - VoidCallback? onOpen, - VoidCallback? onOpened, - VoidCallback? onClose, - VoidCallback? onClosed, - TPopupVisibleChangeCallback? onVisibleChange, - VoidCallback? onOverlayClick, - }) { - final isBottom = placement == TPopupPlacement.bottom; - final isCenter = placement == TPopupPlacement.center; - final effectiveCloseBuilder = isCenter ? closeBuilder : null; - - return TPopupConfig( - child: child, - placement: placement, - width: width, - height: height, - margin: margin, - radius: radius, - backgroundColor: backgroundColor, - showOverlay: showOverlay, - closeOnOverlayClick: closeOnOverlayClick, - overlayColor: overlayColor, - overlayOpacity: overlayOpacity, - preventScrollThrough: preventScrollThrough, - destroyOnClose: destroyOnClose, - duration: duration, - title: isBottom ? title : null, - titleWidget: isBottom ? titleWidget : null, - titleAlignLeft: isBottom ? titleAlignLeft : false, - cancelBtn: isBottom ? cancelBtn : null, - cancel: isBottom ? cancel : null, - cancelBuilder: isBottom ? cancelBuilder : null, - onCancel: isBottom ? onCancel : null, - confirmBtn: isBottom ? confirmBtn : null, - confirm: isBottom ? confirm : null, - confirmBuilder: isBottom ? confirmBuilder : null, - onConfirm: isBottom ? onConfirm : null, - autoCloseOnCancel: autoCloseOnCancel, - autoCloseOnConfirm: autoCloseOnConfirm, - closeBuilder: effectiveCloseBuilder, - onCloseBtn: isCenter ? onCloseBtn : null, - headerBuilder: isBottom ? headerBuilder : null, - onOpen: onOpen, - onOpened: onOpened, - onClose: onClose, - onClosed: onClosed, - onVisibleChange: onVisibleChange, - onOverlayClick: onOverlayClick, - ); - } - + /// 浮层主体内容(必填)。 final Widget child; + + /// 出现位置,默认 [TPopupPlacement.bottom]。 final TPopupPlacement placement; + + /// 宽度;对 left、right、center 生效。 final double? width; + + /// 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 final double? height; + + /// 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 final EdgeInsets margin; + + /// 内容区圆角,默认主题大圆角。 final double? radius; + + /// 内容区背景色,默认主题容器色。 final Color? backgroundColor; + + /// 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 final bool showOverlay; + + /// 点击蒙层是否关闭(须 [showOverlay] 为 true)。 final bool closeOnOverlayClick; + + /// 蒙层颜色,默认 black54。 final Color? overlayColor; + + /// 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 final double? overlayOpacity; + + /// 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 final bool preventScrollThrough; - /// 为 true 时路由 maintainState 为 false,关闭后丢弃 Popup 路由 State。 + /// 为 true 时 Popup 路由 maintainState 为 false,关闭后不保留路由内 State。 final bool destroyOnClose; + + /// 打开与关闭动画时长(一致)。 final Duration duration; + /// bottom 操作栏中间标题文案。 final String? title; + + /// bottom 操作栏中间标题组件,优先级高于 [title]。 final Widget? titleWidget; + + /// bottom 仅标题行时是否左对齐,默认居中。 final bool titleAlignLeft; + + /// bottom 左侧按钮文案,覆盖默认「取消」。 final String? cancelBtn; + + /// bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。 final Widget? cancel; + + /// bottom 左侧按钮构建器,优先级高于 [cancel]。 final WidgetBuilder? cancelBuilder; + + /// 点击 bottom 左侧按钮回调。 final VoidCallback? onCancel; + + /// bottom 右侧按钮文案,覆盖默认「确定」。 final String? confirmBtn; + + /// bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 final Widget? confirm; + + /// bottom 右侧按钮构建器,优先级高于 [confirm]。 final WidgetBuilder? confirmBuilder; + + /// 点击 bottom 右侧按钮回调。 final VoidCallback? onConfirm; + + /// 点击取消后是否自动关闭,默认 true。 final bool autoCloseOnCancel; + + /// 点击确定后是否自动关闭,默认 true。 final bool autoCloseOnConfirm; - /// center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];自定义见 [TPopupCloseBuilder]。 + /// center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];bottom 与三边忽略。 final TPopupCloseBuilder? closeBuilder; + /// center 点击关闭控件前的回调。 final VoidCallback? onCloseBtn; + /// bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader];自定义见 [TPopupHeaderBuilder]。 final TPopupHeaderBuilder? headerBuilder; + /// 开始打开时回调(路由入栈)。 final VoidCallback? onOpen; + + /// 打开动画结束后回调。 final VoidCallback? onOpened; + + /// 开始关闭时回调。 final VoidCallback? onClose; + + /// 关闭动画结束且路由移除后回调。 final VoidCallback? onClosed; + + /// 显隐变化及触发来源。 final TPopupVisibleChangeCallback? onVisibleChange; + + /// 点击蒙层时回调(在是否关闭判断之前)。 final VoidCallback? onOverlayClick; - /// bottom 左侧是否渲染([cancel] 为 `null` 时隐藏,未传则用默认文案)。 + /// 按 [placement] 裁剪无效字段,得到路由实际使用的配置副本。 + TPopupOptions normalized() { + final isBottom = placement == TPopupPlacement.bottom; + final isCenter = placement == TPopupPlacement.center; + + return TPopupOptions( + child: child, + placement: placement, + width: width, + height: height, + margin: margin, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + title: isBottom ? title : null, + titleWidget: isBottom ? titleWidget : null, + titleAlignLeft: isBottom ? titleAlignLeft : false, + cancelBtn: isBottom ? cancelBtn : null, + cancel: isBottom ? cancel : null, + cancelBuilder: isBottom ? cancelBuilder : null, + onCancel: isBottom ? onCancel : null, + confirmBtn: isBottom ? confirmBtn : null, + confirm: isBottom ? confirm : null, + confirmBuilder: isBottom ? confirmBuilder : null, + onConfirm: isBottom ? onConfirm : null, + autoCloseOnCancel: autoCloseOnCancel, + autoCloseOnConfirm: autoCloseOnConfirm, + closeBuilder: isCenter ? closeBuilder : null, + onCloseBtn: isCenter ? onCloseBtn : null, + headerBuilder: isBottom ? headerBuilder : null, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + } + bool get showCancelSlot => placement == TPopupPlacement.bottom && (cancelBuilder != null || cancel != null); - /// bottom 右侧是否渲染。 bool get showConfirmSlot => placement == TPopupPlacement.bottom && (confirmBuilder != null || confirm != null); - /// 不渲染 bottom 头部(显式 [headerBuilder: null])。 bool get hasNoHeader => placement == TPopupPlacement.bottom && headerBuilder == null; - /// 使用内置操作栏(未传 headerBuilder,占位为 [kPopupDefaultHeader])。 bool get useActionHeader => placement == TPopupPlacement.bottom && isPopupDefaultHeader(headerBuilder) && (showCancelSlot || showConfirmSlot); - /// 自定义头部(非 null 且非默认占位)。 bool get useCustomHeader => placement == TPopupPlacement.bottom && headerBuilder != null && @@ -199,7 +237,6 @@ class TPopupConfig { static bool isActionDefault(Widget? action) => action is TPopupActionDefault; - /// bottom 仅标题行(默认头部占位 + 无 cancel/confirm 槽 + 有 title)。 bool get useTitleOnlyHeader => placement == TPopupPlacement.bottom && isPopupDefaultHeader(headerBuilder) && @@ -212,6 +249,7 @@ class TPopupConfig { !hasNoHeader && (useCustomHeader || useActionHeader || useTitleOnlyHeader); + /// Debug 下检查易误用参数(如 bottom 传 `width`),仅 `debugPrint` 不抛错。 void assertPlacementParams() { assert(() { switch (placement) { diff --git a/tdesign-component/lib/src/components/popup/t_popup_tracker.dart b/tdesign-component/lib/src/components/popup/t_popup_tracker.dart index f8b461f2c..231b04036 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_tracker.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_tracker.dart @@ -1,6 +1,6 @@ part of 't_popup.dart'; -/// 按 Navigator 追踪已打开的 [TPopupHandle] 栈,供 [TPopup.close] 查找栈顶。 +/// 库内:按 [Navigator] 记录 [TPopupHandle] 栈,用于嵌套与 [TPopup.show] 防重复打开。 abstract class TPopupTracker { static final Map> _stacks = {}; diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart index b17e0339a..f4c8e20be 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_types.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart @@ -1,27 +1,31 @@ import 'package:flutter/widgets.dart'; -/// 浮层出现位置。 +/// 浮层从哪个方向出现;决定哪些 [TPopupOptions] 字段生效。 +/// +/// - [top] / [bottom]:纵向滑入,用 `height`、`margin`(bottom 可用 `margin.top` 做日历式留白)。 +/// - [left] / [right]:侧栏,用 `width`、`margin`。 +/// - [center]:居中缩放,用 `closeBuilder` 控制下方关闭按钮;不用 bottom 操作栏字段。 enum TPopupPlacement { - /// 自屏幕顶部滑入;height 与 margin 的 top/left/right 生效。 + /// 自屏幕顶部滑入;`height` 与 `margin` 的 top/left/right 生效。 top, - /// 自屏幕左侧滑入;width 与 margin 的 left/top/bottom 生效。 + /// 自屏幕左侧滑入;`width` 与 margin 的 left/top/bottom 生效。 left, - /// 自屏幕右侧滑入;width 与 margin 的 right/top/bottom 生效。 + /// 自屏幕右侧滑入;`width` 与 margin 的 right/top/bottom 生效。 right, - /// 自屏幕底部滑入;默认带操作栏,height 与 margin 生效。 + /// 自屏幕底部滑入;默认操作栏(取消 | 标题 | 确定),`height`、`margin` 生效。 bottom, - /// 屏幕居中缩放弹出;默认内容下方关闭按钮。 + /// 屏幕居中弹出;默认面板外下方关闭按钮,不用 `title` / `cancel` / `confirm`。 center, } -/// 未传 [cancel]/[confirm] 时的占位 Widget,表示使用默认「取消」「确定」文案。 +/// 未传 [TPopupOptions.cancel] / [confirm] 时的占位,表示渲染默认「取消」「确定」文案。 /// -/// 须显式传 `cancel: null` / `confirm: null` 才能隐藏对应侧;两侧均为 null 且无 -/// Builder 时不渲染 bottom 操作栏。 +/// 要**隐藏**某一侧须写 `cancel: null` 或 `confirm: null`(不是省略参数)。 +/// 两侧都为 `null` 且无 Builder 时,bottom 不显示操作栏(适合 Picker 等自带工具栏)。 class TPopupActionDefault extends StatelessWidget { const TPopupActionDefault({super.key}); @@ -29,10 +33,10 @@ class TPopupActionDefault extends StatelessWidget { Widget build(BuildContext context) => const SizedBox.shrink(); } -/// 与 [TPopupActionDefault] 同一实例,供 [TPopup.show] / [TPopup] 默认参数使用。 +/// 与 [TPopupActionDefault] 同一实例,作为 [TPopupOptions.cancel] / [confirm] 的默认值。 const Widget kPopupActionDefault = TPopupActionDefault(); -/// 自定义 [headerBuilder] 时传入的标题栏数据(已按 cancel/confirm/title 参数组装好 Widget)。 +/// 传给自定义 [TPopupOptions.headerBuilder] 的标题栏数据(库内已组装好各槽 Widget)。 class TPopupHeaderData { const TPopupHeaderData({ this.title, @@ -42,74 +46,75 @@ class TPopupHeaderData { this.onConfirm, }); - /// 中间标题区(可能为 null)。 + /// 中间标题(可为 null)。 final Widget? title; - /// 左侧按钮区(可能为 null,表示该侧已隐藏)。 + /// 左侧区域 Widget(null 表示该侧已隐藏)。 final Widget? cancel; - /// 右侧按钮区(可能为 null)。 + /// 右侧区域 Widget(null 表示该侧已隐藏)。 final Widget? confirm; + /// 点击左侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnCancel] 决定)。 final VoidCallback? onCancel; + + /// 点击右侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnConfirm] 决定)。 final VoidCallback? onConfirm; } -/// bottom 自定义头部构建器。 +/// bottom 完全自定义头部:`Widget Function(context, data)`,优先级高于默认操作栏。 typedef TPopupHeaderBuilder = Widget Function( BuildContext context, TPopupHeaderData data, ); -/// 未传 [headerBuilder] 时的默认参数占位,表示使用内置 bottom 操作栏。 +/// 默认 [headerBuilder] 占位:表示使用内置「取消 | 标题 | 确定」操作栏。 /// -/// **勿直接调用**;仅作为默认参数值。 -/// 与 [headerBuilder: null](不渲染任何头部)不同,须显式区分。 +/// 勿直接调用。与 [headerBuilder: null](完全不显示头部)不同。 Widget kPopupDefaultHeader(BuildContext context, TPopupHeaderData data) { return const SizedBox.shrink(); } -/// 是否为「使用默认操作栏」(未传 [headerBuilder] 时的默认参数值)。 +/// 是否为默认操作栏占位(未自定义 [headerBuilder])。 bool isPopupDefaultHeader(TPopupHeaderBuilder? builder) => builder == kPopupDefaultHeader; -/// center 下方关闭区构建器;[close] 会触发 [onCloseBtn] 并关闭浮层。 +/// center 面板**外下方**关闭区;须调用入参 [close] 才会关层(会走 [onCloseBtn] 等逻辑)。 typedef TPopupCloseBuilder = Widget Function( BuildContext context, VoidCallback close, ); -/// 未传 [closeBuilder] 时的默认参数占位,表示使用内置圆圈关闭图标。 +/// 默认 [closeBuilder] 占位:使用内置圆圈关闭图标(面板外下方)。 /// -/// **勿直接调用**;仅作为 [TPopup.show] / [TPopup] 的默认参数值。 -/// 与 [closeBuilder: null](不渲染关闭区)不同,须显式区分。 +/// 勿直接调用。与 [closeBuilder: null](不显示关闭区)不同。 Widget kPopupDefaultClose(BuildContext context, VoidCallback close) { return const SizedBox.shrink(); } -/// 是否为「使用默认关闭按钮」(未传 [closeBuilder] 时的默认参数值)。 +/// 是否为默认关闭按钮占位。 bool isPopupDefaultClose(TPopupCloseBuilder? builder) => builder == kPopupDefaultClose; -/// 显隐变化触发来源。 +/// 浮层被关闭时的触发来源,见 [TPopupOptions.onVisibleChange]。 enum TPopupTrigger { - /// 点击半透明蒙层。 + /// 点击蒙层(且 [closeOnOverlayClick] 为 true)。 overlay, /// 点击 center 下方关闭控件。 closeBtn, - /// 点击 bottom 操作栏左侧取消。 + /// 点击 bottom 操作栏「取消」。 cancelBtn, - /// 点击 bottom 操作栏右侧确定。 + /// 点击 bottom 操作栏「确定」。 confirmBtn, - /// [TPopupHandle.close]、[TPopup.close] 或系统返回等程序化关闭。 + /// [TPopupHandle.close]、系统返回键等。 programmatic, } -/// 显隐回调:是否可见及触发来源。 +/// 显隐变化:`onVisibleChange(visible, trigger)`。 typedef TPopupVisibleChangeCallback = void Function( bool visible, TPopupTrigger trigger, diff --git a/tdesign-component/test/t_bottom_tab_bar_test.dart b/tdesign-component/test/t_bottom_tab_bar_test.dart index 99cd97514..b64934bcf 100644 --- a/tdesign-component/test/t_bottom_tab_bar_test.dart +++ b/tdesign-component/test/t_bottom_tab_bar_test.dart @@ -64,7 +64,8 @@ void main() { group('TBottomTabBar — iconText 图标颜色 (issue #900)', () { // TC-01: iconText 选中 tab 图标颜色为 brandNormalColor testWidgets('TC-01: 选中 tab 的图标颜色应为 brandNormalColor', (tester) async { - await tester.pumpWidget(_buildTestApp(_buildIconTextTabBar(currentIndex: 0))); + await tester + .pumpWidget(_buildTestApp(_buildIconTextTabBar(currentIndex: 0))); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(TBottomTabBar)); @@ -75,7 +76,8 @@ void main() { expect(icons, isNotEmpty); // 通过 IconTheme 验证选中图标的颜色 - final iconThemes = tester.widgetList(find.byType(IconTheme)).toList(); + final iconThemes = + tester.widgetList(find.byType(IconTheme)).toList(); expect(iconThemes, isNotEmpty, reason: '选中图标应被 IconTheme 包裹以注入颜色'); // 验证至少有一个 IconTheme 的 color 是 brandNormalColor @@ -91,13 +93,15 @@ void main() { // TC-02: iconText 未选中 tab 图标颜色为 textColorPrimary testWidgets('TC-02: 未选中 tab 的图标颜色应为 textColorPrimary', (tester) async { - await tester.pumpWidget(_buildTestApp(_buildIconTextTabBar(currentIndex: 0))); + await tester + .pumpWidget(_buildTestApp(_buildIconTextTabBar(currentIndex: 0))); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(TBottomTabBar)); final expectedColor = TTheme.of(context).textColorPrimary; - final iconThemes = tester.widgetList(find.byType(IconTheme)).toList(); + final iconThemes = + tester.widgetList(find.byType(IconTheme)).toList(); expect(iconThemes, isNotEmpty, reason: '未选中图标应被 IconTheme 包裹以注入颜色'); final unselectedIconTheme = iconThemes.firstWhere( @@ -150,24 +154,29 @@ void main() { await tester.tap(find.text('我的')); await tester.pumpAndSettle(); - final iconThemesAfter = tester.widgetList(find.byType(IconTheme)).toList(); + final iconThemesAfter = + tester.widgetList(find.byType(IconTheme)).toList(); final colors = iconThemesAfter.map((t) => t.data.color).toSet(); - expect(colors, contains(brandColor), reason: '切换后新选中 tab 图标应为 brandNormalColor'); - expect(colors, contains(primaryColor), reason: '切换后旧 tab 图标应为 textColorPrimary'); + expect(colors, contains(brandColor), + reason: '切换后新选中 tab 图标应为 brandNormalColor'); + expect(colors, contains(primaryColor), + reason: '切换后旧 tab 图标应为 textColorPrimary'); }); }); group('TBottomTabBar — icon 类型图标颜色 (issue #900 同类问题)', () { // TC-04: icon 类型选中 tab 图标颜色为 brandNormalColor - testWidgets('TC-04: icon 类型 — 选中 tab 图标颜色应为 brandNormalColor', (tester) async { + testWidgets('TC-04: icon 类型 — 选中 tab 图标颜色应为 brandNormalColor', + (tester) async { await tester.pumpWidget(_buildTestApp(_buildIconTabBar(currentIndex: 0))); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(TBottomTabBar)); final expectedColor = TTheme.of(context).brandNormalColor; - final iconThemes = tester.widgetList(find.byType(IconTheme)).toList(); + final iconThemes = + tester.widgetList(find.byType(IconTheme)).toList(); expect(iconThemes, isNotEmpty); final match = iconThemes.firstWhere( @@ -181,14 +190,16 @@ void main() { }); // TC-05: icon 类型未选中 tab 图标颜色为 textColorPrimary - testWidgets('TC-05: icon 类型 — 未选中 tab 图标颜色应为 textColorPrimary', (tester) async { + testWidgets('TC-05: icon 类型 — 未选中 tab 图标颜色应为 textColorPrimary', + (tester) async { await tester.pumpWidget(_buildTestApp(_buildIconTabBar(currentIndex: 0))); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(TBottomTabBar)); final expectedColor = TTheme.of(context).textColorPrimary; - final iconThemes = tester.widgetList(find.byType(IconTheme)).toList(); + final iconThemes = + tester.widgetList(find.byType(IconTheme)).toList(); final match = iconThemes.firstWhere( (t) => t.data.color == expectedColor, orElse: () => throw TestFailure( @@ -203,7 +214,8 @@ void main() { group('TBottomTabBar — 回归检查', () { // TC-06: 文字颜色不受影响 testWidgets('TC-06: iconText 类型文字颜色回归验证', (tester) async { - await tester.pumpWidget(_buildTestApp(_buildIconTextTabBar(currentIndex: 0))); + await tester + .pumpWidget(_buildTestApp(_buildIconTextTabBar(currentIndex: 0))); await tester.pumpAndSettle(); final BuildContext context = tester.element(find.byType(TBottomTabBar)); @@ -217,14 +229,16 @@ void main() { // 找到 textColor 为 brandColor 的文字(选中) final selectedText = tTexts.firstWhere( (t) => t.textColor == brandColor, - orElse: () => throw TestFailure('未找到 textColor == brandNormalColor 的 TText'), + orElse: () => + throw TestFailure('未找到 textColor == brandNormalColor 的 TText'), ); expect(selectedText.textColor, equals(brandColor)); // 找到 textColor 为 primaryColor 的文字(未选中) final unselectedText = tTexts.firstWhere( (t) => t.textColor == primaryColor, - orElse: () => throw TestFailure('未找到 textColor == textColorPrimary 的 TText'), + orElse: () => + throw TestFailure('未找到 textColor == textColorPrimary 的 TText'), ); expect(unselectedText.textColor, equals(primaryColor)); }); diff --git a/tdesign-component/test/t_picker_test.dart b/tdesign-component/test/t_picker_test.dart index d3da36c71..b5c57eb1d 100644 --- a/tdesign-component/test/t_picker_test.dart +++ b/tdesign-component/test/t_picker_test.dart @@ -399,8 +399,7 @@ void main() { expect(find.byType(ListWheelScrollView), findsNWidgets(3)); }); - testWidgets('初始值非首项 - onChange 返回正确值(多列独立)', - (WidgetTester tester) async { + testWidgets('初始值非首项 - onChange 返回正确值(多列独立)', (WidgetTester tester) async { TPickerValue? captured; const testData = [ [ @@ -438,8 +437,7 @@ void main() { expect(captured!.labels[1], 'B2'); }); - testWidgets('初始值非首项 - 联动模式 onChange 返回正确值', - (WidgetTester tester) async { + testWidgets('初始值非首项 - 联动模式 onChange 返回正确值', (WidgetTester tester) async { TPickerValue? captured; final linkedData = { const TPickerOption(label: '广东省', value: 'GD'): { @@ -478,8 +476,7 @@ void main() { expect(captured!.values[1], 'SZ'); }); - testWidgets('disabled 项修正 - 独立模式滚动不会 crash', - (WidgetTester tester) async { + testWidgets('disabled 项修正 - 独立模式滚动不会 crash', (WidgetTester tester) async { TPickerValue? captured; const testData = [ [ @@ -512,8 +509,7 @@ void main() { } }); - testWidgets('didUpdateWidget - items 变化触发重建', - (WidgetTester tester) async { + testWidgets('didUpdateWidget - items 变化触发重建', (WidgetTester tester) async { const testData1 = [ [TPickerOption(label: 'A', value: 'a')], ]; @@ -548,8 +544,7 @@ void main() { expect(find.byType(TPicker), findsOneWidget); }); - testWidgets('didUpdateWidget - items 变化触发重建', - (WidgetTester tester) async { + testWidgets('didUpdateWidget - items 变化触发重建', (WidgetTester tester) async { const testData = [ [ TPickerOption(label: 'A', value: 'a'), @@ -629,8 +624,7 @@ void main() { expect(find.byType(TPicker), findsOneWidget); }); - testWidgets('联动模式 - 列数变化后能恢复', - (WidgetTester tester) async { + testWidgets('联动模式 - 列数变化后能恢复', (WidgetTester tester) async { final linkedData = { const TPickerOption(label: '广东省', value: 'GD'): { const TPickerOption(label: '深圳市', value: 'SZ'): const [ @@ -679,8 +673,7 @@ void main() { expect(find.byType(ListWheelScrollView), findsNWidgets(3)); }); - testWidgets('联动模式 - 滚动后新列选中首项', - (WidgetTester tester) async { + testWidgets('联动模式 - 滚动后新列选中首项', (WidgetTester tester) async { TPickerValue? captured; final linkedData = { const TPickerOption(label: '广东省', value: 'GD'): { @@ -689,7 +682,8 @@ void main() { TPickerOption(label: '福田区', value: 'FT'), TPickerOption(label: '罗湖区', value: 'LL'), ], - const TPickerOption(label: '广州市', value: 'GZ'): const [], + const TPickerOption(label: '广州市', value: 'GZ'): + const [], }, }; @@ -723,8 +717,7 @@ void main() { expect(captured!.values[2], 'NS'); }); - testWidgets('onLoad 回调 - 滚动时触发', - (WidgetTester tester) async { + testWidgets('onLoad 回调 - 滚动时触发', (WidgetTester tester) async { var loadCallCount = 0; int? capturedColumn; int? capturedRemaining; @@ -765,8 +758,7 @@ void main() { expect(capturedRemaining, greaterThanOrEqualTo(0)); }); - testWidgets('onLoad 回调 - 联动模式 parentValue 正确', - (WidgetTester tester) async { + testWidgets('onLoad 回调 - 联动模式 parentValue 正确', (WidgetTester tester) async { dynamic capturedParentValue; int? capturedColumn; final linkedData = { @@ -915,7 +907,8 @@ void main() { [TPickerOption(label: 'A', value: 'a')], [TPickerOption(label: 'B', value: 'b')], ]; - final result = TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); expect(identical(result, input), true); }); @@ -923,7 +916,8 @@ void main() { final input = { const TPickerOption(label: 'A', value: 'a'): null, }; - final result = TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); expect(identical(result, input), true); }); @@ -937,7 +931,8 @@ void main() { {'label': 'C', 'value': 'c'}, ], ]; - final result = TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); expect(result, isA>>()); expect(result.length, 2); expect(result[0].length, 2); @@ -952,7 +947,8 @@ void main() { ], }, }; - final result = TPickerNormalize.normalizeLinked(input, const TPickerKeys(label: 'label', value: 'value')); + final result = TPickerNormalize.normalizeLinked( + input, const TPickerKeys(label: 'label', value: 'value')); expect(result, isA>()); }); @@ -966,7 +962,8 @@ void main() { ], }, }; - final result = TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); expect(result, isA>()); final keyA = result.keys.first; @@ -1009,8 +1006,7 @@ void main() { expect(find.byType(TPicker), findsOneWidget); }); - testWidgets('disabled 项修正 - 正向查找最近 enabled', - (WidgetTester tester) async { + testWidgets('disabled 项修正 - 正向查找最近 enabled', (WidgetTester tester) async { TPickerValue? captured; const testData = [ [ @@ -1042,8 +1038,7 @@ void main() { } }); - testWidgets('disabled 项修正 - 反向查找最近 enabled', - (WidgetTester tester) async { + testWidgets('disabled 项修正 - 反向查找最近 enabled', (WidgetTester tester) async { TPickerValue? captured; const testData = [ [ @@ -1080,7 +1075,8 @@ void main() { ['北京', '上海', '广州'], ['朝阳区', '浦东'], ]; - final result = TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); expect(result, isA>>()); expect(result[0][0].label, '北京'); expect(result[0][0].value, '北京'); // 纯字符串时 value == label @@ -1088,21 +1084,26 @@ void main() { }); test('TPickerNormalize - 空列表归一化', () { - final result = TPickerNormalize.normalizeColumns([], TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeColumns([], TPickerKeys.defaults); expect(result, isA>>()); expect(result.isEmpty, true); }); test('TPickerNormalize - 空 Map 归一化', () { - final result = - TPickerNormalize.normalizeLinked({}, TPickerKeys.defaults); + final result = TPickerNormalize.normalizeLinked( + {}, TPickerKeys.defaults); expect(result, isA>()); expect(result.isEmpty, true); }); test('TPickerNormalize - List 中包含非 List 元素得到空列', () { - final input = ['not_a_list', ['A', 'B']]; - final result = TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); + final input = [ + 'not_a_list', + ['A', 'B'] + ]; + final result = + TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); expect(result, isA>>()); expect(result[0], isEmpty); // 非 List 元素归一化为空列 expect(result[1].length, 2); @@ -1115,7 +1116,8 @@ void main() { {'name': '广州', 'code': 'GZ'}, ], ]; - const keys = TPickerKeys(label: 'name', value: 'code', disabled: 'readonly'); + const keys = + TPickerKeys(label: 'name', value: 'code', disabled: 'readonly'); final result = TPickerNormalize.normalizeColumns(input, keys); expect(result[0][0].label, '深圳'); expect(result[0][0].value, 'SZ'); @@ -1127,7 +1129,8 @@ void main() { final input = { 'A': ['X', 'Y'], }; - final result = TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); expect(result, isA>()); final keyA = result.keys.first; expect(keyA.label, 'A'); @@ -1141,7 +1144,8 @@ void main() { final input = { 'A': 12345, // 既不是 Map 也不是 List }; - final result = TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); final child = result[result.keys.first]; expect(child, isA>()); expect((child as List).isEmpty, true); @@ -1154,7 +1158,8 @@ void main() { {'label': 'Y', 'value': 'y'}, ], ]; - final result = TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeColumns(input, TPickerKeys.defaults); expect(result[0][0].label, 'X'); expect(result[0][1].label, 'Y'); }); @@ -1163,7 +1168,8 @@ void main() { final input = { null: ['child1'], }; - final result = TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); + final result = + TPickerNormalize.normalizeLinked(input, TPickerKeys.defaults); final key = result.keys.first; expect(key.label, ''); expect(key.value, null); @@ -1250,14 +1256,18 @@ void main() { expect(columns.columns[0][0].label, '北京'); final linked = TPickerLinked.fromRaw(const { - '广东': {'深圳': ['南山']}, + '广东': { + '深圳': ['南山'] + }, }); expect(linked.tree.keys.first.label, '广东'); }); test('TPickerKeys - 值相同的不同实例 hashCode 一致', () { - const a = TPickerKeys(label: 'x', value: 'y', disabled: 'd', children: 'c'); - const b = TPickerKeys(label: 'x', value: 'y', disabled: 'd', children: 'c'); + const a = + TPickerKeys(label: 'x', value: 'y', disabled: 'd', children: 'c'); + const b = + TPickerKeys(label: 'x', value: 'y', disabled: 'd', children: 'c'); expect(a == b, true); expect(a.hashCode, b.hashCode); }); @@ -1292,7 +1302,8 @@ void main() { Text('selected: $selected'), TPicker( items: const TPickerColumns(testData), - onChange: (v) => setState(() => selected = v.values.first as String), + onChange: (v) => + setState(() => selected = v.values.first as String), ), ], ); @@ -1349,7 +1360,8 @@ void main() { TPicker( items: TPickerLinked(linkedData), initialValue: const ['GD', 'SZ', 'NS'], - onChange: (v) => setState(() => selected = v.labels.join(' / ')), + onChange: (v) => + setState(() => selected = v.labels.join(' / ')), ), ], ); @@ -1413,8 +1425,7 @@ void main() { expect(a == c, false); }); - testWidgets('工具栏 - 默认显示取消/确认按钮', - (WidgetTester tester) async { + testWidgets('工具栏 - 默认显示取消/确认按钮', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -1435,8 +1446,7 @@ void main() { expect(find.text('确认'), findsOneWidget); }); - testWidgets('工具栏 - 设置 title 后中部显示标题', - (WidgetTester tester) async { + testWidgets('工具栏 - 设置 title 后中部显示标题', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -1459,8 +1469,7 @@ void main() { expect(find.text('请选择'), findsOneWidget); }); - testWidgets('工具栏 - 点击取消触发 onCancel', - (WidgetTester tester) async { + testWidgets('工具栏 - 点击取消触发 onCancel', (WidgetTester tester) async { var cancelled = false; await tester.pumpWidget( MaterialApp( @@ -1481,8 +1490,7 @@ void main() { expect(cancelled, true); }); - testWidgets('工具栏 - 点击确认触发 onConfirm 并携带选中值', - (WidgetTester tester) async { + testWidgets('工具栏 - 点击确认触发 onConfirm 并携带选中值', (WidgetTester tester) async { TPickerValue? confirmedValue; await tester.pumpWidget( MaterialApp( @@ -1533,8 +1541,7 @@ void main() { expect(find.text('确认'), findsNothing); }); - testWidgets('工具栏 - cancel 自定义插槽(图标)', - (WidgetTester tester) async { + testWidgets('工具栏 - cancel 自定义插槽(图标)', (WidgetTester tester) async { var cancelled = false; await tester.pumpWidget( MaterialApp( @@ -1558,8 +1565,7 @@ void main() { expect(cancelled, true); }); - testWidgets('工具栏 - confirm 自定义插槽(图标)', - (WidgetTester tester) async { + testWidgets('工具栏 - confirm 自定义插槽(图标)', (WidgetTester tester) async { TPickerValue? confirmedValue; await tester.pumpWidget( MaterialApp( @@ -1590,8 +1596,7 @@ void main() { expect(confirmedValue!.labels.first, 'Y'); }); - testWidgets('工具栏 - titleWidget 自定义标题插槽', - (WidgetTester tester) async { + testWidgets('工具栏 - titleWidget 自定义标题插槽', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Scaffold( diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart index 3b07c29f1..e4d3e4087 100644 --- a/tdesign-component/test/t_popup_coverage_test.dart +++ b/tdesign-component/test/t_popup_coverage_test.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tdesign_flutter/src/components/popup/_popup_layout.dart'; -import 'package:tdesign_flutter/src/components/popup/t_popup_config.dart'; -import 'package:tdesign_flutter/src/components/popup/t_popup_types.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'helpers/popup_test_helpers.dart'; @@ -16,15 +14,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - title: '仅标题行', - cancel: null, - confirm: null, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + title: '仅标题行', + cancel: null, + confirm: null, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -37,16 +35,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - title: '左对齐标题', - titleAlignLeft: true, - cancel: null, - confirm: null, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + title: '左对齐标题', + titleAlignLeft: true, + cancel: null, + confirm: null, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -57,16 +55,13 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - confirm: null, - onCancel: () => TPopup.close( - tester.element(find.text('open')), - ), - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + confirm: null, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -78,16 +73,13 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - cancel: null, - onConfirm: () => TPopup.close( - tester.element(find.text('open')), - ), - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + cancel: null, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -101,17 +93,17 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.center, - width: 160, - height: 120, - closeBuilder: (_, close) => TextButton( - onPressed: close, - child: const Text('builder关闭'), - ), - child: const SizedBox(height: 80, width: 120), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 160, + height: 120, + closeBuilder: (_, close) => TextButton( + onPressed: close, + child: const Text('builder关闭'), + ), + child: const SizedBox(height: 80, width: 120)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -127,14 +119,14 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.center, - width: 120, - height: 120, - onCloseBtn: () => closeBtnCount++, - child: const SizedBox(height: 80, width: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 120, + height: 120, + onCloseBtn: () => closeBtnCount++, + child: const SizedBox(height: 80, width: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -147,13 +139,13 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, - child: const SizedBox(height: 80, width: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + cancel: null, + confirm: null, + child: const SizedBox(height: 80, width: 200)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -164,11 +156,11 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.top, - child: const SizedBox(height: 60, width: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.top, + child: const SizedBox(height: 60, width: 200)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -179,14 +171,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.center, - width: 100, - height: 100, - closeBuilder: null, - child: const SizedBox(height: 80, width: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 100, + height: 100, + closeBuilder: null, + child: const SizedBox(height: 80, width: 80)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -199,14 +191,14 @@ void main() { await openPopup( tester, onPressed: () { - handle = TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 80, - cancel: null, - confirm: null, - child: const SizedBox(height: 40), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + cancel: null, + confirm: null, + child: const SizedBox(height: 40)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -266,10 +258,10 @@ void main() { }); }); - group('TPopupConfig 覆盖率补充', () { + group('TPopupOptions 覆盖率补充', () { test('hasBuiltInHeader 识别 titleWidget', () { expect( - TPopupConfig.create( + TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, titleWidget: const Text('w'), @@ -281,13 +273,13 @@ void main() { }); test('isActionDefault 识别占位 Widget', () { - expect(TPopupConfig.isActionDefault(kPopupActionDefault), isTrue); - expect(TPopupConfig.isActionDefault(const Text('x')), isFalse); - expect(TPopupConfig.isActionDefault(null), isFalse); + expect(TPopupOptions.isActionDefault(kPopupActionDefault), isTrue); + expect(TPopupOptions.isActionDefault(const Text('x')), isFalse); + expect(TPopupOptions.isActionDefault(null), isFalse); }); test('useCustomHeader 与 useTitleOnlyHeader 互斥于 useActionHeader', () { - final custom = TPopupConfig.create( + final custom = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, headerBuilder: (_, __) => const Text('h'), @@ -295,7 +287,7 @@ void main() { expect(custom.useCustomHeader, isTrue); expect(custom.useActionHeader, isFalse); - final titleOnly = TPopupConfig.create( + final titleOnly = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, title: '仅标题', @@ -313,7 +305,8 @@ void main() { expect(isPopupDefaultClose(null), isFalse); }); - testWidgets('kPopupDefaultHeader / kPopupDefaultClose 占位函数可调用', (tester) async { + testWidgets('kPopupDefaultHeader / kPopupDefaultClose 占位函数可调用', + (tester) async { await tester.pumpWidget( MaterialApp( home: Builder( @@ -333,7 +326,7 @@ void main() { test('assertPlacementParams 覆盖 width 与 top 操作栏提示', () { expect( - () => TPopupConfig.create( + () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, width: 200, @@ -341,7 +334,7 @@ void main() { returnsNormally, ); expect( - () => TPopupConfig.create( + () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, onCancel: () {}, @@ -349,7 +342,7 @@ void main() { returnsNormally, ); expect( - () => TPopupConfig.create( + () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, height: 100, @@ -358,7 +351,7 @@ void main() { returnsNormally, ); expect( - () => TPopupConfig.create( + () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, onCancel: () {}, @@ -369,30 +362,31 @@ void main() { }); group('TPopup 覆盖率深化', () { - testWidgets('headerBuilder 透传 titleWidget 与 cancelBuilder 槽位', (tester) async { + testWidgets('headerBuilder 透传 titleWidget 与 cancelBuilder 槽位', + (tester) async { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - titleWidget: const Text('头Widget'), - cancelBuilder: (_) => const Text('builder左'), - confirmBuilder: (_) => const Text('builder右'), - headerBuilder: (_, data) => Column( - children: [ - if (data.title != null) data.title!, - Row( - children: [ - if (data.cancel != null) data.cancel!, - if (data.confirm != null) data.confirm!, - ], - ), - ], - ), - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + titleWidget: const Text('头Widget'), + cancelBuilder: (_) => const Text('builder左'), + confirmBuilder: (_) => const Text('builder右'), + headerBuilder: (_, data) => Column( + children: [ + if (data.title != null) data.title!, + Row( + children: [ + if (data.cancel != null) data.cancel!, + if (data.confirm != null) data.confirm!, + ], + ), + ], + ), + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -405,21 +399,21 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - cancel: const Text('左槽Widget'), - confirm: const Text('右槽Widget'), - headerBuilder: (_, data) => Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (data.cancel != null) data.cancel!, - if (data.confirm != null) data.confirm!, - ], - ), - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + cancel: const Text('左槽Widget'), + confirm: const Text('右槽Widget'), + headerBuilder: (_, data) => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (data.cancel != null) data.cancel!, + if (data.confirm != null) data.confirm!, + ], + ), + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -431,16 +425,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - cancel: const Text('自定义左'), - confirm: const Text('自定义右'), - onCancel: () {}, - onConfirm: () {}, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + cancel: const Text('自定义左'), + confirm: const Text('自定义右'), + onCancel: () {}, + onConfirm: () {}, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -456,18 +450,18 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 160, - title: '标题', - onVisibleChange: (visible, trigger) { - if (!visible) { - hideTriggers.add(trigger); - } - }, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + title: '标题', + onVisibleChange: (visible, trigger) { + if (!visible) { + hideTriggers.add(trigger); + } + }, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -479,17 +473,17 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 160, - onVisibleChange: (visible, trigger) { - if (!visible) { - hideTriggers.add(trigger); - } - }, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + onVisibleChange: (visible, trigger) { + if (!visible) { + hideTriggers.add(trigger); + } + }, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -500,18 +494,18 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: hostContext, - placement: TPopupPlacement.center, - width: 120, - height: 120, - onVisibleChange: (visible, trigger) { - if (!visible) { - hideTriggers.add(trigger); - } - }, - child: const SizedBox(height: 80, width: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 120, + height: 120, + onVisibleChange: (visible, trigger) { + if (!visible) { + hideTriggers.add(trigger); + } + }, + child: const SizedBox(height: 80, width: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -521,38 +515,37 @@ void main() { }); testWidgets('Popup 内嵌套 show 可再开一层且先关内层', (tester) async { - late BuildContext outerContext; - late BuildContext innerContext; + TPopupHandle? outerHandle; + TPopupHandle? innerHandle; await openPopup( tester, onPressed: () { - outerContext = tester.element(find.text('open')); - TPopup.show( - context: outerContext, - placement: TPopupPlacement.bottom, - height: 200, - cancel: null, - confirm: null, - child: Builder( - builder: (ctx) { - return ElevatedButton( - onPressed: () { - innerContext = ctx; - TPopup.show( - context: innerContext, - placement: TPopupPlacement.bottom, - height: 120, - cancel: null, - confirm: null, - child: const Text('内层'), + outerHandle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + cancel: null, + confirm: null, + child: Builder( + builder: (ctx) { + return ElevatedButton( + onPressed: () { + innerHandle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 120, + cancel: null, + confirm: null, + child: const Text('内层'), + ), + ).show(ctx); + }, + child: const Text('开内层'), ); }, - child: const Text('开内层'), - ); - }, - ), - ); + )), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -562,32 +555,31 @@ void main() { await tester.pumpAndSettle(); expect(find.text('内层'), findsOneWidget); - TPopup.close(innerContext); + innerHandle!.close(); await tester.pumpAndSettle(); expect(find.text('内层'), findsNothing); expect(find.text('开内层'), findsOneWidget); - TPopup.close(outerContext); + outerHandle!.close(); await tester.pumpAndSettle(); }); testWidgets('preventScrollThrough 为 false 仍可打开关闭', (tester) async { - late BuildContext hostContext; + TPopupHandle? handle; await openPopup( tester, onPressed: () { - hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 120, - preventScrollThrough: false, - child: const SizedBox(height: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 120, + preventScrollThrough: false, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); }); @@ -595,21 +587,20 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.center, - width: 100, - height: 100, - radius: 4, - backgroundColor: Colors.red, - child: const SizedBox(height: 60, width: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 100, + height: 100, + radius: 4, + backgroundColor: Colors.red, + child: const SizedBox(height: 60, width: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); - final hasRedPanel = tester - .widgetList(find.byType(Container)) - .any((c) { + final hasRedPanel = + tester.widgetList(find.byType(Container)).any((c) { final d = c.decoration; return d is BoxDecoration && d.color == Colors.red; }); @@ -617,26 +608,25 @@ void main() { }); testWidgets('bottom margin.bottom 与无 overlay 仍可关闭', (tester) async { - late BuildContext hostContext; + TPopupHandle? handle; await openPopup( tester, onPressed: () { - hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 100, - margin: const EdgeInsets.only(bottom: 16), - showOverlay: false, - closeOnOverlayClick: false, - cancel: null, - confirm: null, - child: const SizedBox(height: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + margin: const EdgeInsets.only(bottom: 16), + showOverlay: false, + closeOnOverlayClick: false, + cancel: null, + confirm: null, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); }); }); @@ -654,7 +644,9 @@ void main() { MaterialApp( home: Scaffold( body: Stack( - children: [layout.wrapPositioned(child: const SizedBox(height: 1))], + children: [ + layout.wrapPositioned(child: const SizedBox(height: 1)) + ], ), ), ), diff --git a/tdesign-component/test/t_popup_layout_test.dart b/tdesign-component/test/t_popup_layout_test.dart index c06eb0be7..fbe0abc96 100644 --- a/tdesign-component/test/t_popup_layout_test.dart +++ b/tdesign-component/test/t_popup_layout_test.dart @@ -17,7 +17,9 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Stack(children: [layout.wrapPositioned(child: const SizedBox(height: 50))]), + body: Stack(children: [ + layout.wrapPositioned(child: const SizedBox(height: 50)) + ]), ), ), ); @@ -36,7 +38,9 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Stack(children: [layout.wrapPositioned(child: const SizedBox(height: 50))]), + body: Stack(children: [ + layout.wrapPositioned(child: const SizedBox(height: 50)) + ]), ), ), ); @@ -55,7 +59,9 @@ void main() { MaterialApp( home: Scaffold( body: Stack( - children: [layout.wrapPositioned(child: const SizedBox(height: 1))], + children: [ + layout.wrapPositioned(child: const SizedBox(height: 1)) + ], ), ), ), @@ -76,7 +82,8 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Stack(children: [layout.wrapPositioned(child: const SizedBox())]), + body: Stack( + children: [layout.wrapPositioned(child: const SizedBox())]), ), ), ); @@ -97,13 +104,15 @@ void main() { await tester.pumpWidget( MaterialApp( home: Scaffold( - body: Stack(children: [layout.wrapPositioned(child: const SizedBox())]), + body: Stack( + children: [layout.wrapPositioned(child: const SizedBox())]), ), ), ); expect(find.byType(Center), findsOneWidget); final sizedBoxes = tester.widgetList( - find.descendant(of: find.byType(Center), matching: find.byType(SizedBox)), + find.descendant( + of: find.byType(Center), matching: find.byType(SizedBox)), ); final sizedBox = sizedBoxes.firstWhere((w) => w.width == 200); expect(sizedBox.height, 150); diff --git a/tdesign-component/test/t_popup_config_test.dart b/tdesign-component/test/t_popup_options_test.dart similarity index 55% rename from tdesign-component/test/t_popup_config_test.dart rename to tdesign-component/test/t_popup_options_test.dart index 884defd1f..7d248fc65 100644 --- a/tdesign-component/test/t_popup_config_test.dart +++ b/tdesign-component/test/t_popup_options_test.dart @@ -1,166 +1,165 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tdesign_flutter/src/components/popup/t_popup_config.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; void main() { - group('TPopupConfig', () { - test('create 默认 placement 为 bottom', () { - final config = TPopupConfig.create(child: const SizedBox()); - expect(config.placement, TPopupPlacement.bottom); + group('TPopupOptions', () { + test('默认 placement 为 bottom', () { + final options = TPopupOptions(child: const SizedBox()).normalized(); + expect(options.placement, TPopupPlacement.bottom); }); test('bottom 默认渲染操作栏', () { - final config = TPopupConfig.create( + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - ); - expect(config.useActionHeader, isTrue); - expect(config.showCancelSlot, isTrue); - expect(config.showConfirmSlot, isTrue); - expect(config.hasBuiltInHeader, isTrue); + ).normalized(); + expect(options.useActionHeader, isTrue); + expect(options.showCancelSlot, isTrue); + expect(options.showConfirmSlot, isTrue); + expect(options.hasBuiltInHeader, isTrue); }); test('cancel 与 confirm 均为 null 时不渲染操作栏', () { - final config = TPopupConfig.create( + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, cancel: null, confirm: null, - ); - expect(config.useActionHeader, isFalse); - expect(config.showCancelSlot, isFalse); - expect(config.showConfirmSlot, isFalse); + ).normalized(); + expect(options.useActionHeader, isFalse); + expect(options.showCancelSlot, isFalse); + expect(options.showConfirmSlot, isFalse); }); - test('create 忽略 bottom 的 closeBuilder', () { - final config = TPopupConfig.create( + test('normalized 忽略 bottom 的 closeBuilder', () { + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, closeBuilder: (_, __) => const Text('x'), onCancel: () {}, - ); - expect(config.closeBuilder, isNull); - expect(config.useActionHeader, isTrue); + ).normalized(); + expect(options.closeBuilder, isNull); + expect(options.useActionHeader, isTrue); }); - test('create center 默认 closeBuilder', () { - final config = TPopupConfig.create( + test('center 默认 closeBuilder', () { + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, - ); - expect(isPopupDefaultClose(config.closeBuilder), isTrue); + ).normalized(); + expect(isPopupDefaultClose(options.closeBuilder), isTrue); }); - test('create center closeBuilder null 无关闭区', () { - final config = TPopupConfig.create( + test('center closeBuilder null 无关闭区', () { + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, closeBuilder: null, - ); - expect(config.closeBuilder, isNull); + ).normalized(); + expect(options.closeBuilder, isNull); }); - test('create top 剥离 title 与 headerBuilder', () { - final config = TPopupConfig.create( + test('top 剥离 title 与 headerBuilder', () { + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, title: '标题', headerBuilder: (_, __) => const Text('h'), onCancel: () {}, - ); - expect(config.title, isNull); - expect(config.headerBuilder, isNull); - expect(config.useActionHeader, isFalse); - expect(config.hasBuiltInHeader, isFalse); + ).normalized(); + expect(options.title, isNull); + expect(options.headerBuilder, isNull); + expect(options.useActionHeader, isFalse); + expect(options.hasBuiltInHeader, isFalse); }); test('headerBuilder null 表示无头部', () { - final config = TPopupConfig.create( + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, title: '标题', headerBuilder: null, - ); - expect(config.hasNoHeader, isTrue); - expect(config.hasBuiltInHeader, isFalse); + ).normalized(); + expect(options.hasNoHeader, isTrue); + expect(options.hasBuiltInHeader, isFalse); }); test('hasBuiltInHeader 识别 title 与 headerBuilder', () { expect( - TPopupConfig.create( + TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, title: '标题', cancel: null, confirm: null, - ).hasBuiltInHeader, + ).normalized().hasBuiltInHeader, isTrue, ); expect( - TPopupConfig.create( + TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, title: '标题', - ).useActionHeader, + ).normalized().useActionHeader, isTrue, ); expect( - TPopupConfig.create( + TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, headerBuilder: (_, __) => const Text('h'), - ).hasBuiltInHeader, + ).normalized().hasBuiltInHeader, isTrue, ); expect( - TPopupConfig.create( + TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, - ).hasBuiltInHeader, + ).normalized().hasBuiltInHeader, isFalse, ); }); test('left/right 剥离 closeBuilder 与 onCloseBtn', () { - final left = TPopupConfig.create( + final left = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.left, closeBuilder: (_, __) => const Text('x'), onCloseBtn: () {}, - ); + ).normalized(); expect(left.closeBuilder, isNull); expect(left.onCloseBtn, isNull); }); test('useCustomHeader 为 true 时 hasBuiltInHeader', () { - final config = TPopupConfig.create( + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, headerBuilder: (_, __) => const SizedBox(), - ); - expect(config.useCustomHeader, isTrue); - expect(config.hasBuiltInHeader, isTrue); + ).normalized(); + expect(options.useCustomHeader, isTrue); + expect(options.hasBuiltInHeader, isTrue); }); test('assertPlacementParams 非 bottom 带 cancel 槽位提示', () { - final config = TPopupConfig( + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, cancel: kPopupActionDefault, confirm: kPopupActionDefault, ); - expect(() => config.assertPlacementParams(), returnsNormally); + expect(() => options.assertPlacementParams(), returnsNormally); }); test('assertPlacementParams 在 debug 模式不抛错', () { - final config = TPopupConfig.create( + final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.left, height: 100, width: 200, - ); - expect(() => config.assertPlacementParams(), returnsNormally); + ).normalized(); + expect(() => options.assertPlacementParams(), returnsNormally); }); }); } diff --git a/tdesign-component/test/t_popup_route_test.dart b/tdesign-component/test/t_popup_route_test.dart index d26000c97..8e9f69191 100644 --- a/tdesign-component/test/t_popup_route_test.dart +++ b/tdesign-component/test/t_popup_route_test.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:tdesign_flutter/src/components/popup/_popup_route.dart'; -import 'package:tdesign_flutter/src/components/popup/t_popup_config.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'helpers/popup_test_helpers.dart'; @@ -19,10 +18,10 @@ void main() { Builder( builder: (context) { route = TPopupNavigatorRoute( - config: TPopupConfig.create( + options: TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - ), + ).normalized(), onCloseWithTrigger: (_, [__]) {}, ); return route.buildPage( @@ -41,13 +40,13 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 120, - preventScrollThrough: true, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 120, + preventScrollThrough: true, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -83,16 +82,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 120, - showOverlay: false, - preventScrollThrough: true, - cancel: null, - confirm: null, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 120, + showOverlay: false, + preventScrollThrough: true, + cancel: null, + confirm: null, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -105,22 +104,23 @@ void main() { testWidgets('fireCloseStart 仅触发一次 onClose', (tester) async { var closeCount = 0; late BuildContext hostContext; + TPopupHandle? handle; await openPopup( tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 100, - onClose: () => closeCount++, - child: const SizedBox(height: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + onClose: () => closeCount++, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); expect(closeCount, 1); }); diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart index 6dfe52a9e..2f7b2f769 100644 --- a/tdesign-component/test/t_popup_test.dart +++ b/tdesign-component/test/t_popup_test.dart @@ -25,14 +25,14 @@ void main() { resource: resource, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 200, - onCancel: () => cancelCount++, - onConfirm: () => confirmCount++, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + onCancel: () => cancelCount++, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -47,13 +47,13 @@ void main() { tester, resource: resource, onPressed: () { - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 200, - onConfirm: () => confirmCount++, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -71,24 +71,25 @@ void main() { PopupTestResourceDelegate.zh(), PopupTestResourceDelegate.en(), ]) { + TPopupHandle? handle; await openPopup( tester, resource: resource, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 160, - onCancel: () {}, - child: const SizedBox(height: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + onCancel: () {}, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); expect(find.text(resource.cancelText), findsOneWidget); expect(find.text(resource.confirmText), findsOneWidget); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); } }); @@ -99,19 +100,20 @@ void main() { var openCount = 0; var openedCount = 0; late BuildContext hostContext; + TPopupHandle? handle; await openPopup( tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 120, - onOpen: () => openCount++, - onOpened: () => openedCount++, - child: const SizedBox(height: 80), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 120, + onOpen: () => openCount++, + onOpened: () => openedCount++, + child: const SizedBox(height: 80)), + ).show(hostContext); }, ); await tester.pump(); @@ -119,12 +121,11 @@ void main() { await tester.pumpAndSettle(); expect(openedCount, 1); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); }); - testWidgets('handle.close 与 TPopup.close 触发 onClose / onClosed', - (tester) async { + testWidgets('handle.close 触发 onClose / onClosed', (tester) async { var closeCount = 0; var closedCount = 0; var visibleChanges = []; @@ -135,15 +136,15 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 120, - onClose: () => closeCount++, - onClosed: () => closedCount++, - onVisibleChange: (v, _) => visibleChanges.add(v), - child: const SizedBox(height: 80), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 120, + onClose: () => closeCount++, + onClosed: () => closedCount++, + onVisibleChange: (v, _) => visibleChanges.add(v), + child: const SizedBox(height: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -164,13 +165,13 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 100, - onClose: () => closeCount++, - child: const SizedBox(height: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + onClose: () => closeCount++, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -192,28 +193,29 @@ void main() { for (final placement in placements) { testWidgets('$placement 可正常打开关闭', (tester) async { late BuildContext hostContext; + TPopupHandle? handle; await openPopup( tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: placement, - height: placement == TPopupPlacement.left || - placement == TPopupPlacement.right - ? null - : 120, - width: placement == TPopupPlacement.top || - placement == TPopupPlacement.bottom - ? null - : 200, - child: const SizedBox(height: 60, width: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: placement, + height: placement == TPopupPlacement.left || + placement == TPopupPlacement.right + ? null + : 120, + width: placement == TPopupPlacement.top || + placement == TPopupPlacement.bottom + ? null + : 200, + child: const SizedBox(height: 60, width: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); expect(find.byType(Stack), findsWidgets); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); }); } @@ -229,15 +231,15 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 200, - title: '标题', - onCancel: () => cancelCount++, - onConfirm: () => confirmCount++, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + title: '标题', + onCancel: () => cancelCount++, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -249,13 +251,13 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 200, - onConfirm: () => confirmCount++, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + onConfirm: () => confirmCount++, + child: const SizedBox(height: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -268,14 +270,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 200, - autoCloseOnCancel: false, - onCancel: () {}, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + autoCloseOnCancel: false, + onCancel: () {}, + child: const SizedBox(height: 80)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -288,14 +290,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 200, - autoCloseOnConfirm: false, - onConfirm: () {}, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + autoCloseOnConfirm: false, + onConfirm: () {}, + child: const SizedBox(height: 80)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -308,14 +310,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 180, - title: '不应出现', - headerBuilder: null, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + title: '不应出现', + headerBuilder: null, + child: const SizedBox(height: 80)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -327,15 +329,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 180, - title: '仅标题', - cancel: null, - confirm: null, - child: const SizedBox(height: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + title: '仅标题', + cancel: null, + confirm: null, + child: const SizedBox(height: 80)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -349,14 +351,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - title: '传入标题', - headerBuilder: (_, data) => Text('自定义:${data.title}'), - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + title: '传入标题', + headerBuilder: (_, data) => Text('自定义:${data.title}'), + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -370,13 +372,13 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.center, - width: 120, - height: 120, - child: const SizedBox(height: 80, width: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 120, + height: 120, + child: const SizedBox(height: 80, width: 80)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -389,13 +391,13 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.center, - width: 240, - height: 240, - child: const SizedBox.expand(), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 240, + height: 240, + child: const SizedBox.expand()), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -406,14 +408,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.center, - width: 120, - height: 120, - closeBuilder: null, - child: const SizedBox(height: 80, width: 80), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 120, + height: 120, + closeBuilder: null, + child: const SizedBox(height: 80, width: 80)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -424,16 +426,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 160, - cancelBuilder: (_) => const Text('自定义取消'), - confirmBuilder: (_) => const Text('自定义确认'), - onCancel: () {}, - onConfirm: () {}, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + cancelBuilder: (_) => const Text('自定义取消'), + confirmBuilder: (_) => const Text('自定义确认'), + onCancel: () {}, + onConfirm: () {}, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -451,13 +453,13 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 100, - onClosed: () => overlayClose++, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + onClosed: () => overlayClose++, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -469,19 +471,20 @@ void main() { testWidgets('closeOnOverlayClick 为 false 点击蒙层不关闭', (tester) async { late BuildContext hostContext; + TPopupHandle? handle; await openPopup( tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 100, - closeOnOverlayClick: false, - onCancel: () {}, - onOverlayClick: () {}, - child: const SizedBox(height: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + closeOnOverlayClick: false, + onCancel: () {}, + onOverlayClick: () {}, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -489,7 +492,7 @@ void main() { await tester.tapAt(const Offset(10, 10)); await tester.pump(); expect(find.text('取消'), findsOneWidget); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); }); @@ -497,14 +500,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 100, - showOverlay: false, - preventScrollThrough: true, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + showOverlay: false, + preventScrollThrough: true, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -518,14 +521,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 80, - overlayColor: Colors.red, - overlayOpacity: 0.5, - child: const SizedBox(height: 40), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + overlayColor: Colors.red, + overlayOpacity: 0.5, + child: const SizedBox(height: 40)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -536,12 +539,12 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - margin: const EdgeInsets.only(top: 80), - child: const SizedBox(height: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + margin: const EdgeInsets.only(top: 80), + child: const SizedBox(height: 200)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -552,11 +555,13 @@ void main() { testWidgets('initialVisible 自动打开', (tester) async { await tester.pumpWidget( wrapPopupTest( - const TPopup( + TPopup( initialVisible: true, - placement: TPopupPlacement.bottom, - height: 100, - child: SizedBox(height: 60), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + child: SizedBox(height: 60), + ), ), ), ); @@ -568,11 +573,13 @@ void main() { testWidgets('dispose 时关闭弹层', (tester) async { await tester.pumpWidget( wrapPopupTest( - const TPopup( + TPopup( initialVisible: true, - placement: TPopupPlacement.bottom, - height: 100, - child: SizedBox(height: 60), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + child: SizedBox(height: 60), + ), ), ), ); @@ -589,18 +596,18 @@ void main() { tester, onPressed: () { final ctx = tester.element(find.text('open')); - first = TPopup.show( - context: ctx, - placement: TPopupPlacement.bottom, - height: 80, - child: const SizedBox(height: 40), - ); - final second = TPopup.show( - context: ctx, - placement: TPopupPlacement.bottom, - height: 80, - child: const SizedBox(height: 40), - ); + first = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + child: const SizedBox(height: 40)), + ).show(ctx); + final second = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + child: const SizedBox(height: 40)), + ).show(ctx); expect(second.isShowing, isTrue); expect(identical(first, second), isTrue); }, @@ -618,13 +625,13 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 100, - onClosed: () => closedCount++, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + onClosed: () => closedCount++, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -639,13 +646,13 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.top, - height: 120, - title: '顶部标题', - child: const Text('内容'), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.top, + height: 120, + title: '顶部标题', + child: const Text('内容')), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -659,21 +666,22 @@ void main() { TPopupPlacement.right, ]) { late BuildContext hostContext; + TPopupHandle? handle; await openPopup( tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: placement, - width: 240, - child: const SizedBox(height: 40), - ); + handle = TPopup( + options: TPopupOptions( + placement: placement, + width: 240, + child: const SizedBox(height: 40)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); expect(find.byType(Expanded), findsOneWidget); - TPopup.close(hostContext); + handle!.close(); await tester.pumpAndSettle(); } }); @@ -682,15 +690,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 180, - titleWidget: const Text('Widget标题'), - cancelBtn: '左', - confirmBtn: '右', - child: const SizedBox(height: 40), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + titleWidget: const Text('Widget标题'), + cancelBtn: '左', + confirmBtn: '右', + child: const SizedBox(height: 40)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -705,18 +713,18 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.center, - width: 140, - destroyOnClose: true, - closeBuilder: (_, close) => GestureDetector( - onTap: close, - child: const Text('关'), - ), - onCloseBtn: () {}, - child: const SizedBox(height: 60, width: 120), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 140, + destroyOnClose: true, + closeBuilder: (_, close) => GestureDetector( + onTap: close, + child: const Text('关'), + ), + onCloseBtn: () {}, + child: const SizedBox(height: 60, width: 120)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -731,13 +739,13 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 100, - onOverlayClick: () => overlayClick++, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + onOverlayClick: () => overlayClick++, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -746,40 +754,17 @@ void main() { expect(overlayClick, 1); }); - testWidgets('TPopup.close 无 Popup 时不 pop 当前页', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: TTheme( - data: TThemeData.defaultData(), - child: Scaffold( - body: Builder( - builder: (context) { - return ElevatedButton( - onPressed: () => TPopup.close(context), - child: const Text('close'), - ); - }, - ), - ), - ), - ), - ); - await tester.tap(find.text('close')); - await tester.pump(); - expect(find.text('close'), findsOneWidget); - }); - testWidgets('show 返回的 handle 关闭后 isShowing 为 false', (tester) async { TPopupHandle? handle; await openPopup( tester, onPressed: () { - handle = TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 80, - child: const SizedBox(height: 40), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + child: const SizedBox(height: 40)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -798,17 +783,17 @@ void main() { await openPopup( tester, onPressed: () { - handle = TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 100, - onVisibleChange: (v, t) { - if (!v) { - hideTrigger = t; - } - }, - child: const SizedBox(height: 60), - ); + handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + onVisibleChange: (v, t) { + if (!v) { + hideTrigger = t; + } + }, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); @@ -825,17 +810,17 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 120, - onVisibleChange: (v, t) { - if (!v) { - hideTrigger = t; - } - }, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 120, + onVisibleChange: (v, t) { + if (!v) { + hideTrigger = t; + } + }, + child: const SizedBox(height: 60)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -852,15 +837,15 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - first = TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 80, - destroyOnClose: true, - cancel: null, - confirm: null, - child: const SizedBox(height: 40), - ); + first = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + destroyOnClose: true, + cancel: null, + confirm: null, + child: const SizedBox(height: 40)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -872,15 +857,15 @@ void main() { await openPopup( tester, onPressed: () { - second = TPopup.show( - context: hostContext, - placement: TPopupPlacement.bottom, - height: 80, - destroyOnClose: true, - cancel: null, - confirm: null, - child: const SizedBox(height: 40), - ); + second = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + destroyOnClose: true, + cancel: null, + confirm: null, + child: const SizedBox(height: 40)), + ).show(hostContext); }, ); await tester.pumpAndSettle(); @@ -895,16 +880,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup.show( - context: tester.element(find.text('open')), - placement: TPopupPlacement.bottom, - height: 200, - cancel: const Text('自定义取消'), - confirm: const Text('自定义确认'), - onCancel: () {}, - onConfirm: () {}, - child: const SizedBox(height: 60), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 200, + cancel: const Text('自定义取消'), + confirm: const Text('自定义确认'), + onCancel: () {}, + onConfirm: () {}, + child: const SizedBox(height: 60)), + ).show(tester.element(find.text('open'))); }, ); await tester.pumpAndSettle(); From 0609d89cc94ca743bf1ea81b58faed7fdb1c84ce Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 20:55:36 +0000 Subject: [PATCH 06/27] [autofix.ci] apply automated fixes --- .../code/indexes._buildCustomIndexes.txt | 50 +- .../assets/code/indexes._buildOther.txt | 36 +- .../assets/code/indexes._buildSimple.txt | 34 +- .../code/picker.buildCustomItemBuilder.txt | 16 +- .../assets/code/picker.buildCustomKeys.txt | 9 +- .../assets/code/picker.buildCustomSize.txt | 3 +- .../assets/code/picker.buildCustomSlot.txt | 6 +- .../code/picker.buildGlobalDisabled.txt | 7 +- .../assets/code/picker.buildItemDisabled.txt | 13 +- .../assets/code/picker.buildLinked.txt | 10 +- .../assets/code/picker.buildSingleColumn.txt | 6 +- .../assets/code/picker.buildTimeSelect.txt | 11 +- .../assets/code/popup._buildApiDuration.txt | 20 +- .../assets/code/popup._buildApiMarginTop.txt | 24 +- .../code/popup._buildApiOnOverlayClick.txt | 21 +- .../code/popup._buildApiShowOverlayFalse.txt | 26 +- .../assets/code/popup._buildNestedPopup.txt | 101 ++-- .../assets/code/popup._buildPopFromBottom.txt | 20 +- ...p._buildPopFromBottomWithCloseAndTitle.txt | 56 +- ...uildPopFromBottomWithOperationAndTitle.txt | 20 +- .../assets/code/popup._buildPopFromCenter.txt | 28 +- .../popup._buildPopFromCenterWithClose.txt | 32 +- ...opup._buildPopFromCenterWithUnderClose.txt | 40 +- .../assets/code/popup._buildPopFromLeft.txt | 16 +- .../assets/code/popup._buildPopFromRight.txt | 16 +- .../assets/code/popup._buildPopFromTop.txt | 22 +- tdesign-site/src/indexes/README.md | 140 ++--- tdesign-site/src/picker/README.md | 81 ++- tdesign-site/src/popup/README.md | 543 ++++++++++-------- 29 files changed, 755 insertions(+), 652 deletions(-) diff --git a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt index 88e3c0b48..a815f5e36 100644 --- a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt +++ b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt @@ -9,31 +9,31 @@ Widget _buildCustomIndexes(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - builderIndex: (context, index, isActive) { - return TText( - '自定义 $index', - textColor: isActive - ? TTheme.of(context).brandNormalColor - : TTheme.of(context).textColorPrimary, - ); - }, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderIndex: (context, index, isActive) { + return TText( + '自定义 $index', + textColor: isActive + ? TTheme.of(context).brandNormalColor + : TTheme.of(context).textColorPrimary, + ); + }, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/indexes._buildOther.txt b/tdesign-component/example/assets/code/indexes._buildOther.txt index f871cd50d..aab1db16f 100644 --- a/tdesign-component/example/assets/code/indexes._buildOther.txt +++ b/tdesign-component/example/assets/code/indexes._buildOther.txt @@ -9,24 +9,24 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - capsuleTheme: true, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + capsuleTheme: true, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/indexes._buildSimple.txt b/tdesign-component/example/assets/code/indexes._buildSimple.txt index 36e19e5a2..273faa1d6 100644 --- a/tdesign-component/example/assets/code/indexes._buildSimple.txt +++ b/tdesign-component/example/assets/code/indexes._buildSimple.txt @@ -9,23 +9,23 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/picker.buildCustomItemBuilder.txt b/tdesign-component/example/assets/code/picker.buildCustomItemBuilder.txt index c8c74463e..0e74fc62e 100644 --- a/tdesign-component/example/assets/code/picker.buildCustomItemBuilder.txt +++ b/tdesign-component/example/assets/code/picker.buildCustomItemBuilder.txt @@ -5,12 +5,14 @@ children: [ Text( '示例:itemBuilder 自定义子项渲染,可添加图标、背景色等', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder), ), const SizedBox(height: 4), Text( '选中: ${_customItemBuilderValue.isEmpty ? "未选择" : _customItemBuilderValue}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary), ), const SizedBox(height: 8), _pickerCard( @@ -36,15 +38,19 @@ content, style: TextStyle( fontSize: 16, - fontWeight: selected ? FontWeight.w600 : FontWeight.normal, - color: selected ? theme.brandNormalColor : theme.fontGyColor1, + fontWeight: + selected ? FontWeight.w600 : FontWeight.normal, + color: selected + ? theme.brandNormalColor + : theme.fontGyColor1, ), ), ], ), ); }, - onChange: (v) => setState(() => _customItemBuilderValue = v.labels.first), + onChange: (v) => + setState(() => _customItemBuilderValue = v.labels.first), ), ), ], diff --git a/tdesign-component/example/assets/code/picker.buildCustomKeys.txt b/tdesign-component/example/assets/code/picker.buildCustomKeys.txt index 6c8f8dd8a..b7012897c 100644 --- a/tdesign-component/example/assets/code/picker.buildCustomKeys.txt +++ b/tdesign-component/example/assets/code/picker.buildCustomKeys.txt @@ -1,19 +1,22 @@ Widget buildCustomKeys(BuildContext context) { // 用 keys 告诉组件「city 映射为 label,code 是 value,readonly 是 disabled」 - const keys = TPickerKeys(label: 'city', value: 'code', disabled: 'readonly'); + const keys = + TPickerKeys(label: 'city', value: 'code', disabled: 'readonly'); final label = _customKeysValue?.labels.join() ?? ''; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '后端原始字段:city / code / readonly。通过 keys(label: "city") 映射为 label', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder), ), const SizedBox(height: 4), Text( '当前选中:${label.isEmpty ? "未选择" : label}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary), ), const SizedBox(height: 8), _pickerCard( diff --git a/tdesign-component/example/assets/code/picker.buildCustomSize.txt b/tdesign-component/example/assets/code/picker.buildCustomSize.txt index f94fe3c70..b4606f643 100644 --- a/tdesign-component/example/assets/code/picker.buildCustomSize.txt +++ b/tdesign-component/example/assets/code/picker.buildCustomSize.txt @@ -5,7 +5,8 @@ children: [ Text( '示例:height(300) + itemCount(7),每屏显示 7 项', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder), ), const SizedBox(height: 8), _pickerCard( diff --git a/tdesign-component/example/assets/code/picker.buildCustomSlot.txt b/tdesign-component/example/assets/code/picker.buildCustomSlot.txt index 4c7137928..d3f56da0e 100644 --- a/tdesign-component/example/assets/code/picker.buildCustomSlot.txt +++ b/tdesign-component/example/assets/code/picker.buildCustomSlot.txt @@ -17,8 +17,7 @@ titleWidget: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(TIcons.location, - size: 18, color: theme.brandNormalColor), + Icon(TIcons.location, size: 18, color: theme.brandNormalColor), const SizedBox(width: 4), Text('选择地区', style: TextStyle( @@ -29,7 +28,8 @@ ], ), cancel: Icon(TIcons.close, size: 22, color: theme.fontGyColor2), - confirm: Icon(TIcons.check, size: 22, color: theme.brandNormalColor), + confirm: + Icon(TIcons.check, size: 22, color: theme.brandNormalColor), ), ), ], diff --git a/tdesign-component/example/assets/code/picker.buildGlobalDisabled.txt b/tdesign-component/example/assets/code/picker.buildGlobalDisabled.txt index f7abd42cf..bb1e7e3df 100644 --- a/tdesign-component/example/assets/code/picker.buildGlobalDisabled.txt +++ b/tdesign-component/example/assets/code/picker.buildGlobalDisabled.txt @@ -21,13 +21,16 @@ const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: cityItems, initialValue: const ['GZ'], + child: TPicker( + items: cityItems, + initialValue: const ['GZ'], onChange: (v) => debugPrint('选中: $v'), disabled: globalDisabled), ), const SizedBox(height: 4), Text('切换开关可控制整个选择器的禁用/启用状态', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), ], ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/picker.buildItemDisabled.txt b/tdesign-component/example/assets/code/picker.buildItemDisabled.txt index f7bf58114..7e80c7c81 100644 --- a/tdesign-component/example/assets/code/picker.buildItemDisabled.txt +++ b/tdesign-component/example/assets/code/picker.buildItemDisabled.txt @@ -3,15 +3,20 @@ return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('选中: ${selectedItemDisabled.isEmpty ? "未选择" : selectedItemDisabled}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + Text( + '选中: ${selectedItemDisabled.isEmpty ? "未选择" : selectedItemDisabled}', + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 4), Text('提示: 标灰的选项不可选(第1列「保密」、第2列「A排1座/A排6座/A排7座/A排8座/A排12座」)', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: itemDisabledItems, initialValue: const ['M', 'A5'], + child: TPicker( + items: itemDisabledItems, + initialValue: const ['M', 'A5'], onChange: (v) => setState(() => selectedItemDisabled = '${v.labels.first} ${v.labels.last}')), ), diff --git a/tdesign-component/example/assets/code/picker.buildLinked.txt b/tdesign-component/example/assets/code/picker.buildLinked.txt index 90579f3c0..a3bfaea34 100644 --- a/tdesign-component/example/assets/code/picker.buildLinked.txt +++ b/tdesign-component/example/assets/code/picker.buildLinked.txt @@ -4,12 +4,16 @@ crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中地区: ${selectedLinked.isEmpty ? "未选择" : selectedLinked}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: linkedItems, initialValue: const ['GD', 'SZ', 'NS'], - onChange: (v) => setState(() => selectedLinked = v.labels.join(' / '))), + child: TPicker( + items: linkedItems, + initialValue: const ['GD', 'SZ', 'NS'], + onChange: (v) => + setState(() => selectedLinked = v.labels.join(' / '))), ), ], ); diff --git a/tdesign-component/example/assets/code/picker.buildSingleColumn.txt b/tdesign-component/example/assets/code/picker.buildSingleColumn.txt index 839df74f9..953af8c2a 100644 --- a/tdesign-component/example/assets/code/picker.buildSingleColumn.txt +++ b/tdesign-component/example/assets/code/picker.buildSingleColumn.txt @@ -4,11 +4,13 @@ crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中城市: ${selectedCity.isEmpty ? "未选择" : selectedCity}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: cityItems, + child: TPicker( + items: cityItems, onChange: (v) => setState(() => selectedCity = v.labels.first)), ), ], diff --git a/tdesign-component/example/assets/code/picker.buildTimeSelect.txt b/tdesign-component/example/assets/code/picker.buildTimeSelect.txt index eb85ddf1f..f9c5f818f 100644 --- a/tdesign-component/example/assets/code/picker.buildTimeSelect.txt +++ b/tdesign-component/example/assets/code/picker.buildTimeSelect.txt @@ -4,13 +4,16 @@ crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中时间: ${selectedTime.isEmpty ? "未选择" : selectedTime}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: timeItems, itemCount: 5, - onChange: (v) => setState(() => - selectedTime = '${v.values[0]}:${v.values[1].toString().padLeft(2, '0')}:${v.values[2].toString().padLeft(2, '0')}')), + child: TPicker( + items: timeItems, + itemCount: 5, + onChange: (v) => setState(() => selectedTime = + '${v.values[0]}:${v.values[1].toString().padLeft(2, '0')}:${v.values[2].toString().padLeft(2, '0')}')), ), ], ); diff --git a/tdesign-component/example/assets/code/popup._buildApiDuration.txt b/tdesign-component/example/assets/code/popup._buildApiDuration.txt index 2a190439a..93228a8e5 100644 --- a/tdesign-component/example/assets/code/popup._buildApiDuration.txt +++ b/tdesign-component/example/assets/code/popup._buildApiDuration.txt @@ -7,16 +7,16 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 240, - duration: const Duration(milliseconds: 600), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 240, + duration: const Duration(milliseconds: 600), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt index a7dbd5cd4..40f88c1e3 100644 --- a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt +++ b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt @@ -7,19 +7,17 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 320, - margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - title: '日历式留白', - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container( - height: 240, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 320, + margin: const EdgeInsets.only(top: 120, left: 16, right: 16), + title: '日历式留白', + child: Container( + height: 240, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt index a1f80b865..353a3fb54 100644 --- a/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt +++ b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt @@ -7,17 +7,16 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 260, - onOverlayClick: () => - TToast.showText('点击蒙层', context: context), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 260, + onOverlayClick: () => TToast.showText('点击蒙层', context: context), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt index d6968601d..2d5e79494 100644 --- a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt +++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt @@ -7,20 +7,18 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - showOverlay: false, - // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) - title: '无蒙层', - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + showOverlay: false, + // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) + title: '无蒙层', + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt index 9a6f3e30f..255f07946 100644 --- a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt +++ b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt @@ -7,57 +7,58 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 360, - headerBuilder: null, - child: Builder( - builder: (innerContext) { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TText( - '外层:headerBuilder: null,仅 child', - textColor: TTheme.of(innerContext).textColorSecondary, + TPopupHandle? outerHandle; + outerHandle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 360, + headerBuilder: null, + child: Builder( + builder: (innerContext) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TText( + '外层:headerBuilder: null,仅 child', + textColor: TTheme.of(innerContext).textColorSecondary, + ), + const SizedBox(height: 16), + TButton( + text: '打开内层 Popup', + isBlock: true, + theme: TButtonTheme.primary, + size: TButtonSize.large, + onTap: () { + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + title: '内层标题', + child: Container( + height: 160, + color: TTheme.of(innerContext) + .bgColorSecondaryContainer, + ), + ), + ).show(innerContext); + }, + ), + const SizedBox(height: 12), + TButton( + text: '关闭外层', + isBlock: true, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => outerHandle?.close(), + ), + ], ), - const SizedBox(height: 16), - TButton( - text: '打开内层 Popup', - isBlock: true, - theme: TButtonTheme.primary, - size: TButtonSize.large, - onTap: () { - TPopup.show( - context: innerContext, - placement: TPopupPlacement.bottom, - height: 280, - title: '内层标题', - onCancel: () => TPopup.close(innerContext), - onConfirm: () => TPopup.close(innerContext), - child: Container( - height: 160, - color: TTheme.of(innerContext).bgColorSecondaryContainer, - ), - ); - }, - ), - const SizedBox(height: 12), - TButton( - text: '关闭外层', - isBlock: true, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () => TPopup.close(innerContext), - ), - ], - ), - ); - }, - ), - ); + ); + }, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt index 0669277c1..304a899f9 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt @@ -7,16 +7,16 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 240, - headerBuilder: null, - child: Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 240, + headerBuilder: null, + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt index a79e31363..a24554ad5 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt @@ -7,38 +7,36 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - cancel: TText( - '关闭', - textColor: TTheme.of(context).textColorSecondary, - font: TTheme.of(context).fontBodyLarge, - ), - titleWidget: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(TIcons.info_circle, - color: TTheme.of(context).brandNormalColor, size: 18), - const SizedBox(width: 4), - TText( - '自定义标题', + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + cancel: TText( + '关闭', + textColor: TTheme.of(context).textColorSecondary, + font: TTheme.of(context).fontBodyLarge, + ), + titleWidget: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(TIcons.info_circle, + color: TTheme.of(context).brandNormalColor, size: 18), + const SizedBox(width: 4), + TText( + '自定义标题', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + ), + ], + ), + confirm: TText( + '完成', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontTitleMedium, + fontWeight: FontWeight.w600, ), - ], - ), - confirm: TText( - '完成', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontTitleMedium, - fontWeight: FontWeight.w600, - ), - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container(height: 200), - ); + child: Container(height: 200)), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt index e98e490d3..310cfec3c 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt @@ -7,18 +7,14 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - title: '标题文字', - onCancel: () => TPopup.close(context), - onConfirm: () { - TToast.showText('确定', context: context); - TPopup.close(context); - }, - child: Container(height: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + onConfirm: () => TToast.showText('确定', context: context), + child: Container(height: 200)), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt index 262fe914a..1c746fa7c 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt @@ -7,20 +7,20 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeBuilder: null, - child: Container( - decoration: BoxDecoration( - color: TTheme.of(context).bgColorContainer, - borderRadius: - BorderRadius.circular(TTheme.of(context).radiusLarge), - ), - width: 240, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeBuilder: null, + child: Container( + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: + BorderRadius.circular(TTheme.of(context).radiusLarge), + ), + width: 240, + height: 240, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt index 91fd4f1f0..3e44b9601 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt @@ -7,22 +7,22 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeOnOverlayClick: false, - width: 240, - height: 240, - closeBuilder: (_, close) => IconButton( - icon: Icon( - TIcons.close_circle, - color: TTheme.of(context).fontWhColor1, - size: 32, - ), - onPressed: close, - ), - child: const SizedBox(width: 240, height: 240), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeOnOverlayClick: false, + width: 240, + height: 240, + closeBuilder: (_, close) => IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).fontWhColor1, + size: 32, + ), + onPressed: close, + ), + child: const SizedBox(width: 240, height: 240)), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt index 5fabf5187..abea61c66 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt @@ -7,26 +7,26 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeOnOverlayClick: true, - width: 240, - height: 200, - closeBuilder: (_, close) => IconButton( - icon: Icon( - TIcons.poweroff, - color: TTheme.of(context).fontWhColor1, - size: 36, - ), - onPressed: close, - ), - child: Container( - width: 240, - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeOnOverlayClick: true, + width: 240, + height: 200, + closeBuilder: (_, close) => IconButton( + icon: Icon( + TIcons.poweroff, + color: TTheme.of(context).fontWhColor1, + size: 36, + ), + onPressed: close, + ), + child: Container( + width: 240, + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt index bc5825c30..c90d9356a 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt @@ -7,14 +7,14 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.left, - width: 280, - child: Container( - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.left, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt index ab28e32fd..997c4d2f9 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt @@ -7,14 +7,14 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - child: Container( - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt index 9ea230450..1868b2d6a 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt @@ -7,17 +7,17 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.top, - height: 240, - onOpen: () => print('open'), - onOpened: () => print('opened'), - child: Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.top, + height: 240, + onOpen: () => print('open'), + onOpened: () => print('opened'), + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + )), + ).show(context); }, ); } \ No newline at end of file diff --git a/tdesign-site/src/indexes/README.md b/tdesign-site/src/indexes/README.md index 2f5287d96..be8e1092b 100644 --- a/tdesign-site/src/indexes/README.md +++ b/tdesign-site/src/indexes/README.md @@ -36,23 +36,23 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); }
@@ -74,23 +74,23 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); }
@@ -115,24 +115,24 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - capsuleTheme: true, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + capsuleTheme: true, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); }
@@ -154,24 +154,24 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), - child: TIndexes( - indexList: indexList, - capsuleTheme: true, - builderContent: (context, index) { - final list = _list - .firstWhere((element) => element['index'] == index)['children'] - as List; - return TCellGroup( - cells: list.map((e) => TCell(title: e)).toList(), - ); - }, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), + child: TIndexes( + indexList: indexList, + capsuleTheme: true, + builderContent: (context, index) { + final list = _list.firstWhere( + (element) => element['index'] == index)['children'] + as List; + return TCellGroup( + cells: list.map((e) => TCell(title: e)).toList(), + ); + }, + )), + ).show(context); }, ); }
diff --git a/tdesign-site/src/picker/README.md b/tdesign-site/src/picker/README.md index 2dfa16264..4762ebb45 100644 --- a/tdesign-site/src/picker/README.md +++ b/tdesign-site/src/picker/README.md @@ -30,11 +30,13 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中城市: ${selectedCity.isEmpty ? "未选择" : selectedCity}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: cityItems, + child: TPicker( + items: cityItems, onChange: (v) => setState(() => selectedCity = v.labels.first)), ), ], @@ -54,13 +56,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中时间: ${selectedTime.isEmpty ? "未选择" : selectedTime}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: timeItems, itemCount: 5, - onChange: (v) => setState(() => - selectedTime = '${v.values[0]}:${v.values[1].toString().padLeft(2, '0')}:${v.values[2].toString().padLeft(2, '0')}')), + child: TPicker( + items: timeItems, + itemCount: 5, + onChange: (v) => setState(() => selectedTime = + '${v.values[0]}:${v.values[1].toString().padLeft(2, '0')}:${v.values[2].toString().padLeft(2, '0')}')), ), ], ); @@ -79,12 +84,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('选中地区: ${selectedLinked.isEmpty ? "未选择" : selectedLinked}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: linkedItems, initialValue: const ['GD', 'SZ', 'NS'], - onChange: (v) => setState(() => selectedLinked = v.labels.join(' / '))), + child: TPicker( + items: linkedItems, + initialValue: const ['GD', 'SZ', 'NS'], + onChange: (v) => + setState(() => selectedLinked = v.labels.join(' / '))), ), ], ); @@ -178,15 +187,20 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('选中: ${selectedItemDisabled.isEmpty ? "未选择" : selectedItemDisabled}', - style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary)), + Text( + '选中: ${selectedItemDisabled.isEmpty ? "未选择" : selectedItemDisabled}', + style: TextStyle( + fontSize: 14, color: TTheme.of(context).textColorSecondary)), const SizedBox(height: 4), Text('提示: 标灰的选项不可选(第1列「保密」、第2列「A排1座/A排6座/A排7座/A排8座/A排12座」)', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: itemDisabledItems, initialValue: const ['M', 'A5'], + child: TPicker( + items: itemDisabledItems, + initialValue: const ['M', 'A5'], onChange: (v) => setState(() => selectedItemDisabled = '${v.labels.first} ${v.labels.last}')), ), @@ -224,13 +238,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; const SizedBox(height: 8), _pickerCard( context, - child: TPicker(items: cityItems, initialValue: const ['GZ'], + child: TPicker( + items: cityItems, + initialValue: const ['GZ'], onChange: (v) => debugPrint('选中: $v'), disabled: globalDisabled), ), const SizedBox(height: 4), Text('切换开关可控制整个选择器的禁用/启用状态', - style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), + style: TextStyle( + fontSize: 12, color: TTheme.of(context).textColorPlaceholder)), ], ); }
@@ -327,8 +344,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; titleWidget: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(TIcons.location, - size: 18, color: theme.brandNormalColor), + Icon(TIcons.location, size: 18, color: theme.brandNormalColor), const SizedBox(width: 4), Text('选择地区', style: TextStyle( @@ -339,7 +355,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ], ), cancel: Icon(TIcons.close, size: 22, color: theme.fontGyColor2), - confirm: Icon(TIcons.check, size: 22, color: theme.brandNormalColor), + confirm: + Icon(TIcons.check, size: 22, color: theme.brandNormalColor), ), ), ], @@ -357,19 +374,22 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
   Widget buildCustomKeys(BuildContext context) {
     // 用 keys 告诉组件「city 映射为 label,code 是 value,readonly 是 disabled」
-    const keys = TPickerKeys(label: 'city', value: 'code', disabled: 'readonly');
+    const keys =
+        TPickerKeys(label: 'city', value: 'code', disabled: 'readonly');
     final label = _customKeysValue?.labels.join() ?? '';
     return Column(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
         Text(
           '后端原始字段:city / code / readonly。通过 keys(label: "city") 映射为 label',
-          style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder),
+          style: TextStyle(
+              fontSize: 12, color: TTheme.of(context).textColorPlaceholder),
         ),
         const SizedBox(height: 4),
         Text(
           '当前选中:${label.isEmpty ? "未选择" : label}',
-          style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary),
+          style: TextStyle(
+              fontSize: 14, color: TTheme.of(context).textColorSecondary),
         ),
         const SizedBox(height: 8),
         _pickerCard(
@@ -399,7 +419,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       children: [
         Text(
           '示例:height(300) + itemCount(7),每屏显示 7 项',
-          style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder),
+          style: TextStyle(
+              fontSize: 12, color: TTheme.of(context).textColorPlaceholder),
         ),
         const SizedBox(height: 8),
         _pickerCard(
@@ -429,12 +450,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       children: [
         Text(
           '示例:itemBuilder 自定义子项渲染,可添加图标、背景色等',
-          style: TextStyle(fontSize: 12, color: TTheme.of(context).textColorPlaceholder),
+          style: TextStyle(
+              fontSize: 12, color: TTheme.of(context).textColorPlaceholder),
         ),
         const SizedBox(height: 4),
         Text(
           '选中: ${_customItemBuilderValue.isEmpty ? "未选择" : _customItemBuilderValue}',
-          style: TextStyle(fontSize: 14, color: TTheme.of(context).textColorSecondary),
+          style: TextStyle(
+              fontSize: 14, color: TTheme.of(context).textColorSecondary),
         ),
         const SizedBox(height: 8),
         _pickerCard(
@@ -460,15 +483,19 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
                       content,
                       style: TextStyle(
                         fontSize: 16,
-                        fontWeight: selected ? FontWeight.w600 : FontWeight.normal,
-                        color: selected ? theme.brandNormalColor : theme.fontGyColor1,
+                        fontWeight:
+                            selected ? FontWeight.w600 : FontWeight.normal,
+                        color: selected
+                            ? theme.brandNormalColor
+                            : theme.fontGyColor1,
                       ),
                     ),
                   ],
                 ),
               );
             },
-            onChange: (v) => setState(() => _customItemBuilderValue = v.labels.first),
+            onChange: (v) =>
+                setState(() => _customItemBuilderValue = v.labels.first),
           ),
         ),
       ],
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index f95ff8abf..36e003253 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -33,17 +33,17 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
       type: TButtonType.outline,
       size: TButtonSize.large,
       onTap: () {
-        TPopup.show(
-          context: context,
-          placement: TPopupPlacement.top,
-          height: 240,
-          onOpen: () => print('open'),
-          onOpened: () => print('opened'),
-          child: Container(
-            color: TTheme.of(context).bgColorContainer,
-            height: 240,
-          ),
-        );
+        TPopup(
+          options: TPopupOptions(
+              placement: TPopupPlacement.top,
+              height: 240,
+              onOpen: () => print('open'),
+              onOpened: () => print('opened'),
+              child: Container(
+                color: TTheme.of(context).bgColorContainer,
+                height: 240,
+              )),
+        ).show(context);
       },
     );
   }
@@ -64,14 +64,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.left, - width: 280, - child: Container( - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.left, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); }
@@ -92,20 +92,20 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeBuilder: null, - child: Container( - decoration: BoxDecoration( - color: TTheme.of(context).bgColorContainer, - borderRadius: - BorderRadius.circular(TTheme.of(context).radiusLarge), - ), - width: 240, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeBuilder: null, + child: Container( + decoration: BoxDecoration( + color: TTheme.of(context).bgColorContainer, + borderRadius: + BorderRadius.circular(TTheme.of(context).radiusLarge), + ), + width: 240, + height: 240, + )), + ).show(context); }, ); }
@@ -126,16 +126,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 240, - headerBuilder: null, - child: Container( - color: TTheme.of(context).bgColorContainer, - height: 240, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 240, + headerBuilder: null, + child: Container( + color: TTheme.of(context).bgColorContainer, + height: 240, + )), + ).show(context); }, ); } @@ -156,14 +156,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.right, - width: 280, - child: Container( - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.right, + width: 280, + child: Container( + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -185,18 +185,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - title: '标题文字', - onCancel: () => TPopup.close(context), - onConfirm: () { - TToast.showText('确定', context: context); - TPopup.close(context); - }, - child: Container(height: 200), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + title: '标题文字', + onConfirm: () => TToast.showText('确定', context: context), + child: Container(height: 200)), + ).show(context); }, ); } @@ -217,38 +213,36 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - cancel: TText( - '关闭', - textColor: TTheme.of(context).textColorSecondary, - font: TTheme.of(context).fontBodyLarge, - ), - titleWidget: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(TIcons.info_circle, - color: TTheme.of(context).brandNormalColor, size: 18), - const SizedBox(width: 4), - TText( - '自定义标题', + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + cancel: TText( + '关闭', + textColor: TTheme.of(context).textColorSecondary, + font: TTheme.of(context).fontBodyLarge, + ), + titleWidget: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(TIcons.info_circle, + color: TTheme.of(context).brandNormalColor, size: 18), + const SizedBox(width: 4), + TText( + '自定义标题', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + ), + ], + ), + confirm: TText( + '完成', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontTitleMedium, + fontWeight: FontWeight.w600, ), - ], - ), - confirm: TText( - '完成', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontTitleMedium, - fontWeight: FontWeight.w600, - ), - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container(height: 200), - ); + child: Container(height: 200)), + ).show(context); }, ); } @@ -269,22 +263,22 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeOnOverlayClick: false, - width: 240, - height: 240, - closeBuilder: (_, close) => IconButton( - icon: Icon( - TIcons.close_circle, - color: TTheme.of(context).fontWhColor1, - size: 32, - ), - onPressed: close, - ), - child: const SizedBox(width: 240, height: 240), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeOnOverlayClick: false, + width: 240, + height: 240, + closeBuilder: (_, close) => IconButton( + icon: Icon( + TIcons.close_circle, + color: TTheme.of(context).fontWhColor1, + size: 32, + ), + onPressed: close, + ), + child: const SizedBox(width: 240, height: 240)), + ).show(context); }, ); } @@ -305,26 +299,26 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.center, - closeOnOverlayClick: true, - width: 240, - height: 200, - closeBuilder: (_, close) => IconButton( - icon: Icon( - TIcons.poweroff, - color: TTheme.of(context).fontWhColor1, - size: 36, - ), - onPressed: close, - ), - child: Container( - width: 240, - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.center, + closeOnOverlayClick: true, + width: 240, + height: 200, + closeBuilder: (_, close) => IconButton( + icon: Icon( + TIcons.poweroff, + color: TTheme.of(context).fontWhColor1, + size: 36, + ), + onPressed: close, + ), + child: Container( + width: 240, + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -345,57 +339,58 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 360, - headerBuilder: null, - child: Builder( - builder: (innerContext) { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TText( - '外层:headerBuilder: null,仅 child', - textColor: TTheme.of(innerContext).textColorSecondary, + TPopupHandle? outerHandle; + outerHandle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 360, + headerBuilder: null, + child: Builder( + builder: (innerContext) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TText( + '外层:headerBuilder: null,仅 child', + textColor: TTheme.of(innerContext).textColorSecondary, + ), + const SizedBox(height: 16), + TButton( + text: '打开内层 Popup', + isBlock: true, + theme: TButtonTheme.primary, + size: TButtonSize.large, + onTap: () { + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + title: '内层标题', + child: Container( + height: 160, + color: TTheme.of(innerContext) + .bgColorSecondaryContainer, + ), + ), + ).show(innerContext); + }, + ), + const SizedBox(height: 12), + TButton( + text: '关闭外层', + isBlock: true, + type: TButtonType.outline, + size: TButtonSize.large, + onTap: () => outerHandle?.close(), + ), + ], ), - const SizedBox(height: 16), - TButton( - text: '打开内层 Popup', - isBlock: true, - theme: TButtonTheme.primary, - size: TButtonSize.large, - onTap: () { - TPopup.show( - context: innerContext, - placement: TPopupPlacement.bottom, - height: 280, - title: '内层标题', - onCancel: () => TPopup.close(innerContext), - onConfirm: () => TPopup.close(innerContext), - child: Container( - height: 160, - color: TTheme.of(innerContext).bgColorSecondaryContainer, - ), - ); - }, - ), - const SizedBox(height: 12), - TButton( - text: '关闭外层', - isBlock: true, - type: TButtonType.outline, - size: TButtonSize.large, - onTap: () => TPopup.close(innerContext), - ), - ], - ), - ); - }, - ), - ); + ); + }, + )), + ).show(context); }, ); } @@ -417,19 +412,17 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 320, - margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - title: '日历式留白', - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container( - height: 240, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 320, + margin: const EdgeInsets.only(top: 120, left: 16, right: 16), + title: '日历式留白', + child: Container( + height: 240, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -450,20 +443,18 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 280, - showOverlay: false, - // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) - title: '无蒙层', - onCancel: () => TPopup.close(context), - onConfirm: () => TPopup.close(context), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 280, + showOverlay: false, + // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) + title: '无蒙层', + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -484,17 +475,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 260, - onOverlayClick: () => - TToast.showText('点击蒙层', context: context), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 260, + onOverlayClick: () => TToast.showText('点击蒙层', context: context), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -515,16 +505,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup.show( - context: context, - placement: TPopupPlacement.bottom, - height: 240, - duration: const Duration(milliseconds: 600), - child: Container( - height: 200, - color: TTheme.of(context).bgColorContainer, - ), - ); + TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 240, + duration: const Duration(milliseconds: 600), + child: Container( + height: 200, + color: TTheme.of(context).bgColorContainer, + )), + ).show(context); }, ); } @@ -536,12 +526,70 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ## API ### TPopup #### 简介 -弹出层:支持五向滑入/居中弹出、蒙层、bottom 操作栏与 center 关闭区。 +弹出层:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 + + ## 怎么用 + + **命令式(推荐)** — 先组配置,再 `show`,用返回的 [TPopupHandle] 关闭: + + ```dart + final handle = TPopup( + options: TPopupOptions( + placement: TPopupPlacement.bottom, + title: '标题', + child: MyPanel(), + ), + ).show(context); + + // 关闭这一层(须保留 handle,不要用 context 猜栈顶) + handle.close(); + ``` + + **声明式** — 包住子树,`initialVisible: true` 时首帧自动 [show];[build] 只渲染 [options.child]: + + ```dart + TPopup( + options: TPopupOptions(child: body), + initialVisible: true, + ) + ``` + + 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 + [TPopupOptions.normalized] 中裁掉)。 +#### 默认构造方法 + +| 参数 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| initialVisible | bool | false | 为 true 时,挂载后首帧自动调用 [show](仅声明式)。 | +| key | | - | | +| navigatorContext | BuildContext? | - | 指定使用哪个 [Navigator];默认 [show] 传入的 `context` 所在 Navigator。 | +| options | TPopupOptions | - | 浮层内容与行为配置,见 [TPopupOptions]。 | +| useRootNavigator | bool | false | 为 true 时使用根 [Navigator](嵌套导航场景)。 | + +``` +``` + +### TPopupOptions +#### 简介 +浮层配置:[TPopup] 构造与 [TPopup.show] 的唯一参数来源。 + + ## 按 [placement] 用哪些字段 + + | placement | 常用字段 | + |-----------|----------| + | [TPopupPlacement.bottom] | `title` / `cancel` / `confirm` / `headerBuilder`、`height`、`margin` | + | [TPopupPlacement.center] | `closeBuilder`、`width`、`height`(有下方关闭时) | + | [TPopupPlacement.top] / [left] / [right] | 主要 `child`、`margin`、方向对应 `width` 或 `height` | + + 传给其它 placement 的 bottom / center 专用字段会在 [normalized] 里裁掉。 + + ## 三态占位(bottom / center) + + - **未传参数**:使用默认 UI(如默认取消/确定文案、默认关闭图标)。 + - **显式 `null`**:隐藏该槽位(如 `cancel: null` 隐藏左侧;`closeBuilder: null` 无关闭按钮)。 + - **自定义 Widget / Builder**:完全自定义该区域。 - 命令式用法优先调用 [show];声明式将 [TPopup] 包裹业务子树并设 [initialVisible](弹层在独立路由中,[build] 仅渲染 [child])。 - bottom 操作栏参数仅对 [TPopupPlacement.bottom] 生效;center 关闭参数仅对 center 生效; - top/left/right 仅使用 [child] 与布局参数。 - 嵌套时 [close] 只关栈顶 Popup;无 Popup 时不操作当前页。 + [TPopup.show] 内部会先 [normalized] 再绘制。 #### 默认构造方法 | 参数 | 类型 | 默认值 | 说明 | @@ -553,21 +601,18 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; | cancelBtn | String? | - | bottom 左侧按钮文案,覆盖默认「取消」。 | | cancelBuilder | WidgetBuilder? | - | bottom 左侧按钮构建器,优先级高于 [cancel]。 | | child | Widget | - | 浮层主体内容(必填)。 | -| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose] 默认圆圈图标; | +| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];bottom 与三边忽略。 | | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 | | confirm | Widget? | kPopupActionDefault | bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 | | confirmBtn | String? | - | bottom 右侧按钮文案,覆盖默认「确定」。 | | confirmBuilder | WidgetBuilder? | - | bottom 右侧按钮构建器,优先级高于 [confirm]。 | -| destroyOnClose | bool | false | 为 true 时 Popup 路由 [Route.maintainState] 为 false,关闭后不保留路由内 State; | +| destroyOnClose | bool | false | 为 true 时 Popup 路由 maintainState 为 false,关闭后不保留路由内 State。 | | duration | Duration | const Duration(milliseconds: 240) | 打开与关闭动画时长(一致)。 | -| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader] 默认操作栏;自定义见 [TPopupHeaderBuilder]。 | +| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader];自定义见 [TPopupHeaderBuilder]。 | | height | double? | - | 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 | -| initialVisible | bool | false | 声明式:为 true 时在首帧后自动 [show]。 | -| key | | - | | -| margin | EdgeInsets? | - | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 | -| navigatorContext | BuildContext? | - | 指定 Navigator 的 context,默认使用当前 context。 | +| margin | EdgeInsets | EdgeInsets.zero | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 | | onCancel | VoidCallback? | - | 点击 bottom 左侧按钮回调。 | -| onClose | VoidCallback? | - | 开始关闭时回调(含蒙层、按钮、程序化关闭)。 | +| onClose | VoidCallback? | - | 开始关闭时回调。 | | onCloseBtn | VoidCallback? | - | center 点击关闭控件前的回调。 | | onClosed | VoidCallback? | - | 关闭动画结束且路由移除后回调。 | | onConfirm | VoidCallback? | - | 点击 bottom 右侧按钮回调。 | @@ -584,7 +629,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; | title | String? | - | bottom 操作栏中间标题文案。 | | titleAlignLeft | bool | false | bottom 仅标题行时是否左对齐,默认居中。 | | titleWidget | Widget? | - | bottom 操作栏中间标题组件,优先级高于 [title]。 | -| useRootNavigator | bool | false | 是否使用根 Navigator。 | | width | double? | - | 宽度;对 left、right、center 生效。 | @@ -592,8 +636,23 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; | 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | -| close | | required BuildContext context, Object? result, | 关闭当前 Navigator 栈顶 [TPopup]。 仅关闭 Tracker 栈顶展示中的 Popup;无 Popup 时不操作(不会 pop 当前页)。 | -| show | | required BuildContext context, required Widget child, TPopupPlacement placement, double? width, double? height, EdgeInsets? margin, double? radius, Color? backgroundColor, bool showOverlay, bool closeOnOverlayClick, Color? overlayColor, double? overlayOpacity, bool preventScrollThrough, bool destroyOnClose, Duration duration, String? title, Widget? titleWidget, bool titleAlignLeft, String? cancelBtn, Widget? cancel, WidgetBuilder? cancelBuilder, VoidCallback? onCancel, String? confirmBtn, Widget? confirm, WidgetBuilder? confirmBuilder, VoidCallback? onConfirm, bool autoCloseOnCancel, bool autoCloseOnConfirm, TPopupCloseBuilder? closeBuilder, VoidCallback? onCloseBtn, TPopupHeaderBuilder? headerBuilder, VoidCallback? onOpen, VoidCallback? onOpened, VoidCallback? onClose, VoidCallback? onClosed, TPopupVisibleChangeCallback? onVisibleChange, VoidCallback? onOverlayClick, BuildContext? navigatorContext, bool useRootNavigator, | 命令式打开浮层,参数与 [TPopup] 构造器一致。 返回 [TPopupHandle];优先 [TPopupHandle.close],或在 Popup 子树内 [close]。 [cancel]/[confirm] 默认 [kPopupActionDefault] 表示默认文案,显式 null 可隐藏操作栏侧。 [closeBuilder] 未传为 [kPopupDefaultClose](默认关闭图标),显式 null 不显示关闭区。 | +| isActionDefault | | required Widget? action, | | + +``` +``` + +### TPopupHeaderData +#### 简介 +传给自定义 [TPopupOptions.headerBuilder] 的标题栏数据(库内已组装好各槽 Widget)。 +#### 默认构造方法 + +| 参数 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| cancel | Widget? | - | 左侧区域 Widget(null 表示该侧已隐藏)。 | +| confirm | Widget? | - | 右侧区域 Widget(null 表示该侧已隐藏)。 | +| onCancel | VoidCallback? | - | 点击左侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnCancel] 决定)。 | +| onConfirm | VoidCallback? | - | 点击右侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnConfirm] 决定)。 | +| title | Widget? | - | 中间标题(可为 null)。 | \ No newline at end of file From 8b68556c5ef951dbdc0b6b08cdf39245ed030953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Fri, 22 May 2026 11:39:23 +0800 Subject: [PATCH 07/27] refactor(popup): update TPopup usage across multiple components to use the new show method --- .../code/indexes._buildCustomIndexes.txt | 5 +- .../assets/code/indexes._buildOther.txt | 5 +- .../assets/code/indexes._buildSimple.txt | 5 +- .../assets/code/popup._buildApiDuration.txt | 5 +- .../assets/code/popup._buildApiMarginTop.txt | 5 +- .../code/popup._buildApiOnOverlayClick.txt | 5 +- .../code/popup._buildApiShowOverlayFalse.txt | 5 +- .../assets/code/popup._buildNestedPopup.txt | 10 +- .../assets/code/popup._buildPopFromBottom.txt | 5 +- ...p._buildPopFromBottomWithCloseAndTitle.txt | 5 +- ...uildPopFromBottomWithOperationAndTitle.txt | 5 +- .../assets/code/popup._buildPopFromCenter.txt | 5 +- .../popup._buildPopFromCenterWithClose.txt | 5 +- ...opup._buildPopFromCenterWithUnderClose.txt | 5 +- .../assets/code/popup._buildPopFromLeft.txt | 5 +- .../assets/code/popup._buildPopFromRight.txt | 5 +- .../assets/code/popup._buildPopFromTop.txt | 5 +- .../lib/component_test/popup_test.dart | 7 +- .../example/lib/page/t_indexes_page.dart | 15 +- .../example/lib/page/t_picker_page.dart | 9 +- .../example/lib/page/t_popup_page.dart | 172 ++++---- .../action_sheet/t_action_sheet.dart | 9 +- .../components/calendar/t_calendar_popup.dart | 9 +- .../lib/src/components/drawer/t_drawer.dart | 5 +- .../components/popup/_popup_center_close.dart | 11 +- .../src/components/popup/_popup_header.dart | 210 +++------- .../src/components/popup/_popup_layout.dart | 15 +- .../src/components/popup/_popup_route.dart | 2 - .../src/components/popup/_popup_shell.dart | 110 ++--- .../lib/src/components/popup/t_popup.dart | 128 ++---- .../src/components/popup/t_popup_handle.dart | 90 +++- .../src/components/popup/t_popup_options.dart | 268 ++++++------ .../src/components/popup/t_popup_types.dart | 129 +++--- .../test/t_popup_coverage_test.dart | 284 ++++++------- .../test/t_popup_layout_test.dart | 31 +- .../test/t_popup_options_test.dart | 169 +++++--- .../test/t_popup_route_test.dart | 19 +- tdesign-component/test/t_popup_test.dart | 391 ++++++++++-------- tdesign-site/src/indexes/README.md | 20 +- tdesign-site/src/popup/README.md | 110 ++--- 40 files changed, 1144 insertions(+), 1159 deletions(-) diff --git a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt index a815f5e36..78eb937c7 100644 --- a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt +++ b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt @@ -9,7 +9,8 @@ Widget _buildCustomIndexes(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -33,7 +34,7 @@ Widget _buildCustomIndexes(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/indexes._buildOther.txt b/tdesign-component/example/assets/code/indexes._buildOther.txt index aab1db16f..907e9d57b 100644 --- a/tdesign-component/example/assets/code/indexes._buildOther.txt +++ b/tdesign-component/example/assets/code/indexes._buildOther.txt @@ -9,7 +9,8 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -26,7 +27,7 @@ Widget _buildOther(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/indexes._buildSimple.txt b/tdesign-component/example/assets/code/indexes._buildSimple.txt index 273faa1d6..526e86fe5 100644 --- a/tdesign-component/example/assets/code/indexes._buildSimple.txt +++ b/tdesign-component/example/assets/code/indexes._buildSimple.txt @@ -9,7 +9,8 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -25,7 +26,7 @@ Widget _buildSimple(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiDuration.txt b/tdesign-component/example/assets/code/popup._buildApiDuration.txt index 93228a8e5..823bf860a 100644 --- a/tdesign-component/example/assets/code/popup._buildApiDuration.txt +++ b/tdesign-component/example/assets/code/popup._buildApiDuration.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 240, @@ -16,7 +17,7 @@ height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt index 40f88c1e3..359e5379e 100644 --- a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt +++ b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 320, @@ -17,7 +18,7 @@ height: 240, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt index 353a3fb54..ad92d6457 100644 --- a/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt +++ b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 260, @@ -16,7 +17,7 @@ height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt index 2d5e79494..f78db1049 100644 --- a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt +++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, @@ -18,7 +19,7 @@ height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt index 255f07946..abc06d8f8 100644 --- a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt +++ b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt @@ -8,7 +8,8 @@ size: TButtonSize.large, onTap: () { TPopupHandle? outerHandle; - outerHandle = TPopup( + outerHandle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 360, @@ -31,7 +32,8 @@ theme: TButtonTheme.primary, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + innerContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, @@ -42,7 +44,7 @@ .bgColorSecondaryContainer, ), ), - ).show(innerContext); + ); }, ), const SizedBox(height: 12), @@ -58,7 +60,7 @@ ); }, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt index 304a899f9..93cf8defd 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 240, @@ -16,7 +17,7 @@ color: TTheme.of(context).bgColorContainer, height: 240, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt index a24554ad5..fa292abb9 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, @@ -36,7 +37,7 @@ fontWeight: FontWeight.w600, ), child: Container(height: 200)), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt index 310cfec3c..406c6e993 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt @@ -7,14 +7,15 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, title: '标题文字', onConfirm: () => TToast.showText('确定', context: context), child: Container(height: 200)), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt index 1c746fa7c..7e70ac0c5 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeBuilder: null, @@ -20,7 +21,7 @@ width: 240, height: 240, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt index 3e44b9601..b5f1c2dcc 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeOnOverlayClick: false, @@ -22,7 +23,7 @@ onPressed: close, ), child: const SizedBox(width: 240, height: 240)), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt index abea61c66..aa75133dc 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeOnOverlayClick: true, @@ -26,7 +27,7 @@ height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt index c90d9356a..05204a2ce 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt @@ -7,14 +7,15 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.left, width: 280, child: Container( color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt index 997c4d2f9..6f7efe555 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt @@ -7,14 +7,15 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, child: Container( color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt index 1868b2d6a..d1c6b9e5e 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt @@ -7,7 +7,8 @@ type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.top, height: 240, @@ -17,7 +18,7 @@ color: TTheme.of(context).bgColorContainer, height: 240, )), - ).show(context); + ); }, ); } \ No newline at end of file diff --git a/tdesign-component/example/lib/component_test/popup_test.dart b/tdesign-component/example/lib/component_test/popup_test.dart index e85184870..1a4a1cbc7 100644 --- a/tdesign-component/example/lib/component_test/popup_test.dart +++ b/tdesign-component/example/lib/component_test/popup_test.dart @@ -25,10 +25,11 @@ class TestPage extends StatefulWidget { class _TestPageState extends State { void _showProblemDialog() { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, - title: 'title', + titleBuilder: (_) => TText('title'), radius: 20, backgroundColor: const Color(0xFFFAFFFC), child: Container( @@ -40,7 +41,7 @@ class _TestPageState extends State { ], ), )), - ).show(context); + ); } @override diff --git a/tdesign-component/example/lib/page/t_indexes_page.dart b/tdesign-component/example/lib/page/t_indexes_page.dart index 774fafb8e..3409e5999 100644 --- a/tdesign-component/example/lib/page/t_indexes_page.dart +++ b/tdesign-component/example/lib/page/t_indexes_page.dart @@ -157,7 +157,8 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -173,7 +174,7 @@ Widget _buildSimple(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } @@ -189,7 +190,8 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -206,7 +208,7 @@ Widget _buildOther(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } @@ -222,7 +224,8 @@ Widget _buildCustomIndexes(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -246,7 +249,7 @@ Widget _buildCustomIndexes(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } diff --git a/tdesign-component/example/lib/page/t_picker_page.dart b/tdesign-component/example/lib/page/t_picker_page.dart index 04fbc2bb1..4c867ea7c 100644 --- a/tdesign-component/example/lib/page/t_picker_page.dart +++ b/tdesign-component/example/lib/page/t_picker_page.dart @@ -190,11 +190,12 @@ class _TPickerPageState extends State { /// TPicker 自带「取消 / 标题 / 确认」工具栏,业务方在 onCancel/onConfirm /// 中自行决定是否调用 Navigator.pop 关闭弹窗。 void _showPickerPopup(BuildContext context, {required Widget picker}) { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: Material( color: TTheme.of(context).bgColorContainer, child: SafeArea( @@ -202,7 +203,7 @@ class _TPickerPageState extends State { child: picker, ), )), - ).show(context); + ); } // ========== 嵌入式容器 ========== diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart index 6af27f73f..f12344329 100644 --- a/tdesign-component/example/lib/page/t_popup_page.dart +++ b/tdesign-component/example/lib/page/t_popup_page.dart @@ -12,24 +12,22 @@ class TPopupPage extends StatelessWidget { static const double _headerHeight = 58; - /// 底部标题 + 关闭(自定义 headerBuilder,使用 [TPopupHeaderData])。 + /// 底部标题 + 关闭(自定义 headerBuilder:标题居中 + 右侧关闭图标)。 static TPopupHeaderBuilder _bottomTitleCloseHeader({ String? title, - required VoidCallback onClose, }) { - return (BuildContext ctx, TPopupHeaderData data) { + return (BuildContext ctx, VoidCallback close) { final theme = TTheme.of(ctx); - final headerTitle = data.title ?? - (title != null && title.isNotEmpty - ? TText( - title, - textColor: theme.textColorPrimary, - font: theme.fontTitleLarge, - fontWeight: FontWeight.w700, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ) - : null); + final headerTitle = (title != null && title.isNotEmpty) + ? TText( + title, + textColor: theme.textColorPrimary, + font: theme.fontTitleLarge, + fontWeight: FontWeight.w700, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + : null; return SizedBox( height: _headerHeight, child: Padding( @@ -41,7 +39,7 @@ class TPopupPage extends StatelessWidget { ), IconButton( icon: Icon(TIcons.close, color: theme.textColorSecondary), - onPressed: onClose, + onPressed: close, ), ], ), @@ -100,24 +98,24 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - cancel: TText( + titleBuilder: (_) => TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), + cancelBuilder: (_, __) => TText( '点这里确认!', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontBodyLarge, ), - confirm: TText( + confirmBuilder: (_, __) => TText( '关闭', textColor: TTheme.of(context).errorNormalColor, font: TTheme.of(context).fontBodyLarge, ), - onCancel: () => TToast.showText('确认', context: context), child: Container(height: 200)), - ).show(context); + ); }, ); }, @@ -133,16 +131,16 @@ class TPopupPage extends StatelessWidget { size: TButtonSize.large, onTap: () { TPopupHandle? handle; - handle = TPopup( + handle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, headerBuilder: _bottomTitleCloseHeader( title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - onClose: () => handle?.close(), ), child: Container(height: 200)), - ).show(context); + ); }, ); }, @@ -162,17 +160,17 @@ class TPopupPage extends StatelessWidget { size: TButtonSize.large, onTap: () { TPopupHandle? handle; - handle = TPopup( + handle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, radius: 6, headerBuilder: _bottomTitleCloseHeader( title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - onClose: () => handle?.close(), ), child: Container(height: 200)), - ).show(context); + ); }, ), const SizedBox(height: 16), @@ -183,26 +181,25 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, radius: 6, - title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', - cancel: TText( + titleBuilder: (_) => TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), + cancelBuilder: (_, __) => TText( '点这里确认!', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontBodyLarge, ), - confirm: TText( + confirmBuilder: (_, __) => TText( '关闭', textColor: TTheme.of(context).errorNormalColor, font: TTheme.of(context).fontBodyLarge, ), - onCancel: () => - TToast.showText('确认', context: context), child: Container(height: 200)), - ).show(context); + ); }, ), const SizedBox(height: 16), @@ -213,7 +210,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, width: 240, @@ -228,7 +226,7 @@ class TPopupPage extends StatelessWidget { onPressed: close, ), child: const SizedBox(height: 240, width: 240)), - ).show(context); + ); }, ), const SizedBox(height: 16), @@ -239,14 +237,15 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, width: 240, height: 240, radius: 6, child: const SizedBox(height: 240, width: 240)), - ).show(context); + ); }, ), ], @@ -265,7 +264,8 @@ class TPopupPage extends StatelessWidget { onTap: () { final renderBox = navBarkey.currentContext!.findRenderObject() as RenderBox; - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -273,7 +273,7 @@ class TPopupPage extends StatelessWidget { child: Container( color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); }, @@ -293,7 +293,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.top, height: 240, @@ -303,7 +304,7 @@ class TPopupPage extends StatelessWidget { color: TTheme.of(context).bgColorContainer, height: 240, )), - ).show(context); + ); }, ); } @@ -317,14 +318,15 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.left, width: 280, child: Container( color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -338,7 +340,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeBuilder: null, @@ -351,7 +354,7 @@ class TPopupPage extends StatelessWidget { width: 240, height: 240, )), - ).show(context); + ); }, ); } @@ -365,7 +368,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 240, @@ -374,7 +378,7 @@ class TPopupPage extends StatelessWidget { color: TTheme.of(context).bgColorContainer, height: 240, )), - ).show(context); + ); }, ); } @@ -388,21 +392,22 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, child: Container( color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } // --- 02 组件示例 --- - /// 外层 Popup 的 child 内再 `TPopup(options: …).show`:用各自 [TPopupHandle] 关闭。 + /// 外层 Popup 的 child 内再 `TPopup.show(innerContext, options: …)`:用各自 [TPopupHandle] 关闭。 @Demo(group: 'popup') Widget _buildNestedPopup(BuildContext context) { return TButton( @@ -413,7 +418,8 @@ class TPopupPage extends StatelessWidget { size: TButtonSize.large, onTap: () { TPopupHandle? outerHandle; - outerHandle = TPopup( + outerHandle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 360, @@ -436,18 +442,19 @@ class TPopupPage extends StatelessWidget { theme: TButtonTheme.primary, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + innerContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, - title: '内层标题', + titleBuilder: (_) => const TText('内层标题'), child: Container( height: 160, color: TTheme.of(innerContext) .bgColorSecondaryContainer, ), ), - ).show(innerContext); + ); }, ), const SizedBox(height: 12), @@ -463,7 +470,7 @@ class TPopupPage extends StatelessWidget { ); }, )), - ).show(context); + ); }, ); } @@ -477,14 +484,14 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, - title: '标题文字', - onConfirm: () => TToast.showText('确定', context: context), + titleBuilder: (_) => TText('标题文字'), child: Container(height: 200)), - ).show(context); + ); }, ); } @@ -498,16 +505,17 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, - cancel: TText( + cancelBuilder: (_, __) => TText( '关闭', textColor: TTheme.of(context).textColorSecondary, font: TTheme.of(context).fontBodyLarge, ), - titleWidget: Row( + titleBuilder: (_) => Row( mainAxisSize: MainAxisSize.min, children: [ Icon(TIcons.info_circle, @@ -520,14 +528,14 @@ class TPopupPage extends StatelessWidget { ), ], ), - confirm: TText( + confirmBuilder: (_, __) => TText( '完成', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontTitleMedium, fontWeight: FontWeight.w600, ), child: Container(height: 200)), - ).show(context); + ); }, ); } @@ -541,7 +549,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeOnOverlayClick: false, @@ -556,7 +565,7 @@ class TPopupPage extends StatelessWidget { onPressed: close, ), child: const SizedBox(width: 240, height: 240)), - ).show(context); + ); }, ); } @@ -570,7 +579,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeOnOverlayClick: true, @@ -589,7 +599,7 @@ class TPopupPage extends StatelessWidget { height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -605,17 +615,18 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 320, margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - title: '日历式留白', + titleBuilder: (_) => TText('日历式留白'), child: Container( height: 240, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -629,18 +640,19 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, showOverlay: false, // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) - title: '无蒙层', + titleBuilder: (_) => const TText('无蒙层'), child: Container( height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -654,7 +666,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 260, @@ -663,7 +676,7 @@ class TPopupPage extends StatelessWidget { height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -677,7 +690,8 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 240, @@ -686,7 +700,7 @@ class TPopupPage extends StatelessWidget { height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } diff --git a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart index f8722f0e1..157909917 100644 --- a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart +++ b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart @@ -332,17 +332,18 @@ class TActionSheet { break; } - _actionSheetHandle = TPopup( + _actionSheetHandle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, showOverlay: showOverlay, closeOnOverlayClick: showOverlay && closeOnOverlayClick, overlayColor: showOverlay ? null : Colors.transparent, onClosed: onClose, child: sheetChild, ), - ).show(context); + ); } } diff --git a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart index 05844f2ae..c897c7273 100644 --- a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart +++ b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart @@ -69,11 +69,12 @@ class TCalendarPopup { return; } final childWidget = builder?.call(context) ?? child; - _calendarHandle = TPopup( + _calendarHandle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, margin: EdgeInsets.only(top: top ?? 0), closeOnOverlayClick: false, onOverlayClick: () { @@ -91,7 +92,7 @@ class TCalendarPopup { child: childWidget!, ), ), - ).show(context); + ); } void _onClose() { diff --git a/tdesign-component/lib/src/components/drawer/t_drawer.dart b/tdesign-component/lib/src/components/drawer/t_drawer.dart index 5c85ffe6d..c255fe685 100644 --- a/tdesign-component/lib/src/components/drawer/t_drawer.dart +++ b/tdesign-component/lib/src/components/drawer/t_drawer.dart @@ -108,7 +108,8 @@ class TDrawer { final overlayEnabled = showOverlay ?? true; final dismissible = overlayEnabled && (closeOnOverlayClick ?? true); - _drawerHandle = TPopup( + _drawerHandle = TPopup.show( + context, options: TPopupOptions( placement: placement == TDrawerPlacement.right ? TPopupPlacement.right @@ -134,7 +135,7 @@ class TDrawer { isShowLastBordered: isShowLastBordered, ), ), - ).show(context); + ); } void open() { diff --git a/tdesign-component/lib/src/components/popup/_popup_center_close.dart b/tdesign-component/lib/src/components/popup/_popup_center_close.dart index af8a67926..967951ba1 100644 --- a/tdesign-component/lib/src/components/popup/_popup_center_close.dart +++ b/tdesign-component/lib/src/components/popup/_popup_center_close.dart @@ -7,7 +7,11 @@ import '../icon/t_icons.dart'; import 't_popup_options.dart'; import 't_popup_types.dart'; -/// 构建 center 面板下方关闭控件(默认图标或 [closeBuilder])。 +/// 构建 center 面板下方关闭控件。 +/// +/// - `closeBuilder` 为 sentinel [kPopupDefaultClose] → 内置圆形关闭图标。 +/// - 自定义 → 调用用户 builder。 +/// - 调用方需保证 `options.closeBuilder != null`。 Widget buildPopupCenterCloseControl({ required BuildContext context, required TPopupOptions options, @@ -52,10 +56,7 @@ class PopupCenterUnderClose extends StatelessWidget { ); } - void close() { - options.onCloseBtn?.call(); - onCloseWithTrigger(TPopupTrigger.closeBtn); - } + void close() => onCloseWithTrigger(TPopupTrigger.programmatic); final closeControl = buildPopupCenterCloseControl( context: context, diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart index 4454e9971..fb4ad65b4 100644 --- a/tdesign-component/lib/src/components/popup/_popup_header.dart +++ b/tdesign-component/lib/src/components/popup/_popup_header.dart @@ -11,6 +11,10 @@ import 't_popup_options.dart'; import 't_popup_types.dart'; /// 内置标题栏区域(仅 [TPopupPlacement.bottom])。 +/// +/// - `headerBuilder` 为 sentinel [kPopupDefaultHeader] → 内置三段式(cancel | title | confirm)。 +/// - `headerBuilder` 为自定义 → 整行替换,库内不再插入任何子 Widget。 +/// - `headerBuilder` 为 null → 不渲染头部。 class PopupHeader extends StatelessWidget { const PopupHeader({ super.key, @@ -23,166 +27,76 @@ class PopupHeader extends StatelessWidget { static const double headerHeight = 58; + void _close() { + onCloseWithTrigger(TPopupTrigger.programmatic); + } + @override Widget build(BuildContext context) { - if (options.placement != TPopupPlacement.bottom || options.hasNoHeader) { + if (options.placement != TPopupPlacement.bottom || + options.headerBuilder == null) { return const SizedBox.shrink(); } if (options.useCustomHeader) { - return options.headerBuilder!( - context, - _buildHeaderData(context), - ); - } - - if (options.useActionHeader) { - return SizedBox( - height: headerHeight, - child: _ActionHeader( - options: options, - onCloseWithTrigger: onCloseWithTrigger, - ), - ); - } - - final title = _buildTitleWidget(context); - if (title == null) { - return const SizedBox.shrink(); + return options.headerBuilder!(context, _close); } + // 走内置三段式 return SizedBox( height: headerHeight, - child: Container( - alignment: - options.titleAlignLeft ? Alignment.centerLeft : Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 16), - child: title, + child: _DefaultHeader( + options: options, + close: _close, ), ); } - - TPopupHeaderData _buildHeaderData(BuildContext context) { - final theme = TTheme.of(context); - return TPopupHeaderData( - title: _buildTitleWidget(context), - cancel: - options.showCancelSlot ? _buildCancelWidget(context, theme) : null, - confirm: - options.showConfirmSlot ? _buildConfirmWidget(context, theme) : null, - onCancel: options.onCancel, - onConfirm: options.onConfirm, - ); - } - - Widget? _buildTitleWidget(BuildContext context) { - if (options.titleWidget != null) { - return options.titleWidget; - } - if (options.title != null && options.title!.isNotEmpty) { - return TText( - options.title!, - textColor: TTheme.of(context).textColorPrimary, - font: TTheme.of(context).fontTitleLarge, - fontWeight: FontWeight.w700, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - } - return null; - } - - Widget _buildCancelWidget(BuildContext context, TThemeData theme) { - if (options.cancelBuilder != null) { - return options.cancelBuilder!(context); - } - if (TPopupOptions.isActionDefault(options.cancel)) { - return TText( - options.cancelBtn ?? context.resource.cancel, - textColor: theme.textColorSecondary, - font: theme.fontBodyLarge, - ); - } - return options.cancel!; - } - - Widget _buildConfirmWidget(BuildContext context, TThemeData theme) { - if (options.confirmBuilder != null) { - return options.confirmBuilder!(context); - } - if (TPopupOptions.isActionDefault(options.confirm)) { - return TText( - options.confirmBtn ?? context.resource.confirm, - textColor: theme.brandNormalColor, - font: theme.fontTitleMedium, - fontWeight: FontWeight.w600, - ); - } - return options.confirm!; - } } -class _ActionHeader extends StatelessWidget { - const _ActionHeader({ +class _DefaultHeader extends StatelessWidget { + const _DefaultHeader({ required this.options, - required this.onCloseWithTrigger, + required this.close, }); final TPopupOptions options; - final void Function(TPopupTrigger trigger) onCloseWithTrigger; + final VoidCallback close; @override Widget build(BuildContext context) { final theme = TTheme.of(context); - final title = options.titleWidget ?? - TText( - options.title ?? '', - textColor: theme.textColorPrimary, - font: theme.fontTitleLarge, - fontWeight: FontWeight.w700, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); + final showCancel = options.cancelBuilder != null; + final showConfirm = options.confirmBuilder != null; + + final title = options.titleBuilder?.call(context); return Row( children: [ - if (options.showCancelSlot) + if (showCancel) Padding( padding: EdgeInsets.only(left: theme.spacer8), child: Semantics( button: true, label: _cancelSemanticsLabel(context, options), excludeSemantics: true, - child: TToolbarPressable( - onTap: () { - options.onCancel?.call(); - if (options.autoCloseOnCancel) { - onCloseWithTrigger(TPopupTrigger.cancelBtn); - } - }, - child: _buildCancel(context, theme), - ), + child: _buildCancel(context, theme), ), ) else SizedBox(width: theme.spacer16), - Expanded(child: Center(child: title)), - if (options.showConfirmSlot) + Expanded( + child: title == null + ? const SizedBox.shrink() + : Center(child: _titleWrap(context, theme, title)), + ), + if (showConfirm) Padding( padding: EdgeInsets.only(right: theme.spacer8), child: Semantics( button: true, label: _confirmSemanticsLabel(context, options), excludeSemantics: true, - child: TToolbarPressable( - onTap: () { - options.onConfirm?.call(); - if (options.autoCloseOnConfirm) { - onCloseWithTrigger(TPopupTrigger.confirmBtn); - } - }, - child: _buildConfirm(context, theme), - ), + child: _buildConfirm(context, theme), ), ) else @@ -191,48 +105,54 @@ class _ActionHeader extends StatelessWidget { ); } + Widget _titleWrap(BuildContext context, TThemeData theme, Widget child) { + // 标题由用户 builder 决定样式,这里只做布局约束。 + return DefaultTextStyle.merge( + style: TextStyle( + color: theme.textColorPrimary, + fontSize: theme.fontTitleLarge?.size, + fontWeight: FontWeight.w700, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + child: child, + ); + } + Widget _buildCancel(BuildContext context, TThemeData theme) { - if (options.cancelBuilder != null) { - return options.cancelBuilder!(context); - } - if (TPopupOptions.isActionDefault(options.cancel)) { - return TText( - options.cancelBtn ?? context.resource.cancel, - textColor: theme.textColorSecondary, - font: theme.fontBodyLarge, + if (isPopupDefaultCancel(options.cancelBuilder)) { + return TToolbarPressable( + onTap: close, + child: TText( + context.resource.cancel, + textColor: theme.textColorSecondary, + font: theme.fontBodyLarge, + ), ); } - return options.cancel!; + return options.cancelBuilder!(context, close); } Widget _buildConfirm(BuildContext context, TThemeData theme) { - if (options.confirmBuilder != null) { - return options.confirmBuilder!(context); - } - if (TPopupOptions.isActionDefault(options.confirm)) { - return TText( - options.confirmBtn ?? context.resource.confirm, - textColor: theme.brandNormalColor, - font: theme.fontTitleMedium, - fontWeight: FontWeight.w600, + if (isPopupDefaultConfirm(options.confirmBuilder)) { + return TToolbarPressable( + onTap: close, + child: TText( + context.resource.confirm, + textColor: theme.brandNormalColor, + font: theme.fontTitleMedium, + fontWeight: FontWeight.w600, + ), ); } - return options.confirm!; + return options.confirmBuilder!(context, close); } } String _cancelSemanticsLabel(BuildContext context, TPopupOptions options) { - final btn = options.cancelBtn; - if (btn != null && btn.isNotEmpty) { - return btn; - } return context.resource.cancel; } String _confirmSemanticsLabel(BuildContext context, TPopupOptions options) { - final btn = options.confirmBtn; - if (btn != null && btn.isNotEmpty) { - return btn; - } return context.resource.confirm; } diff --git a/tdesign-component/lib/src/components/popup/_popup_layout.dart b/tdesign-component/lib/src/components/popup/_popup_layout.dart index 8bba1efe6..986974275 100644 --- a/tdesign-component/lib/src/components/popup/_popup_layout.dart +++ b/tdesign-component/lib/src/components/popup/_popup_layout.dart @@ -3,6 +3,9 @@ import 'package:flutter/material.dart'; import 't_popup_types.dart'; /// 根据 placement 计算 Positioned 约束。 +/// +/// center 模式只负责 [Positioned.fill] + [Center],**面板尺寸由 PopupShell 决定**, +/// 这里不再插入 SizedBox(避免和 shell 中的尺寸约束双重包裹)。 class PopupLayout { PopupLayout({ required this.placement, @@ -10,7 +13,6 @@ class PopupLayout { required this.margin, this.width, this.height, - this.centerLooseHeight = false, }); final TPopupPlacement placement; @@ -19,9 +21,6 @@ class PopupLayout { final double? width; final double? height; - /// 居中且关闭按钮在内容下方时,不限制总高度(含下方关闭区)。 - final bool centerLooseHeight; - static const double defaultDrawerWidth = 280; EdgeInsets resolvedMargin() { @@ -87,13 +86,7 @@ class PopupLayout { ); case TPopupPlacement.center: return Positioned.fill( - child: Center( - child: SizedBox( - width: centerLooseHeight ? null : width, - height: centerLooseHeight ? null : height, - child: child, - ), - ), + child: Center(child: child), ); } } diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart index 2b384de19..7d78f4b84 100644 --- a/tdesign-component/lib/src/components/popup/_popup_route.dart +++ b/tdesign-component/lib/src/components/popup/_popup_route.dart @@ -109,8 +109,6 @@ class TPopupNavigatorRoute extends PopupRoute { margin: options.margin, width: options.width, height: options.height, - centerLooseHeight: options.placement == TPopupPlacement.center && - options.closeBuilder != null, ); final t = curved.value; diff --git a/tdesign-component/lib/src/components/popup/_popup_shell.dart b/tdesign-component/lib/src/components/popup/_popup_shell.dart index bc66873e0..82eb655ed 100644 --- a/tdesign-component/lib/src/components/popup/_popup_shell.dart +++ b/tdesign-component/lib/src/components/popup/_popup_shell.dart @@ -8,7 +8,7 @@ import '_popup_header.dart'; import 't_popup_options.dart'; import 't_popup_types.dart'; -/// 浮层内容外壳:圆角、Header(仅 bottom)、child。 +/// 浮层内容外壳:圆角、Header(仅 bottom)、child;center 由 [PopupCenterUnderClose] 接管下方关闭区。 class PopupShell extends StatelessWidget { const PopupShell({ super.key, @@ -26,72 +26,74 @@ class PopupShell extends StatelessWidget { final backgroundColor = options.backgroundColor ?? theme.bgColorContainer; final borderRadius = _borderRadius(options.placement, radius); - Widget content = options.child; - if (options.placement == TPopupPlacement.center) { - if (options.closeBuilder != null) { - final panel = Container( - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(radius), - ), - clipBehavior: Clip.antiAlias, - child: content, - ); - return PopupCenterUnderClose( - options: options, - content: panel, - onCloseWithTrigger: onCloseWithTrigger, - ); - } - return Center( - child: SizedBox( - width: options.width, - height: options.height, - child: Container( - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(radius), - ), - clipBehavior: Clip.antiAlias, - child: content, - ), - ), + return _buildCenter(context, radius, backgroundColor); + } + + return _buildEdge(context, borderRadius, backgroundColor); + } + + Widget _buildCenter(BuildContext context, double radius, Color background) { + final panel = Container( + decoration: BoxDecoration( + color: background, + borderRadius: BorderRadius.circular(radius), + ), + clipBehavior: Clip.antiAlias, + child: options.child, + ); + + if (options.closeBuilder != null) { + return PopupCenterUnderClose( + options: options, + content: panel, + onCloseWithTrigger: onCloseWithTrigger, ); } + return SizedBox( + width: options.width, + height: options.height, + child: panel, + ); + } + Widget _buildEdge( + BuildContext context, + BorderRadius? borderRadius, + Color background, + ) { final useExpanded = options.placement == TPopupPlacement.left || options.placement == TPopupPlacement.right || options.height != null; - Widget panel = Container( + final body = options.placement == TPopupPlacement.bottom + ? Column( + mainAxisSize: useExpanded ? MainAxisSize.max : MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + PopupHeader( + options: options, + onCloseWithTrigger: onCloseWithTrigger, + ), + if (useExpanded) Expanded(child: options.child) else options.child, + ], + ) + : (useExpanded + ? Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [Expanded(child: options.child)], + ) + : options.child); + + return Container( decoration: BoxDecoration( - color: backgroundColor, + color: background, borderRadius: borderRadius, ), clipBehavior: Clip.antiAlias, - child: options.placement == TPopupPlacement.bottom - ? Column( - mainAxisSize: useExpanded ? MainAxisSize.max : MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - PopupHeader( - options: options, - onCloseWithTrigger: onCloseWithTrigger, - ), - if (useExpanded) Expanded(child: content) else content, - ], - ) - : (useExpanded - ? Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [Expanded(child: content)], - ) - : content), + child: body, ); - - return panel; } BorderRadius? _borderRadius(TPopupPlacement placement, double radius) { diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart index bd1a5bdf6..7719daba5 100644 --- a/tdesign-component/lib/src/components/popup/t_popup.dart +++ b/tdesign-component/lib/src/components/popup/t_popup.dart @@ -10,67 +10,45 @@ export 't_popup_types.dart'; part 't_popup_handle.dart'; part 't_popup_tracker.dart'; -/// 弹出层:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 +/// 弹出层命名空间:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 /// -/// ## 怎么用 -/// -/// **命令式(推荐)** — 先组配置,再 `show`,用返回的 [TPopupHandle] 关闭: +/// 仅提供静态入口 [show];返回的 [TPopupHandle] 控制本次浮层的显隐。 /// /// ```dart -/// final handle = TPopup( +/// final handle = TPopup.show( +/// context, /// options: TPopupOptions( /// placement: TPopupPlacement.bottom, /// title: '标题', /// child: MyPanel(), /// ), -/// ).show(context); +/// ); /// -/// // 关闭这一层(须保留 handle,不要用 context 猜栈顶) +/// // 关闭后再开(同一 handle) /// handle.close(); -/// ``` -/// -/// **声明式** — 包住子树,`initialVisible: true` 时首帧自动 [show];[build] 只渲染 [options.child]: -/// -/// ```dart -/// TPopup( -/// options: TPopupOptions(child: body), -/// initialVisible: true, -/// ) +/// handle.open(context); /// ``` /// /// 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 /// [TPopupOptions.normalized] 中裁掉)。 -class TPopup extends StatefulWidget { - const TPopup({ - super.key, - required this.options, - this.initialVisible = false, - this.navigatorContext, - this.useRootNavigator = false, - }); - - /// 浮层内容与行为配置,见 [TPopupOptions]。 - final TPopupOptions options; - - /// 为 true 时,挂载后首帧自动调用 [show](仅声明式)。 - final bool initialVisible; - - /// 指定使用哪个 [Navigator];默认 [show] 传入的 `context` 所在 Navigator。 - final BuildContext? navigatorContext; +final class TPopup { + const TPopup._(); - /// 为 true 时使用根 [Navigator](嵌套导航场景)。 - final bool useRootNavigator; - - /// 打开浮层并压入独立路由。 + /// 命令式打开浮层并压入独立路由。 /// - /// 返回 [TPopupHandle]:用 [TPopupHandle.close] 关闭**本次**打开的层; + /// 返回 [TPopupHandle]:可用 [TPopupHandle.close] / [TPopupHandle.open] 控制显隐; /// [TPopupHandle.isShowing] 可查询是否仍在展示。 /// /// 同一按钮在页面 context 上重复调用时,若已有展示中的 Popup 会返回已有 handle(防连点)。 - TPopupHandle show(BuildContext context) { - final normalized = options.normalized(); - normalized.assertPlacementParams(); - + /// + /// - [navigatorContext]:指定使用哪个 [Navigator],默认用 `context`。 + /// - [useRootNavigator]:是否使用根 [Navigator](嵌套导航场景)。 + static TPopupHandle show( + BuildContext context, { + required TPopupOptions options, + BuildContext? navigatorContext, + bool useRootNavigator = false, + }) { final navContext = navigatorContext ?? context; final navigator = Navigator.of( navContext, @@ -84,68 +62,12 @@ class TPopup extends StatefulWidget { return existing; } - TPopupNavigatorRoute? route; - late TPopupHandle handle; - - void closeWithTrigger(TPopupTrigger trigger, [Object? result]) { - if (!handle.isShowing) { - return; - } - handle._markClosing(); - route?.fireCloseStart(trigger); - navigator.pop(result); - } - - route = TPopupNavigatorRoute( - options: normalized, - onCloseWithTrigger: closeWithTrigger, - ); - - handle = TPopupHandle._( - route: route, - onCloseWithTrigger: closeWithTrigger, + final handle = TPopupHandle._( + options: options, + navigatorContext: navigatorContext, + useRootNavigator: useRootNavigator, ); - - TPopupTracker.push(navigator, handle); - - navigator.push(route).whenComplete(() { - TPopupTracker.remove(navigator, handle); - handle._detachRoute(); - }); - + handle.open(context); return handle; } - - @override - State createState() => _TPopupState(); -} - -class _TPopupState extends State { - TPopupHandle? _handle; - - @override - void initState() { - super.initState(); - if (widget.initialVisible) { - WidgetsBinding.instance.addPostFrameCallback((_) => _open()); - } - } - - @override - void dispose() { - _handle?.close(); - super.dispose(); - } - - void _open() { - if (_handle?.isShowing == true) { - return; - } - _handle = widget.show(context); - } - - @override - Widget build(BuildContext context) { - return widget.options.child; - } } diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart index 4987452f9..5c021ebcf 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart @@ -1,39 +1,99 @@ part of 't_popup.dart'; -/// [TPopup.show] 的返回值,表示**一次**打开操作。 +/// [TPopup.show] 的返回值:可查询展示状态,并多次 [open] / [close]。 /// -/// 保存此对象并在需要时调用 [close];不要依赖 `context` 推断要关哪一层。 +/// 保存此对象;关闭后再次 [open] 会按创建时的 [TPopupOptions] 重新压入路由。 /// /// ```dart -/// final handle = TPopup(options: opts).show(context); -/// if (handle.isShowing) { -/// handle.close('result'); // 可选 result 传给 Navigator.pop +/// final handle = TPopup.show(context, options: opts); +/// handle.close(); +/// if (!handle.isShowing) { +/// handle.open(context); /// } /// ``` class TPopupHandle { TPopupHandle._({ - required void Function(TPopupTrigger trigger, [Object? result]) - onCloseWithTrigger, - TPopupNavigatorRoute? route, - }) : _route = route, - _onCloseWithTrigger = onCloseWithTrigger; + required this.options, + this.navigatorContext, + this.useRootNavigator = false, + }); + + /// 打开/再次打开时使用的配置(每次 [open] 会 [TPopupOptions.normalized])。 + final TPopupOptions options; + + /// 与 [TPopup.show] 相同:指定 Navigator 的 context。 + final BuildContext? navigatorContext; + + /// 与 [TPopup.show] 相同:是否使用根 Navigator。 + final bool useRootNavigator; TPopupNavigatorRoute? _route; - final void Function(TPopupTrigger trigger, [Object? result]) - _onCloseWithTrigger; bool _isClosed = false; - /// 本次 [TPopup.show] 对应的浮层是否仍在展示。 + /// 浮层是否仍在展示(路由在栈中且未进入关闭流程)。 bool get isShowing => _route != null && !_isClosed; - /// 关闭本次 [TPopup.show] 打开的浮层([TPopupTrigger.programmatic])。 + /// 打开或重新打开浮层。 + /// + /// 已展示时调用无副作用。关闭后再次调用会压入新的 [TPopupNavigatorRoute]。 + void open(BuildContext context) { + if (isShowing) { + return; + } + final normalized = options.normalized(); + normalized.assertPlacementParams(); + + final navigator = _navigatorOf(context); + _isClosed = false; + + TPopupNavigatorRoute? route; + + void closeWithTrigger(TPopupTrigger trigger, [Object? result]) { + if (!isShowing) { + return; + } + _markClosing(); + route?.fireCloseStart(trigger); + navigator.pop(result); + } + + route = TPopupNavigatorRoute( + options: normalized, + onCloseWithTrigger: closeWithTrigger, + ); + _route = route; + + TPopupTracker.push(navigator, this); + + navigator.push(route).whenComplete(() { + TPopupTracker.remove(navigator, this); + _detachRoute(); + }); + } + + /// 关闭当前展示的浮层([TPopupTrigger.programmatic])。 /// /// 已关闭或未展示时调用无副作用。嵌套多层时须用**对应层**的 handle 关闭。 void close([Object? result]) { if (!isShowing) { return; } - _onCloseWithTrigger(TPopupTrigger.programmatic, result); + _markClosing(); + _route?.fireCloseStart(TPopupTrigger.programmatic); + _navigatorOfForClose().pop(result); + } + + NavigatorState _navigatorOf(BuildContext context) { + final navContext = navigatorContext ?? context; + return Navigator.of( + navContext, + rootNavigator: useRootNavigator, + ); + } + + /// [close] 不依赖外部 context,使用路由所在 Navigator。 + NavigatorState _navigatorOfForClose() { + return _route!.navigator!; } void _markClosing() { diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart index 00b536f8e..ac22c1b98 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_options.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart @@ -2,25 +2,30 @@ import 'package:flutter/material.dart'; import 't_popup_types.dart'; -/// 浮层配置:[TPopup] 构造与 [TPopup.show] 的唯一参数来源。 +/// 浮层配置:[TPopup.show] 的唯一参数来源。 /// /// ## 按 [placement] 用哪些字段 /// -/// | placement | 常用字段 | -/// |-----------|----------| -/// | [TPopupPlacement.bottom] | `title` / `cancel` / `confirm` / `headerBuilder`、`height`、`margin` | -/// | [TPopupPlacement.center] | `closeBuilder`、`width`、`height`(有下方关闭时) | -/// | [TPopupPlacement.top] / [left] / [right] | 主要 `child`、`margin`、方向对应 `width` 或 `height` | +/// | placement | 头部 / 关闭区 | 尺寸字段 | +/// |-----------|----------------|----------| +/// | [TPopupPlacement.bottom] | `headerBuilder` / `titleBuilder` / `cancelBuilder` / `confirmBuilder` | `height`、`margin` | +/// | [TPopupPlacement.center] | `closeBuilder` | `width`、`height` | +/// | [TPopupPlacement.top] | — | `height`、`margin.top` / `left` / `right` | +/// | [TPopupPlacement.left] / [right] | — | `width`、对应方向 `margin` | /// -/// 传给其它 placement 的 bottom / center 专用字段会在 [normalized] 里裁掉。 +/// 非 bottom 上传 `headerBuilder` / `titleBuilder` / `cancelBuilder` / `confirmBuilder`、 +/// 或非 center 上传 `closeBuilder` 都会被 [normalized] 裁掉。 /// -/// ## 三态占位(bottom / center) +/// ## Builder 三态 /// -/// - **未传参数**:使用默认 UI(如默认取消/确定文案、默认关闭图标)。 -/// - **显式 `null`**:隐藏该槽位(如 `cancel: null` 隐藏左侧;`closeBuilder: null` 无关闭按钮)。 -/// - **自定义 Widget / Builder**:完全自定义该区域。 +/// 每个 builder 字段都有三种使用方式: /// -/// [TPopup.show] 内部会先 [normalized] 再绘制。 +/// - **不传**(保留默认)→ 使用内置 UI(如 `headerBuilder` 默认走三段式、`cancelBuilder` 默认显示「取消」、 +/// `closeBuilder` 默认显示圆形关闭图标)。 +/// - **显式传 `null`** → 隐藏该部分。 +/// - **传自定义函数** → 完全替换该部分;函数会拿到 `close` 回调,用于在 onTap 中关闭浮层。 +/// +/// `titleBuilder` 例外:默认为 `null` 表示无标题(不需要内置标题文案)。 class TPopupOptions { const TPopupOptions({ required this.child, @@ -37,22 +42,11 @@ class TPopupOptions { this.preventScrollThrough = true, this.destroyOnClose = false, this.duration = const Duration(milliseconds: 240), - this.title, - this.titleWidget, - this.titleAlignLeft = false, - this.cancelBtn, - this.cancel = kPopupActionDefault, - this.cancelBuilder, - this.onCancel, - this.confirmBtn, - this.confirm = kPopupActionDefault, - this.confirmBuilder, - this.onConfirm, - this.autoCloseOnCancel = true, - this.autoCloseOnConfirm = true, - this.closeBuilder = kPopupDefaultClose, - this.onCloseBtn, this.headerBuilder = kPopupDefaultHeader, + this.titleBuilder, + this.cancelBuilder = kPopupDefaultCancel, + this.confirmBuilder = kPopupDefaultConfirm, + this.closeBuilder = kPopupDefaultClose, this.onOpen, this.onOpened, this.onClose, @@ -70,10 +64,15 @@ class TPopupOptions { /// 宽度;对 left、right、center 生效。 final double? width; - /// 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 + /// 高度;对 top、bottom 生效;center 用于约束面板尺寸。 final double? height; - /// 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 + /// 外边距: + /// - top:`top` / `left` / `right` 生效。 + /// - bottom:`top` > 0 触发「贴顶模式」(日历式留白);否则贴底,`left` / `right` / `bottom` 生效。 + /// - left:`top` / `bottom` / `left` 生效。 + /// - right:`top` / `bottom` / `right` 生效。 + /// - center:全忽略。 final EdgeInsets margin; /// 内容区圆角,默认主题大圆角。 @@ -103,73 +102,54 @@ class TPopupOptions { /// 打开与关闭动画时长(一致)。 final Duration duration; - /// bottom 操作栏中间标题文案。 - final String? title; - - /// bottom 操作栏中间标题组件,优先级高于 [title]。 - final Widget? titleWidget; - - /// bottom 仅标题行时是否左对齐,默认居中。 - final bool titleAlignLeft; - - /// bottom 左侧按钮文案,覆盖默认「取消」。 - final String? cancelBtn; - - /// bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。 - final Widget? cancel; + // ============ bottom 头部 ============ - /// bottom 左侧按钮构建器,优先级高于 [cancel]。 - final WidgetBuilder? cancelBuilder; - - /// 点击 bottom 左侧按钮回调。 - final VoidCallback? onCancel; - - /// bottom 右侧按钮文案,覆盖默认「确定」。 - final String? confirmBtn; - - /// bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 - final Widget? confirm; - - /// bottom 右侧按钮构建器,优先级高于 [confirm]。 - final WidgetBuilder? confirmBuilder; + /// bottom 头部构建器: + /// - 默认 [kPopupDefaultHeader] → 渲染内置三段式(`cancelBuilder | titleBuilder | confirmBuilder`)。 + /// - `null` → 不显示头部。 + /// - 自定义 `(ctx, close) => Widget` → 完全替换整行头部([titleBuilder] / [cancelBuilder] / + /// [confirmBuilder] 被忽略)。 + final TPopupHeaderBuilder? headerBuilder; - /// 点击 bottom 右侧按钮回调。 - final VoidCallback? onConfirm; + /// bottom 标题槽(仅当 [headerBuilder] 为 [kPopupDefaultHeader] 时生效): + /// - `null` → 无标题。 + /// - 自定义 `(ctx) => Widget` → 显示自定义标题。 + final WidgetBuilder? titleBuilder; - /// 点击取消后是否自动关闭,默认 true。 - final bool autoCloseOnCancel; + /// bottom 左槽(仅当 [headerBuilder] 为 [kPopupDefaultHeader] 时生效): + /// - 默认 [kPopupDefaultCancel] → 显示本地化「取消」按钮,点击关闭浮层。 + /// - `null` → 隐藏左槽。 + /// - 自定义 `(ctx, close) => Widget` → 替换左槽,自行决定是否调 `close()`。 + final TPopupSlotBuilder? cancelBuilder; - /// 点击确定后是否自动关闭,默认 true。 - final bool autoCloseOnConfirm; + /// bottom 右槽(仅当 [headerBuilder] 为 [kPopupDefaultHeader] 时生效): + /// - 默认 [kPopupDefaultConfirm] → 显示本地化「确定」按钮,点击关闭浮层。 + /// - `null` → 隐藏右槽。 + /// - 自定义 `(ctx, close) => Widget` → 替换右槽。 + final TPopupSlotBuilder? confirmBuilder; - /// center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];bottom 与三边忽略。 - final TPopupCloseBuilder? closeBuilder; + // ============ center 关闭区 ============ - /// center 点击关闭控件前的回调。 - final VoidCallback? onCloseBtn; + /// center 面板下方关闭区: + /// - 默认 [kPopupDefaultClose] → 显示圆形关闭图标,点击关闭浮层。 + /// - `null` → 不显示关闭区。 + /// - 自定义 `(ctx, close) => Widget` → 替换关闭区。 + final TPopupSlotBuilder? closeBuilder; - /// bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader];自定义见 [TPopupHeaderBuilder]。 - final TPopupHeaderBuilder? headerBuilder; + // ============ 生命周期 ============ - /// 开始打开时回调(路由入栈)。 final VoidCallback? onOpen; - - /// 打开动画结束后回调。 final VoidCallback? onOpened; - - /// 开始关闭时回调。 final VoidCallback? onClose; - - /// 关闭动画结束且路由移除后回调。 final VoidCallback? onClosed; - - /// 显隐变化及触发来源。 final TPopupVisibleChangeCallback? onVisibleChange; - - /// 点击蒙层时回调(在是否关闭判断之前)。 final VoidCallback? onOverlayClick; /// 按 [placement] 裁剪无效字段,得到路由实际使用的配置副本。 + /// + /// - bottom 才保留 `headerBuilder` / `titleBuilder` / `cancelBuilder` / `confirmBuilder`; + /// 其它 placement 上这些字段强制重置为 sentinel(不渲染头部,因为没渲染入口)。 + /// - center 才保留 `closeBuilder`;其它 placement 重置为 sentinel。 TPopupOptions normalized() { final isBottom = placement == TPopupPlacement.bottom; final isCenter = placement == TPopupPlacement.center; @@ -189,22 +169,11 @@ class TPopupOptions { preventScrollThrough: preventScrollThrough, destroyOnClose: destroyOnClose, duration: duration, - title: isBottom ? title : null, - titleWidget: isBottom ? titleWidget : null, - titleAlignLeft: isBottom ? titleAlignLeft : false, - cancelBtn: isBottom ? cancelBtn : null, - cancel: isBottom ? cancel : null, + headerBuilder: isBottom ? headerBuilder : null, + titleBuilder: isBottom ? titleBuilder : null, cancelBuilder: isBottom ? cancelBuilder : null, - onCancel: isBottom ? onCancel : null, - confirmBtn: isBottom ? confirmBtn : null, - confirm: isBottom ? confirm : null, confirmBuilder: isBottom ? confirmBuilder : null, - onConfirm: isBottom ? onConfirm : null, - autoCloseOnCancel: autoCloseOnCancel, - autoCloseOnConfirm: autoCloseOnConfirm, closeBuilder: isCenter ? closeBuilder : null, - onCloseBtn: isCenter ? onCloseBtn : null, - headerBuilder: isBottom ? headerBuilder : null, onOpen: onOpen, onOpened: onOpened, onClose: onClose, @@ -214,78 +183,99 @@ class TPopupOptions { ); } - bool get showCancelSlot => - placement == TPopupPlacement.bottom && - (cancelBuilder != null || cancel != null); - - bool get showConfirmSlot => - placement == TPopupPlacement.bottom && - (confirmBuilder != null || confirm != null); - - bool get hasNoHeader => - placement == TPopupPlacement.bottom && headerBuilder == null; - - bool get useActionHeader => - placement == TPopupPlacement.bottom && - isPopupDefaultHeader(headerBuilder) && - (showCancelSlot || showConfirmSlot); + // ============ 派生 ============ + /// 是否走自定义 header([headerBuilder] 非 null 且非默认 sentinel)。 bool get useCustomHeader => placement == TPopupPlacement.bottom && headerBuilder != null && !isPopupDefaultHeader(headerBuilder); - static bool isActionDefault(Widget? action) => action is TPopupActionDefault; + /// 是否走内置三段式头部([headerBuilder] 为默认 sentinel)。 + bool get useDefaultHeader => + placement == TPopupPlacement.bottom && + isPopupDefaultHeader(headerBuilder); - bool get useTitleOnlyHeader => + /// 内置三段式中,左槽是否要画(非 null)。 + bool get showCancelSlot => placement == TPopupPlacement.bottom && - isPopupDefaultHeader(headerBuilder) && - !showCancelSlot && - !showConfirmSlot && - ((title != null && title!.isNotEmpty) || titleWidget != null); + useDefaultHeader && + cancelBuilder != null; - bool get hasBuiltInHeader => + /// 内置三段式中,右槽是否要画(非 null)。 + bool get showConfirmSlot => placement == TPopupPlacement.bottom && - !hasNoHeader && - (useCustomHeader || useActionHeader || useTitleOnlyHeader); + useDefaultHeader && + confirmBuilder != null; + + /// bottom 是否实际渲染头部(自定义 / 内置三段中至少有一项可见)。 + bool get hasBuiltInHeader { + if (placement != TPopupPlacement.bottom || headerBuilder == null) { + return false; + } + if (useCustomHeader) { + return true; + } + // 默认三段:任一槽位(cancel/title/confirm)非 null 都算 + return cancelBuilder != null || + confirmBuilder != null || + titleBuilder != null; + } - /// Debug 下检查易误用参数(如 bottom 传 `width`),仅 `debugPrint` 不抛错。 + /// Debug 下检查易误用参数(如 bottom 传 `width`、center 传 `margin`),仅 `debugPrint` 不抛错。 void assertPlacementParams() { assert(() { switch (placement) { + case TPopupPlacement.top: + if (width != null) { + debugPrint('TPopup: width is ignored for placement=top'); + } + if (margin.bottom > 0) { + debugPrint('TPopup: margin.bottom is ignored for placement=top'); + } + break; + case TPopupPlacement.bottom: + if (width != null) { + debugPrint('TPopup: width is ignored for placement=bottom'); + } + break; case TPopupPlacement.left: - case TPopupPlacement.right: if (height != null) { - debugPrint( - 'TPopup: height is ignored for placement=$placement', - ); + debugPrint('TPopup: height is ignored for placement=left'); + } + if (margin.right > 0) { + debugPrint('TPopup: margin.right is ignored for placement=left'); } break; - case TPopupPlacement.center: - if (height != null && closeBuilder == null) { - debugPrint( - 'TPopup: height is ignored for placement=$placement', - ); + case TPopupPlacement.right: + if (height != null) { + debugPrint('TPopup: height is ignored for placement=right'); + } + if (margin.left > 0) { + debugPrint('TPopup: margin.left is ignored for placement=right'); } break; - case TPopupPlacement.top: - case TPopupPlacement.bottom: - if (width != null) { - debugPrint( - 'TPopup: width is ignored for placement=$placement', - ); + case TPopupPlacement.center: + if (margin != EdgeInsets.zero) { + debugPrint('TPopup: margin is ignored for placement=center'); } break; } - if (placement != TPopupPlacement.bottom && - (cancel != null || - confirm != null || - cancelBuilder != null || - confirmBuilder != null)) { + // 非 bottom 设了 header / 三段相关字段(非默认 sentinel)→ 提示 + final hasBottomHeaderCustom = !isPopupDefaultHeader(headerBuilder) || + titleBuilder != null || + !isPopupDefaultCancel(cancelBuilder) || + !isPopupDefaultConfirm(confirmBuilder); + if (placement != TPopupPlacement.bottom && hasBottomHeaderCustom) { debugPrint( - 'TPopup: cancel/confirm only applies to placement=bottom', + 'TPopup: header/title/cancel/confirmBuilder only apply to placement=bottom', ); } + // 非 center 设了 closeBuilder 自定义 → 提示 + if (placement != TPopupPlacement.center && + !isPopupDefaultClose(closeBuilder)) { + debugPrint('TPopup: closeBuilder only applies to placement=center'); + } return true; }()); } diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart index f4c8e20be..212893cd1 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_types.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart @@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart'; /// /// - [top] / [bottom]:纵向滑入,用 `height`、`margin`(bottom 可用 `margin.top` 做日历式留白)。 /// - [left] / [right]:侧栏,用 `width`、`margin`。 -/// - [center]:居中缩放,用 `closeBuilder` 控制下方关闭按钮;不用 bottom 操作栏字段。 +/// - [center]:居中缩放,用 `closeBuilder` 控制下方关闭区。 enum TPopupPlacement { /// 自屏幕顶部滑入;`height` 与 `margin` 的 top/left/right 生效。 top, @@ -15,102 +15,83 @@ enum TPopupPlacement { /// 自屏幕右侧滑入;`width` 与 margin 的 right/top/bottom 生效。 right, - /// 自屏幕底部滑入;默认操作栏(取消 | 标题 | 确定),`height`、`margin` 生效。 + /// 自屏幕底部滑入;默认内置「取消 | 标题 | 确定」头部,`height`、`margin` 生效。 bottom, - /// 屏幕居中弹出;默认面板外下方关闭按钮,不用 `title` / `cancel` / `confirm`。 + /// 屏幕居中弹出;默认面板下方圆形关闭图标;`closeBuilder: null` 隐藏关闭区。 center, } -/// 未传 [TPopupOptions.cancel] / [confirm] 时的占位,表示渲染默认「取消」「确定」文案。 +/// bottom 头部完全自定义构建器签名。 /// -/// 要**隐藏**某一侧须写 `cancel: null` 或 `confirm: null`(不是省略参数)。 -/// 两侧都为 `null` 且无 Builder 时,bottom 不显示操作栏(适合 Picker 等自带工具栏)。 -class TPopupActionDefault extends StatelessWidget { - const TPopupActionDefault({super.key}); - - @override - Widget build(BuildContext context) => const SizedBox.shrink(); -} - -/// 与 [TPopupActionDefault] 同一实例,作为 [TPopupOptions.cancel] / [confirm] 的默认值。 -const Widget kPopupActionDefault = TPopupActionDefault(); - -/// 传给自定义 [TPopupOptions.headerBuilder] 的标题栏数据(库内已组装好各槽 Widget)。 -class TPopupHeaderData { - const TPopupHeaderData({ - this.title, - this.cancel, - this.confirm, - this.onCancel, - this.onConfirm, - }); - - /// 中间标题(可为 null)。 - final Widget? title; - - /// 左侧区域 Widget(null 表示该侧已隐藏)。 - final Widget? cancel; - - /// 右侧区域 Widget(null 表示该侧已隐藏)。 - final Widget? confirm; - - /// 点击左侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnCancel] 决定)。 - final VoidCallback? onCancel; - - /// 点击右侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnConfirm] 决定)。 - final VoidCallback? onConfirm; -} - -/// bottom 完全自定义头部:`Widget Function(context, data)`,优先级高于默认操作栏。 +/// - [close]:调用即关闭浮层(触发 [TPopupTrigger.programmatic])。 typedef TPopupHeaderBuilder = Widget Function( BuildContext context, - TPopupHeaderData data, + VoidCallback close, ); -/// 默认 [headerBuilder] 占位:表示使用内置「取消 | 标题 | 确定」操作栏。 +/// bottom 槽位 / center 关闭区构建器签名。 /// -/// 勿直接调用。与 [headerBuilder: null](完全不显示头部)不同。 -Widget kPopupDefaultHeader(BuildContext context, TPopupHeaderData data) { - return const SizedBox.shrink(); -} - -/// 是否为默认操作栏占位(未自定义 [headerBuilder])。 -bool isPopupDefaultHeader(TPopupHeaderBuilder? builder) => - builder == kPopupDefaultHeader; - -/// center 面板**外下方**关闭区;须调用入参 [close] 才会关层(会走 [onCloseBtn] 等逻辑)。 -typedef TPopupCloseBuilder = Widget Function( +/// - [close]:调用即关闭浮层(触发 [TPopupTrigger.programmatic])。 +typedef TPopupSlotBuilder = Widget Function( BuildContext context, VoidCallback close, ); -/// 默认 [closeBuilder] 占位:使用内置圆圈关闭图标(面板外下方)。 +/// **内置三段式头部**占位常量(bottom 默认): +/// 实际渲染为 `cancelBuilder | titleBuilder | confirmBuilder` 三段。 /// -/// 勿直接调用。与 [closeBuilder: null](不显示关闭区)不同。 -Widget kPopupDefaultClose(BuildContext context, VoidCallback close) { - return const SizedBox.shrink(); -} +/// 直接调用返回空 Widget;库内通过 `identical` 判断是否走内置布局。 +/// 与 `headerBuilder: null`(无头部)语义不同。 +Widget kPopupDefaultHeader(BuildContext context, VoidCallback close) => + const SizedBox.shrink(); + +/// **内置「取消」按钮**占位常量(bottom 默认左槽)。 +/// +/// 实际渲染为本地化「取消」文本,点击调用 [close]。 +/// 与 `cancelBuilder: null`(隐藏左槽)语义不同。 +Widget kPopupDefaultCancel(BuildContext context, VoidCallback close) => + const SizedBox.shrink(); + +/// **内置「确定」按钮**占位常量(bottom 默认右槽)。 +/// +/// 实际渲染为本地化「确定」文本,点击调用 [close]。 +/// 与 `confirmBuilder: null`(隐藏右槽)语义不同。 +Widget kPopupDefaultConfirm(BuildContext context, VoidCallback close) => + const SizedBox.shrink(); + +/// **内置「关闭」图标**占位常量(center 默认关闭区)。 +/// +/// 实际渲染为圆形关闭图标,点击调用 [close]。 +/// 与 `closeBuilder: null`(隐藏关闭区)语义不同。 +Widget kPopupDefaultClose(BuildContext context, VoidCallback close) => + const SizedBox.shrink(); -/// 是否为默认关闭按钮占位。 -bool isPopupDefaultClose(TPopupCloseBuilder? builder) => - builder == kPopupDefaultClose; +/// 是否为「使用内置三段式头部」占位(bottom)。 +bool isPopupDefaultHeader(TPopupHeaderBuilder? builder) => + identical(builder, kPopupDefaultHeader); + +/// 是否为「使用内置取消按钮」占位。 +bool isPopupDefaultCancel(TPopupSlotBuilder? builder) => + identical(builder, kPopupDefaultCancel); + +/// 是否为「使用内置确定按钮」占位。 +bool isPopupDefaultConfirm(TPopupSlotBuilder? builder) => + identical(builder, kPopupDefaultConfirm); + +/// 是否为「使用内置圆形关闭图标」占位(center)。 +bool isPopupDefaultClose(TPopupSlotBuilder? builder) => + identical(builder, kPopupDefaultClose); /// 浮层被关闭时的触发来源,见 [TPopupOptions.onVisibleChange]。 +/// +/// 注意:从库内的内置按钮触发关闭统一上报 [programmatic](自定义 builder 走自己的 `close`), +/// 「点击蒙层」仍单独上报 [overlay]。 enum TPopupTrigger { /// 点击蒙层(且 [closeOnOverlayClick] 为 true)。 overlay, - /// 点击 center 下方关闭控件。 - closeBtn, - - /// 点击 bottom 操作栏「取消」。 - cancelBtn, - - /// 点击 bottom 操作栏「确定」。 - confirmBtn, - - /// [TPopupHandle.close]、系统返回键等。 + /// [TPopupHandle.close]、内置按钮、系统返回键等。 programmatic, } diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart index e4d3e4087..b8496e1d3 100644 --- a/tdesign-component/test/t_popup_coverage_test.dart +++ b/tdesign-component/test/t_popup_coverage_test.dart @@ -14,15 +14,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - title: '仅标题行', - cancel: null, - confirm: null, + titleBuilder: (_) => TText('仅标题行'), + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -35,16 +36,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - title: '左对齐标题', - titleAlignLeft: true, - cancel: null, - confirm: null, + titleBuilder: (_) => TText('左对齐标题'), + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -55,13 +56,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - confirm: null, + confirmBuilder: null, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -73,13 +75,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - cancel: null, + cancelBuilder: null, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -93,7 +96,8 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.center, width: 160, @@ -103,7 +107,7 @@ void main() { child: const Text('builder关闭'), ), child: const SizedBox(height: 80, width: 120)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -112,40 +116,41 @@ void main() { await tester.pumpAndSettle(); }); - testWidgets('center 默认关闭按钮触发 onCloseBtn', (tester) async { - var closeBtnCount = 0; + testWidgets('center 默认关闭按钮点击关闭浮层', (tester) async { late BuildContext hostContext; await openPopup( tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.center, width: 120, height: 120, - onCloseBtn: () => closeBtnCount++, child: const SizedBox(height: 80, width: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); + expect(find.byIcon(TIcons.close_circle), findsOneWidget); await tester.tap(find.byIcon(TIcons.close_circle)); await tester.pumpAndSettle(); - expect(closeBtnCount, 1); + expect(find.byIcon(TIcons.close_circle), findsNothing); }); testWidgets('bottom 无固定 height 贴底布局', (tester) async { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 80, width: 200)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -156,29 +161,31 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.top, child: const SizedBox(height: 60, width: 200)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); expect(find.byType(Positioned), findsWidgets); }); - testWidgets('center closeBtn false 无下方关闭', (tester) async { + testWidgets('center showClose=false 无下方关闭', (tester) async { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.center, width: 100, height: 100, closeBuilder: null, child: const SizedBox(height: 80, width: 80)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -191,14 +198,15 @@ void main() { await openPopup( tester, onPressed: () { - handle = TPopup( + handle = TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 80, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 40)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -208,15 +216,40 @@ void main() { expect(handle!.isShowing, isFalse); }); - testWidgets('TPopupActionDefault 可构建', (tester) async { - await tester.pumpWidget( - const MaterialApp( - home: Scaffold( - body: kPopupActionDefault, - ), - ), + testWidgets('handle.open 关闭后再次打开触发 onOpen / onOpened', (tester) async { + var openCount = 0; + var openedCount = 0; + late BuildContext hostContext; + TPopupHandle? handle; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + handle = TPopup.show( + hostContext, + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + cancelBuilder: null, + confirmBuilder: null, + onOpen: () => openCount++, + onOpened: () => openedCount++, + child: const SizedBox(height: 40)), + ); + }, ); - expect(find.byType(SizedBox), findsWidgets); + await tester.pumpAndSettle(); + expect(openCount, 1); + expect(openedCount, 1); + + handle!.close(); + await tester.pumpAndSettle(); + + handle!.open(hostContext); + await tester.pumpAndSettle(); + expect(openCount, 2); + expect(openedCount, 2); }); testWidgets('TToolbarPressable 按压与禁用', (tester) async { @@ -264,67 +297,36 @@ void main() { TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - titleWidget: const Text('w'), - cancel: null, - confirm: null, + titleBuilder: (_) => const Text('w'), + cancelBuilder: null, + confirmBuilder: null, ).hasBuiltInHeader, isTrue, ); }); - test('isActionDefault 识别占位 Widget', () { - expect(TPopupOptions.isActionDefault(kPopupActionDefault), isTrue); - expect(TPopupOptions.isActionDefault(const Text('x')), isFalse); - expect(TPopupOptions.isActionDefault(null), isFalse); - }); - - test('useCustomHeader 与 useTitleOnlyHeader 互斥于 useActionHeader', () { + test('useCustomHeader 与 useDefaultHeader 互斥', () { final custom = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, headerBuilder: (_, __) => const Text('h'), ); expect(custom.useCustomHeader, isTrue); - expect(custom.useActionHeader, isFalse); + expect(custom.useDefaultHeader, isFalse); final titleOnly = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - title: '仅标题', - cancel: null, - confirm: null, - ); - expect(titleOnly.useTitleOnlyHeader, isTrue); - expect(titleOnly.useActionHeader, isFalse); - }); - - test('isPopupDefaultHeader / isPopupDefaultClose 哨兵识别', () { - expect(isPopupDefaultHeader(kPopupDefaultHeader), isTrue); - expect(isPopupDefaultHeader(null), isFalse); - expect(isPopupDefaultClose(kPopupDefaultClose), isTrue); - expect(isPopupDefaultClose(null), isFalse); - }); - - testWidgets('kPopupDefaultHeader / kPopupDefaultClose 占位函数可调用', - (tester) async { - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (context) { - final header = kPopupDefaultHeader( - context, - const TPopupHeaderData(), - ); - final close = kPopupDefaultClose(context, () {}); - return Column(children: [header, close]); - }, - ), - ), + titleBuilder: (_) => TText('仅标题'), + cancelBuilder: null, + confirmBuilder: null, ); - expect(find.byType(SizedBox), findsWidgets); + expect(titleOnly.useDefaultHeader, isTrue); + expect(titleOnly.useCustomHeader, isFalse); + expect(titleOnly.hasBuiltInHeader, isTrue); }); - test('assertPlacementParams 覆盖 width 与 top 操作栏提示', () { + test('assertPlacementParams 覆盖各 placement 的字段提示', () { expect( () => TPopupOptions( child: const SizedBox(), @@ -337,7 +339,6 @@ void main() { () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, - onCancel: () {}, ).assertPlacementParams(), returnsNormally, ); @@ -354,7 +355,6 @@ void main() { () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, - onCancel: () {}, ).assertPlacementParams(), returnsNormally, ); @@ -367,26 +367,24 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - titleWidget: const Text('头Widget'), - cancelBuilder: (_) => const Text('builder左'), - confirmBuilder: (_) => const Text('builder右'), - headerBuilder: (_, data) => Column( - children: [ - if (data.title != null) data.title!, + headerBuilder: (ctx, close) => Column( + children: const [ + Text('头Widget'), Row( children: [ - if (data.cancel != null) data.cancel!, - if (data.confirm != null) data.confirm!, + Text('builder左'), + Text('builder右'), ], ), ], ), child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -399,21 +397,20 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - cancel: const Text('左槽Widget'), - confirm: const Text('右槽Widget'), - headerBuilder: (_, data) => Row( + headerBuilder: (ctx, close) => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (data.cancel != null) data.cancel!, - if (data.confirm != null) data.confirm!, + children: const [ + Text('左槽Widget'), + Text('右槽Widget'), ], ), child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -425,16 +422,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - cancel: const Text('自定义左'), - confirm: const Text('自定义右'), - onCancel: () {}, - onConfirm: () {}, + cancelBuilder: (_, __) => const Text('自定义左'), + confirmBuilder: (_, __) => const Text('自定义右'), child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -450,30 +446,32 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - title: '标题', + titleBuilder: (_) => TText('标题'), onVisibleChange: (visible, trigger) { if (!visible) { hideTriggers.add(trigger); } }, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); await tester.tap(find.text('取消')); await tester.pumpAndSettle(); - expect(hideTriggers.last, TPopupTrigger.cancelBtn); + expect(hideTriggers.last, TPopupTrigger.programmatic); await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, @@ -483,7 +481,7 @@ void main() { } }, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -494,7 +492,8 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.center, width: 120, @@ -505,13 +504,13 @@ void main() { } }, child: const SizedBox(height: 80, width: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); await tester.tap(find.byIcon(TIcons.close_circle)); await tester.pumpAndSettle(); - expect(hideTriggers.last, TPopupTrigger.closeBtn); + expect(hideTriggers.last, TPopupTrigger.programmatic); }); testWidgets('Popup 内嵌套 show 可再开一层且先关内层', (tester) async { @@ -521,31 +520,33 @@ void main() { await openPopup( tester, onPressed: () { - outerHandle = TPopup( + outerHandle = TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: Builder( builder: (ctx) { return ElevatedButton( onPressed: () { - innerHandle = TPopup( + innerHandle = TPopup.show( + ctx, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 120, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: const Text('内层'), ), - ).show(ctx); + ); }, child: const Text('开内层'), ); }, )), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -569,13 +570,14 @@ void main() { await openPopup( tester, onPressed: () { - handle = TPopup( + handle = TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 120, preventScrollThrough: false, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -587,7 +589,8 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.center, width: 100, @@ -595,7 +598,7 @@ void main() { radius: 4, backgroundColor: Colors.red, child: const SizedBox(height: 60, width: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -612,17 +615,18 @@ void main() { await openPopup( tester, onPressed: () { - handle = TPopup( + handle = TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, margin: const EdgeInsets.only(bottom: 16), showOverlay: false, closeOnOverlayClick: false, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); diff --git a/tdesign-component/test/t_popup_layout_test.dart b/tdesign-component/test/t_popup_layout_test.dart index fbe0abc96..ea573084b 100644 --- a/tdesign-component/test/t_popup_layout_test.dart +++ b/tdesign-component/test/t_popup_layout_test.dart @@ -93,7 +93,8 @@ void main() { } }); - testWidgets('center placement 使用 width 与 height', (tester) async { + testWidgets('center placement 仅 Center 包裹(尺寸由 PopupShell 控制)', + (tester) async { final layout = PopupLayout( placement: TPopupPlacement.center, screenSize: screen, @@ -105,17 +106,23 @@ void main() { MaterialApp( home: Scaffold( body: Stack( - children: [layout.wrapPositioned(child: const SizedBox())]), + children: [ + layout.wrapPositioned( + child: const SizedBox( + key: ValueKey('content'), + width: 200, + height: 150, + ), + ), + ], + ), ), ), ); expect(find.byType(Center), findsOneWidget); - final sizedBoxes = tester.widgetList( - find.descendant( - of: find.byType(Center), matching: find.byType(SizedBox)), - ); - final sizedBox = sizedBoxes.firstWhere((w) => w.width == 200); - expect(sizedBox.height, 150); + final box = tester.widget(find.byKey(const ValueKey('content'))); + expect(box.width, 200); + expect(box.height, 150); }); test('slideOffset 五向偏移', () { @@ -156,14 +163,14 @@ void main() { expect(center.slideOffset(0.5), Offset.zero); }); - testWidgets('centerLooseHeight 不限制高度', (tester) async { + testWidgets('center 仅 Positioned.fill + Center,由 PopupShell 控制尺寸', + (tester) async { final layout = PopupLayout( placement: TPopupPlacement.center, screenSize: screen, margin: EdgeInsets.zero, width: 100, height: 80, - centerLooseHeight: true, ); await tester.pumpWidget( MaterialApp( @@ -177,9 +184,7 @@ void main() { ), ); final center = tester.widget
(find.byType(Center)); - final sizedBox = center.child! as SizedBox; - expect(sizedBox.width, isNull); - expect(sizedBox.height, isNull); + expect(center.child, isA()); }); test('resolvedMargin center 为零', () { diff --git a/tdesign-component/test/t_popup_options_test.dart b/tdesign-component/test/t_popup_options_test.dart index 7d248fc65..121c045a6 100644 --- a/tdesign-component/test/t_popup_options_test.dart +++ b/tdesign-component/test/t_popup_options_test.dart @@ -4,46 +4,70 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; void main() { group('TPopupOptions', () { - test('默认 placement 为 bottom', () { + test('默认 placement 为 bottom,4 个 builder 默认 sentinel', () { final options = TPopupOptions(child: const SizedBox()).normalized(); expect(options.placement, TPopupPlacement.bottom); + expect(isPopupDefaultHeader(options.headerBuilder), isTrue); + expect(isPopupDefaultCancel(options.cancelBuilder), isTrue); + expect(isPopupDefaultConfirm(options.confirmBuilder), isTrue); + expect(options.titleBuilder, isNull); }); - test('bottom 默认渲染操作栏', () { + test('bottom 默认走内置三段式(useDefaultHeader)', () { final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, ).normalized(); - expect(options.useActionHeader, isTrue); + expect(options.useDefaultHeader, isTrue); + expect(options.useCustomHeader, isFalse); expect(options.showCancelSlot, isTrue); expect(options.showConfirmSlot, isTrue); expect(options.hasBuiltInHeader, isTrue); }); - test('cancel 与 confirm 均为 null 时不渲染操作栏', () { + test('cancelBuilder / confirmBuilder 均为 null 时槽位隐藏', () { final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, ).normalized(); - expect(options.useActionHeader, isFalse); expect(options.showCancelSlot, isFalse); expect(options.showConfirmSlot, isFalse); + expect(options.hasBuiltInHeader, isFalse); // titleBuilder 也为 null }); - test('normalized 忽略 bottom 的 closeBuilder', () { + test('headerBuilder null 不显示头部', () { + final options = TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + headerBuilder: null, + ).normalized(); + expect(options.useDefaultHeader, isFalse); + expect(options.useCustomHeader, isFalse); + expect(options.hasBuiltInHeader, isFalse); + }); + + test('headerBuilder 自定义 → useCustomHeader 为 true', () { + final options = TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + headerBuilder: (_, __) => const SizedBox(), + ).normalized(); + expect(options.useCustomHeader, isTrue); + expect(options.hasBuiltInHeader, isTrue); + }); + + test('normalized 忽略 bottom 的 closeBuilder(非 sentinel 也置 null)', () { final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, closeBuilder: (_, __) => const Text('x'), - onCancel: () {}, ).normalized(); expect(options.closeBuilder, isNull); - expect(options.useActionHeader, isTrue); }); - test('center 默认 closeBuilder', () { + test('center 默认 closeBuilder 为 sentinel(内置图标)', () { final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, @@ -51,7 +75,7 @@ void main() { expect(isPopupDefaultClose(options.closeBuilder), isTrue); }); - test('center closeBuilder null 无关闭区', () { + test('center closeBuilder=null 不显示关闭区', () { final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, @@ -60,58 +84,60 @@ void main() { expect(options.closeBuilder, isNull); }); - test('top 剥离 title 与 headerBuilder', () { + test('top 剥离 header 与三槽,重置为 null', () { final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, - title: '标题', + titleBuilder: (_) => const Text('x'), headerBuilder: (_, __) => const Text('h'), - onCancel: () {}, ).normalized(); - expect(options.title, isNull); expect(options.headerBuilder, isNull); - expect(options.useActionHeader, isFalse); + expect(options.titleBuilder, isNull); + expect(options.cancelBuilder, isNull); + expect(options.confirmBuilder, isNull); expect(options.hasBuiltInHeader, isFalse); }); - test('headerBuilder null 表示无头部', () { - final options = TPopupOptions( + test('left/right 剥离 closeBuilder', () { + final left = TPopupOptions( child: const SizedBox(), - placement: TPopupPlacement.bottom, - title: '标题', - headerBuilder: null, + placement: TPopupPlacement.left, + closeBuilder: (_, __) => const Text('x'), ).normalized(); - expect(options.hasNoHeader, isTrue); - expect(options.hasBuiltInHeader, isFalse); + expect(left.closeBuilder, isNull); }); - test('hasBuiltInHeader 识别 title 与 headerBuilder', () { + test('hasBuiltInHeader 内置三段中任一槽非 null 即 true', () { + // titleBuilder 单独存在 expect( TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - title: '标题', - cancel: null, - confirm: null, + titleBuilder: (_) => const Text('x'), + cancelBuilder: null, + confirmBuilder: null, ).normalized().hasBuiltInHeader, isTrue, ); + // 仅 cancel 默认(其它 null) expect( TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - title: '标题', - ).normalized().useActionHeader, + confirmBuilder: null, + ).normalized().hasBuiltInHeader, isTrue, ); + // 完全无头部 expect( TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - headerBuilder: (_, __) => const Text('h'), + headerBuilder: null, ).normalized().hasBuiltInHeader, - isTrue, + isFalse, ); + // 非 bottom 永远 false expect( TPopupOptions( child: const SizedBox(), @@ -121,45 +147,52 @@ void main() { ); }); - test('left/right 剥离 closeBuilder 与 onCloseBtn', () { - final left = TPopupOptions( - child: const SizedBox(), - placement: TPopupPlacement.left, - closeBuilder: (_, __) => const Text('x'), - onCloseBtn: () {}, - ).normalized(); - expect(left.closeBuilder, isNull); - expect(left.onCloseBtn, isNull); - }); - - test('useCustomHeader 为 true 时 hasBuiltInHeader', () { - final options = TPopupOptions( - child: const SizedBox(), - placement: TPopupPlacement.bottom, - headerBuilder: (_, __) => const SizedBox(), - ).normalized(); - expect(options.useCustomHeader, isTrue); - expect(options.hasBuiltInHeader, isTrue); - }); - - test('assertPlacementParams 非 bottom 带 cancel 槽位提示', () { - final options = TPopupOptions( - child: const SizedBox(), - placement: TPopupPlacement.center, - cancel: kPopupActionDefault, - confirm: kPopupActionDefault, + test('assertPlacementParams 在 debug 模式不抛错', () { + expect( + () => TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.left, + height: 100, + width: 200, + margin: const EdgeInsets.only(right: 10), + ).assertPlacementParams(), + returnsNormally, + ); + expect( + () => TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.center, + titleBuilder: (_) => const Text('x'), + ).assertPlacementParams(), + returnsNormally, ); - expect(() => options.assertPlacementParams(), returnsNormally); }); - test('assertPlacementParams 在 debug 模式不抛错', () { - final options = TPopupOptions( - child: const SizedBox(), - placement: TPopupPlacement.left, - height: 100, - width: 200, - ).normalized(); - expect(() => options.assertPlacementParams(), returnsNormally); + test('assertPlacementParams 各 placement 的 margin 警告项', () { + expect( + () => TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.top, + margin: const EdgeInsets.only(bottom: 10), + ).assertPlacementParams(), + returnsNormally, + ); + expect( + () => TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.right, + margin: const EdgeInsets.only(left: 10), + ).assertPlacementParams(), + returnsNormally, + ); + expect( + () => TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.center, + margin: const EdgeInsets.all(10), + ).assertPlacementParams(), + returnsNormally, + ); }); }); } diff --git a/tdesign-component/test/t_popup_route_test.dart b/tdesign-component/test/t_popup_route_test.dart index 8e9f69191..68dfbbc13 100644 --- a/tdesign-component/test/t_popup_route_test.dart +++ b/tdesign-component/test/t_popup_route_test.dart @@ -40,13 +40,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 120, preventScrollThrough: true, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -82,16 +83,17 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 120, showOverlay: false, preventScrollThrough: true, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -110,13 +112,14 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, onClose: () => closeCount++, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart index 2f7b2f769..74b423995 100644 --- a/tdesign-component/test/t_popup_test.dart +++ b/tdesign-component/test/t_popup_test.dart @@ -14,10 +14,8 @@ void main() { PopupTestResourceDelegate.en(), ]) { testWidgets( - '${resource.locale.languageCode} 底部操作栏默认 cancel / confirm', + '${resource.locale.languageCode} 底部默认 cancel / confirm 按钮点击关闭浮层', (tester) async { - var cancelCount = 0; - var confirmCount = 0; late BuildContext hostContext; await openPopup( @@ -25,14 +23,13 @@ void main() { resource: resource, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - onCancel: () => cancelCount++, - onConfirm: () => confirmCount++, child: const SizedBox(height: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -41,25 +38,25 @@ void main() { await tester.tap(find.text(resource.cancelText)); await tester.pumpAndSettle(); - expect(cancelCount, 1); + expect(find.text(resource.cancelText), findsNothing); await openPopup( tester, resource: resource, onPressed: () { - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - onConfirm: () => confirmCount++, child: const SizedBox(height: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); await tester.tap(find.text(resource.confirmText)); await tester.pumpAndSettle(); - expect(confirmCount, 1); + expect(find.text(resource.confirmText), findsNothing); }, ); } @@ -77,13 +74,13 @@ void main() { resource: resource, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - onCancel: () {}, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -106,14 +103,15 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 120, onOpen: () => openCount++, onOpened: () => openedCount++, child: const SizedBox(height: 80)), - ).show(hostContext); + ); }, ); await tester.pump(); @@ -136,7 +134,8 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 120, @@ -144,7 +143,7 @@ void main() { onClosed: () => closedCount++, onVisibleChange: (v, _) => visibleChanges.add(v), child: const SizedBox(height: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -165,13 +164,14 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, onClose: () => closeCount++, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -198,7 +198,8 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: placement, height: placement == TPopupPlacement.left || @@ -210,7 +211,7 @@ void main() { ? null : 200, child: const SizedBox(height: 60, width: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -222,102 +223,88 @@ void main() { }); group('TPopup Header', () { - testWidgets('底部操作栏:取消与确认', (tester) async { - var cancelCount = 0; - var confirmCount = 0; + testWidgets('底部默认头部:title / cancel / confirm 按钮渲染', (tester) async { late BuildContext hostContext; await openPopup( tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - title: '标题', - onCancel: () => cancelCount++, - onConfirm: () => confirmCount++, + titleBuilder: (_) => TText('标题'), child: const SizedBox(height: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); expect(find.text('标题'), findsOneWidget); + expect(find.text('取消'), findsOneWidget); + expect(find.text('确定'), findsOneWidget); + + // 点取消默认 sentinel 自带 close → 浮层关闭 await tester.tap(find.text('取消')); await tester.pumpAndSettle(); - expect(cancelCount, 1); + expect(find.text('标题'), findsNothing); + // 重新打开点确定 → 同样关闭 await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - onConfirm: () => confirmCount++, child: const SizedBox(height: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); await tester.tap(find.text('确定')); await tester.pumpAndSettle(); - expect(confirmCount, 1); - }); - - testWidgets('autoCloseOnCancel 为 false 时不自动关闭', (tester) async { - await openPopup( - tester, - onPressed: () { - TPopup( - options: TPopupOptions( - placement: TPopupPlacement.bottom, - height: 200, - autoCloseOnCancel: false, - onCancel: () {}, - child: const SizedBox(height: 80)), - ).show(tester.element(find.text('open'))); - }, - ); - await tester.pumpAndSettle(); - await tester.tap(find.text('取消')); - await tester.pump(); - expect(find.text('取消'), findsOneWidget); + expect(find.text('确定'), findsNothing); }); - testWidgets('autoCloseOnConfirm 为 false 时不自动关闭', (tester) async { + testWidgets('cancelBuilder 自定义可选择不调 close → 浮层保持展示', (tester) async { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - autoCloseOnConfirm: false, - onConfirm: () {}, + cancelBuilder: (_, __) => GestureDetector( + onTap: () {}, // 不调 close + child: const Text('自定义取消'), + ), child: const SizedBox(height: 80)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); - await tester.tap(find.text('确定')); + await tester.tap(find.text('自定义取消')); await tester.pump(); - expect(find.text('确定'), findsOneWidget); + expect(find.text('自定义取消'), findsOneWidget); }); - testWidgets('bottom headerBuilder null 无头部', (tester) async { + testWidgets('bottom showHeader=false 不渲染头部', (tester) async { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 180, - title: '不应出现', + titleBuilder: (_) => TText('不应出现'), headerBuilder: null, child: const SizedBox(height: 80)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -329,15 +316,16 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 180, - title: '仅标题', - cancel: null, - confirm: null, + titleBuilder: (_) => TText('仅标题'), + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 80)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -351,14 +339,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - title: '传入标题', - headerBuilder: (_, data) => Text('自定义:${data.title}'), + headerBuilder: (_, __) => const Text('自定义:传入标题'), child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -372,13 +360,14 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.center, width: 120, height: 120, child: const SizedBox(height: 80, width: 80)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -391,31 +380,33 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.center, width: 240, height: 240, child: const SizedBox.expand()), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); expect(find.byIcon(TIcons.close_circle), findsOneWidget); }); - testWidgets('center 默认显示下方关闭 closeBuilder null 隐藏', (tester) async { + testWidgets('center showClose=false 不显示关闭区', (tester) async { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.center, width: 120, height: 120, closeBuilder: null, child: const SizedBox(height: 80, width: 80)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -426,16 +417,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - cancelBuilder: (_) => const Text('自定义取消'), - confirmBuilder: (_) => const Text('自定义确认'), - onCancel: () {}, - onConfirm: () {}, + cancelBuilder: (_, __) => const Text('自定义取消'), + confirmBuilder: (_, __) => const Text('自定义确认'), child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -453,13 +443,14 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, onClosed: () => overlayClose++, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -476,15 +467,15 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, closeOnOverlayClick: false, - onCancel: () {}, onOverlayClick: () {}, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -500,14 +491,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, showOverlay: false, preventScrollThrough: true, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -521,14 +513,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 80, overlayColor: Colors.red, overlayOpacity: 0.5, child: const SizedBox(height: 40)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -539,56 +532,19 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, margin: const EdgeInsets.only(top: 80), child: const SizedBox(height: 200)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); }); }); - group('TPopup 声明式', () { - testWidgets('initialVisible 自动打开', (tester) async { - await tester.pumpWidget( - wrapPopupTest( - TPopup( - initialVisible: true, - options: TPopupOptions( - placement: TPopupPlacement.bottom, - height: 100, - child: SizedBox(height: 60), - ), - ), - ), - ); - await tester.pump(); - await tester.pumpAndSettle(); - expect(find.byType(SizedBox), findsWidgets); - }); - - testWidgets('dispose 时关闭弹层', (tester) async { - await tester.pumpWidget( - wrapPopupTest( - TPopup( - initialVisible: true, - options: TPopupOptions( - placement: TPopupPlacement.bottom, - height: 100, - child: SizedBox(height: 60), - ), - ), - ), - ); - await tester.pumpAndSettle(); - await tester.pumpWidget(const SizedBox()); - await tester.pumpAndSettle(); - }); - }); - group('TPopupHandle / Tracker', () { testWidgets('外层重复 show 第二次无效(返回同一 handle)', (tester) async { TPopupHandle? first; @@ -596,18 +552,20 @@ void main() { tester, onPressed: () { final ctx = tester.element(find.text('open')); - first = TPopup( + first = TPopup.show( + ctx, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 80, child: const SizedBox(height: 40)), - ).show(ctx); - final second = TPopup( + ); + final second = TPopup.show( + ctx, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 80, child: const SizedBox(height: 40)), - ).show(ctx); + ); expect(second.isShowing, isTrue); expect(identical(first, second), isTrue); }, @@ -625,13 +583,14 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, onClosed: () => closedCount++, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -639,6 +598,66 @@ void main() { await tester.pumpAndSettle(); expect(closedCount, 1); }); + + testWidgets('handle.close 后 handle.open 可再次展示', (tester) async { + late BuildContext hostContext; + TPopupHandle? handle; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + handle = TPopup.show( + hostContext, + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 100, + cancelBuilder: null, + confirmBuilder: null, + child: const SizedBox(height: 60, child: Text('panel'))), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('panel'), findsOneWidget); + + handle!.close(); + await tester.pumpAndSettle(); + expect(handle!.isShowing, isFalse); + expect(find.text('panel'), findsNothing); + + handle!.open(hostContext); + await tester.pumpAndSettle(); + expect(handle!.isShowing, isTrue); + expect(find.text('panel'), findsOneWidget); + }); + + testWidgets('handle.open 在已展示时无副作用', (tester) async { + late BuildContext hostContext; + TPopupHandle? handle; + + await openPopup( + tester, + onPressed: () { + hostContext = tester.element(find.text('open')); + handle = TPopup.show( + hostContext, + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 80, + cancelBuilder: null, + confirmBuilder: null, + child: const SizedBox(height: 40)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(handle!.isShowing, isTrue); + + handle!.open(hostContext); + await tester.pumpAndSettle(); + expect(handle!.isShowing, isTrue); + }); }); group('TPopup 扩展场景', () { @@ -646,13 +665,14 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.top, height: 120, - title: '顶部标题', + titleBuilder: (_) => TText('顶部标题'), child: const Text('内容')), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -671,12 +691,13 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - handle = TPopup( + handle = TPopup.show( + hostContext, options: TPopupOptions( placement: placement, width: 240, child: const SizedBox(height: 40)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -686,19 +707,22 @@ void main() { } }); - testWidgets('titleWidget 与 cancelBtn / confirmBtn 文案', (tester) async { + testWidgets('titleBuilder + 自定义 cancel/confirm Builder 文案', (tester) async { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 180, - titleWidget: const Text('Widget标题'), - cancelBtn: '左', - confirmBtn: '右', + titleBuilder: (_) => const Text('Widget标题'), + cancelBuilder: (_, close) => + GestureDetector(onTap: close, child: const Text('左')), + confirmBuilder: (_, close) => + GestureDetector(onTap: close, child: const Text('右')), child: const SizedBox(height: 40)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -713,7 +737,8 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.center, width: 140, @@ -722,9 +747,8 @@ void main() { onTap: close, child: const Text('关'), ), - onCloseBtn: () {}, child: const SizedBox(height: 60, width: 120)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -739,13 +763,14 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, onOverlayClick: () => overlayClick++, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -759,12 +784,13 @@ void main() { await openPopup( tester, onPressed: () { - handle = TPopup( + handle = TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 80, child: const SizedBox(height: 40)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -783,7 +809,8 @@ void main() { await openPopup( tester, onPressed: () { - handle = TPopup( + handle = TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, @@ -793,7 +820,7 @@ void main() { } }, child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); @@ -810,7 +837,8 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - TPopup( + TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 120, @@ -820,13 +848,13 @@ void main() { } }, child: const SizedBox(height: 60)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); await tester.tap(find.text('确定')); await tester.pumpAndSettle(); - expect(hideTrigger, TPopupTrigger.confirmBtn); + expect(hideTrigger, TPopupTrigger.programmatic); }); testWidgets('destroyOnClose 路由关闭后可再次 show', (tester) async { @@ -837,15 +865,16 @@ void main() { tester, onPressed: () { hostContext = tester.element(find.text('open')); - first = TPopup( + first = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 80, destroyOnClose: true, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 40)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -857,15 +886,16 @@ void main() { await openPopup( tester, onPressed: () { - second = TPopup( + second = TPopup.show( + hostContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 80, destroyOnClose: true, - cancel: null, - confirm: null, + cancelBuilder: null, + confirmBuilder: null, child: const SizedBox(height: 40)), - ).show(hostContext); + ); }, ); await tester.pumpAndSettle(); @@ -880,16 +910,15 @@ void main() { await openPopup( tester, onPressed: () { - TPopup( + TPopup.show( + tester.element(find.text('open')), options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - cancel: const Text('自定义取消'), - confirm: const Text('自定义确认'), - onCancel: () {}, - onConfirm: () {}, + cancelBuilder: (_, __) => const Text('自定义取消'), + confirmBuilder: (_, __) => const Text('自定义确认'), child: const SizedBox(height: 60)), - ).show(tester.element(find.text('open'))); + ); }, ); await tester.pumpAndSettle(); diff --git a/tdesign-site/src/indexes/README.md b/tdesign-site/src/indexes/README.md index be8e1092b..95475bf3f 100644 --- a/tdesign-site/src/indexes/README.md +++ b/tdesign-site/src/indexes/README.md @@ -36,7 +36,8 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -52,7 +53,7 @@ Widget _buildSimple(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } @@ -74,7 +75,8 @@ Widget _buildSimple(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -90,7 +92,7 @@ Widget _buildSimple(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } @@ -115,7 +117,8 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -132,7 +135,7 @@ Widget _buildOther(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } @@ -154,7 +157,8 @@ Widget _buildOther(BuildContext context) { theme: TButtonTheme.primary, type: TButtonType.outline, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, @@ -171,7 +175,7 @@ Widget _buildOther(BuildContext context) { ); }, )), - ).show(context); + ); }, ); } diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md index 36e003253..aee503ea3 100644 --- a/tdesign-site/src/popup/README.md +++ b/tdesign-site/src/popup/README.md @@ -33,7 +33,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.top, height: 240, @@ -43,7 +44,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; color: TTheme.of(context).bgColorContainer, height: 240, )), - ).show(context); + ); }, ); } @@ -64,14 +65,15 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.left, width: 280, child: Container( color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -92,7 +94,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeBuilder: null, @@ -105,7 +108,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; width: 240, height: 240, )), - ).show(context); + ); }, ); } @@ -126,7 +129,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 240, @@ -135,7 +139,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; color: TTheme.of(context).bgColorContainer, height: 240, )), - ).show(context); + ); }, ); } @@ -156,14 +160,15 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.right, width: 280, child: Container( color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -185,14 +190,15 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, title: '标题文字', onConfirm: () => TToast.showText('确定', context: context), child: Container(height: 200)), - ).show(context); + ); }, ); } @@ -213,7 +219,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, @@ -242,7 +249,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; fontWeight: FontWeight.w600, ), child: Container(height: 200)), - ).show(context); + ); }, ); } @@ -263,7 +270,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeOnOverlayClick: false, @@ -278,7 +286,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onPressed: close, ), child: const SizedBox(width: 240, height: 240)), - ).show(context); + ); }, ); } @@ -299,7 +307,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.center, closeOnOverlayClick: true, @@ -318,7 +327,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -340,7 +349,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; size: TButtonSize.large, onTap: () { TPopupHandle? outerHandle; - outerHandle = TPopup( + outerHandle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 360, @@ -363,7 +373,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; theme: TButtonTheme.primary, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + innerContext, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, @@ -374,7 +385,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; .bgColorSecondaryContainer, ), ), - ).show(innerContext); + ); }, ), const SizedBox(height: 12), @@ -390,7 +401,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ); }, )), - ).show(context); + ); }, ); } @@ -412,7 +423,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 320, @@ -422,7 +434,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; height: 240, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -443,7 +455,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 280, @@ -454,7 +467,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -475,7 +488,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 260, @@ -484,7 +498,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -505,7 +519,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopup( + TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, height: 240, @@ -514,7 +529,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; height: 200, color: TTheme.of(context).bgColorContainer, )), - ).show(context); + ); }, ); } @@ -526,48 +541,33 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ## API ### TPopup #### 简介 -弹出层:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 +弹出层命名空间:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。仅提供静态入口 [show];返回的 [TPopupHandle] 控制本次浮层的显隐。 ## 怎么用 - **命令式(推荐)** — 先组配置,再 `show`,用返回的 [TPopupHandle] 关闭: - ```dart - final handle = TPopup( + final handle = TPopup.show( + context, options: TPopupOptions( placement: TPopupPlacement.bottom, title: '标题', child: MyPanel(), ), - ).show(context); + ); - // 关闭这一层(须保留 handle,不要用 context 猜栈顶) + // 关闭后再开(同一 handle) handle.close(); - ``` - - **声明式** — 包住子树,`initialVisible: true` 时首帧自动 [show];[build] 只渲染 [options.child]: - - ```dart - TPopup( - options: TPopupOptions(child: body), - initialVisible: true, - ) + handle.open(context); ``` 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 [TPopupOptions.normalized] 中裁掉)。 -#### 默认构造方法 -| 参数 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| initialVisible | bool | false | 为 true 时,挂载后首帧自动调用 [show](仅声明式)。 | -| key | | - | | -| navigatorContext | BuildContext? | - | 指定使用哪个 [Navigator];默认 [show] 传入的 `context` 所在 Navigator。 | -| options | TPopupOptions | - | 浮层内容与行为配置,见 [TPopupOptions]。 | -| useRootNavigator | bool | false | 为 true 时使用根 [Navigator](嵌套导航场景)。 | +#### 静态方法 -``` -``` +| 名称 | 返回类型 | 参数 | 说明 | +| --- | --- | --- | --- | +| show | TPopupHandle | `BuildContext context`, `{required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator = false}` | 命令式打开浮层并压入独立路由,返回 [TPopupHandle] 控制显隐。 | ### TPopupOptions #### 简介 From 205a450d83f10cd8a86dc6baf1879a3211f9bf0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Fri, 22 May 2026 11:41:45 +0800 Subject: [PATCH 08/27] refactor(popup): improve parameter validation and update title handling in TPopup --- tdesign-component/lib/src/components/popup/t_popup.dart | 2 +- .../lib/src/components/popup/t_popup_handle.dart | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart index 7719daba5..1f8aed576 100644 --- a/tdesign-component/lib/src/components/popup/t_popup.dart +++ b/tdesign-component/lib/src/components/popup/t_popup.dart @@ -19,7 +19,7 @@ part 't_popup_tracker.dart'; /// context, /// options: TPopupOptions( /// placement: TPopupPlacement.bottom, -/// title: '标题', +/// titleBuilder: (_) => const Text('标题'), /// child: MyPanel(), /// ), /// ); diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart index 5c021ebcf..3b85cca62 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart @@ -40,8 +40,10 @@ class TPopupHandle { if (isShowing) { return; } + // 先用「原始」配置做 debug 期参数校验(保留 sentinel 与用户传值差异), + // 再 normalize 给路由使用(normalize 会按 placement 强制清空无效字段)。 + options.assertPlacementParams(); final normalized = options.normalized(); - normalized.assertPlacementParams(); final navigator = _navigatorOf(context); _isClosed = false; From 56fab62566200c2729e9d5c6e92d5d66af16f76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Fri, 22 May 2026 16:42:45 +0800 Subject: [PATCH 09/27] refactor(popup): streamline TPopupOptions usage by replacing placement parameters with dedicated methods --- .../code/indexes._buildCustomIndexes.txt | 3 +- .../assets/code/indexes._buildOther.txt | 3 +- .../assets/code/indexes._buildSimple.txt | 3 +- .../assets/code/popup._buildApiDuration.txt | 3 +- .../assets/code/popup._buildApiMarginTop.txt | 5 +- .../code/popup._buildApiOnOverlayClick.txt | 3 +- .../code/popup._buildApiShowOverlayFalse.txt | 5 +- .../assets/code/popup._buildNestedPopup.txt | 8 +- .../assets/code/popup._buildPopFromBottom.txt | 3 +- ...p._buildPopFromBottomWithCloseAndTitle.txt | 9 +- ...uildPopFromBottomWithOperationAndTitle.txt | 6 +- .../assets/code/popup._buildPopFromCenter.txt | 3 +- .../popup._buildPopFromCenterWithClose.txt | 3 +- ...opup._buildPopFromCenterWithUnderClose.txt | 3 +- .../assets/code/popup._buildPopFromLeft.txt | 3 +- .../assets/code/popup._buildPopFromRight.txt | 3 +- .../assets/code/popup._buildPopFromTop.txt | 3 +- .../lib/component_test/popup_test.dart | 3 +- .../example/lib/page/t_indexes_page.dart | 9 +- .../example/lib/page/t_picker_page.dart | 3 +- .../example/lib/page/t_popup_page.dart | 72 +-- .../action_sheet/t_action_sheet.dart | 3 +- .../components/calendar/t_calendar_popup.dart | 3 +- .../components/popup/_popup_center_close.dart | 34 +- .../src/components/popup/_popup_header.dart | 40 +- .../src/components/popup/_popup_layout.dart | 9 +- .../src/components/popup/_popup_route.dart | 19 +- .../src/components/popup/_popup_shell.dart | 10 +- ...popup_tracker.dart => _popup_tracker.dart} | 2 +- .../lib/src/components/popup/t_popup.dart | 71 ++- .../src/components/popup/t_popup_handle.dart | 83 ++- .../src/components/popup/t_popup_options.dart | 581 ++++++++++++++---- .../src/components/popup/t_popup_types.dart | 104 ++-- tdesign-component/lib/tdesign_flutter.dart | 11 +- .../test/t_popup_coverage_test.dart | 543 +++++++++++++++- .../test/t_popup_layout_test.dart | 2 +- .../test/t_popup_options_test.dart | 48 +- .../test/t_popup_route_test.dart | 28 +- tdesign-component/test/t_popup_test.dart | 18 +- 39 files changed, 1298 insertions(+), 467 deletions(-) rename tdesign-component/lib/src/components/popup/{t_popup_tracker.dart => _popup_tracker.dart} (95%) diff --git a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt index 78eb937c7..332d418f2 100644 --- a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt +++ b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt @@ -11,8 +11,7 @@ Widget _buildCustomIndexes(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( diff --git a/tdesign-component/example/assets/code/indexes._buildOther.txt b/tdesign-component/example/assets/code/indexes._buildOther.txt index 907e9d57b..d89966bfb 100644 --- a/tdesign-component/example/assets/code/indexes._buildOther.txt +++ b/tdesign-component/example/assets/code/indexes._buildOther.txt @@ -11,8 +11,7 @@ Widget _buildOther(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( diff --git a/tdesign-component/example/assets/code/indexes._buildSimple.txt b/tdesign-component/example/assets/code/indexes._buildSimple.txt index 526e86fe5..40aa90866 100644 --- a/tdesign-component/example/assets/code/indexes._buildSimple.txt +++ b/tdesign-component/example/assets/code/indexes._buildSimple.txt @@ -11,8 +11,7 @@ Widget _buildSimple(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( diff --git a/tdesign-component/example/assets/code/popup._buildApiDuration.txt b/tdesign-component/example/assets/code/popup._buildApiDuration.txt index 823bf860a..3df04cd44 100644 --- a/tdesign-component/example/assets/code/popup._buildApiDuration.txt +++ b/tdesign-component/example/assets/code/popup._buildApiDuration.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 240, duration: const Duration(milliseconds: 600), child: Container( diff --git a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt index 359e5379e..43846eacc 100644 --- a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt +++ b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt @@ -9,11 +9,10 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 320, margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - title: '日历式留白', + titleBuilder: (_) => TText('日历式留白'), child: Container( height: 240, color: TTheme.of(context).bgColorContainer, diff --git a/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt index ad92d6457..d24828ea7 100644 --- a/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt +++ b/tdesign-component/example/assets/code/popup._buildApiOnOverlayClick.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 260, onOverlayClick: () => TToast.showText('点击蒙层', context: context), child: Container( diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt index f78db1049..4fe8fbf66 100644 --- a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt +++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt @@ -9,12 +9,11 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, showOverlay: false, // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) - title: '无蒙层', + titleBuilder: (_) => const TText('无蒙层'), child: Container( height: 200, color: TTheme.of(context).bgColorContainer, diff --git a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt index abc06d8f8..61479a115 100644 --- a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt +++ b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt @@ -10,8 +10,7 @@ TPopupHandle? outerHandle; outerHandle = TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 360, headerBuilder: null, child: Builder( @@ -34,10 +33,9 @@ onTap: () { TPopup.show( innerContext, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, - title: '内层标题', + titleBuilder: (_) => const TText('内层标题'), child: Container( height: 160, color: TTheme.of(innerContext) diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt index 93cf8defd..d10c8b269 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottom.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 240, headerBuilder: null, child: Container( diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt index fa292abb9..f5c92bfb3 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt @@ -9,15 +9,14 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, - cancel: TText( + cancelBuilder: (_, __) => TText( '关闭', textColor: TTheme.of(context).textColorSecondary, font: TTheme.of(context).fontBodyLarge, ), - titleWidget: Row( + titleBuilder: (_) => Row( mainAxisSize: MainAxisSize.min, children: [ Icon(TIcons.info_circle, @@ -30,7 +29,7 @@ ), ], ), - confirm: TText( + confirmBuilder: (_, __) => TText( '完成', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontTitleMedium, diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt index 406c6e993..d9a005c95 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt @@ -9,11 +9,9 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, - title: '标题文字', - onConfirm: () => TToast.showText('确定', context: context), + titleBuilder: (_) => TText('标题文字'), child: Container(height: 200)), ); }, diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt index 7e70ac0c5..1d4511bca 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenter.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeBuilder: null, child: Container( decoration: BoxDecoration( diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt index b5f1c2dcc..376499c95 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithClose.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeOnOverlayClick: false, width: 240, height: 240, diff --git a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt index aa75133dc..b7dec4b2f 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromCenterWithUnderClose.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeOnOverlayClick: true, width: 240, height: 200, diff --git a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt index 05204a2ce..91184529b 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromLeft.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.left, + options: TPopupOptions.left( width: 280, child: Container( color: TTheme.of(context).bgColorContainer, diff --git a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt index 6f7efe555..3d06a9c16 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromRight.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromRight.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, child: Container( color: TTheme.of(context).bgColorContainer, diff --git a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt index d1c6b9e5e..112a0abf4 100644 --- a/tdesign-component/example/assets/code/popup._buildPopFromTop.txt +++ b/tdesign-component/example/assets/code/popup._buildPopFromTop.txt @@ -9,8 +9,7 @@ onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.top, + options: TPopupOptions.top( height: 240, onOpen: () => print('open'), onOpened: () => print('opened'), diff --git a/tdesign-component/example/lib/component_test/popup_test.dart b/tdesign-component/example/lib/component_test/popup_test.dart index 1a4a1cbc7..475f820d0 100644 --- a/tdesign-component/example/lib/component_test/popup_test.dart +++ b/tdesign-component/example/lib/component_test/popup_test.dart @@ -27,8 +27,7 @@ class _TestPageState extends State { void _showProblemDialog() { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( titleBuilder: (_) => TText('title'), radius: 20, backgroundColor: const Color(0xFFFAFFFC), diff --git a/tdesign-component/example/lib/page/t_indexes_page.dart b/tdesign-component/example/lib/page/t_indexes_page.dart index 3409e5999..bb9b1e98b 100644 --- a/tdesign-component/example/lib/page/t_indexes_page.dart +++ b/tdesign-component/example/lib/page/t_indexes_page.dart @@ -159,8 +159,7 @@ Widget _buildSimple(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( @@ -192,8 +191,7 @@ Widget _buildOther(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( @@ -226,8 +224,7 @@ Widget _buildCustomIndexes(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( diff --git a/tdesign-component/example/lib/page/t_picker_page.dart b/tdesign-component/example/lib/page/t_picker_page.dart index 4c867ea7c..1b548b6f5 100644 --- a/tdesign-component/example/lib/page/t_picker_page.dart +++ b/tdesign-component/example/lib/page/t_picker_page.dart @@ -192,8 +192,7 @@ class _TPickerPageState extends State { void _showPickerPopup(BuildContext context, {required Widget picker}) { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( cancelBuilder: null, confirmBuilder: null, child: Material( diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart index f12344329..11da7ae47 100644 --- a/tdesign-component/example/lib/page/t_popup_page.dart +++ b/tdesign-component/example/lib/page/t_popup_page.dart @@ -100,8 +100,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, titleBuilder: (_) => TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), cancelBuilder: (_, __) => TText( @@ -130,11 +129,9 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopupHandle? handle; - handle = TPopup.show( + TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, headerBuilder: _bottomTitleCloseHeader( title: '标题文字标题文字标题文字标题文字标题文字标题文字标题文字', @@ -159,11 +156,9 @@ class TPopupPage extends StatelessWidget { type: TButtonType.outline, size: TButtonSize.large, onTap: () { - TPopupHandle? handle; - handle = TPopup.show( + TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, radius: 6, headerBuilder: _bottomTitleCloseHeader( @@ -183,8 +178,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, radius: 6, titleBuilder: (_) => TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), @@ -212,8 +206,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( width: 240, height: 240, radius: 6, @@ -239,8 +232,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( width: 240, height: 240, radius: 6, @@ -266,8 +258,7 @@ class TPopupPage extends StatelessWidget { navBarkey.currentContext!.findRenderObject() as RenderBox; TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox.size.height), child: Container( @@ -295,8 +286,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.top, + options: TPopupOptions.top( height: 240, onOpen: () => print('open'), onOpened: () => print('opened'), @@ -320,8 +310,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.left, + options: TPopupOptions.left( width: 280, child: Container( color: TTheme.of(context).bgColorContainer, @@ -342,8 +331,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeBuilder: null, child: Container( decoration: BoxDecoration( @@ -370,8 +358,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 240, headerBuilder: null, child: Container( @@ -394,8 +381,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, child: Container( color: TTheme.of(context).bgColorContainer, @@ -420,8 +406,7 @@ class TPopupPage extends StatelessWidget { TPopupHandle? outerHandle; outerHandle = TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 360, headerBuilder: null, child: Builder( @@ -444,8 +429,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( innerContext, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, titleBuilder: (_) => const TText('内层标题'), child: Container( @@ -486,8 +470,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, titleBuilder: (_) => TText('标题文字'), child: Container(height: 200)), @@ -507,8 +490,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, cancelBuilder: (_, __) => TText( '关闭', @@ -551,8 +533,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeOnOverlayClick: false, width: 240, height: 240, @@ -581,8 +562,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeOnOverlayClick: true, width: 240, height: 200, @@ -617,8 +597,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 320, margin: const EdgeInsets.only(top: 120, left: 16, right: 16), titleBuilder: (_) => TText('日历式留白'), @@ -642,8 +621,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, showOverlay: false, // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) @@ -668,8 +646,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 260, onOverlayClick: () => TToast.showText('点击蒙层', context: context), child: Container( @@ -692,8 +669,7 @@ class TPopupPage extends StatelessWidget { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 240, duration: const Duration(milliseconds: 600), child: Container( diff --git a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart index 157909917..8e51a6902 100644 --- a/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart +++ b/tdesign-component/lib/src/components/action_sheet/t_action_sheet.dart @@ -334,8 +334,7 @@ class TActionSheet { _actionSheetHandle = TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( cancelBuilder: null, confirmBuilder: null, showOverlay: showOverlay, diff --git a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart index c897c7273..d29028ee9 100644 --- a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart +++ b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart @@ -71,8 +71,7 @@ class TCalendarPopup { final childWidget = builder?.call(context) ?? child; _calendarHandle = TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( cancelBuilder: null, confirmBuilder: null, margin: EdgeInsets.only(top: top ?? 0), diff --git a/tdesign-component/lib/src/components/popup/_popup_center_close.dart b/tdesign-component/lib/src/components/popup/_popup_center_close.dart index 967951ba1..2a795cc44 100644 --- a/tdesign-component/lib/src/components/popup/_popup_center_close.dart +++ b/tdesign-component/lib/src/components/popup/_popup_center_close.dart @@ -1,23 +1,13 @@ -import 'package:flutter/material.dart'; +part of 't_popup.dart'; -import '../../theme/t_colors.dart'; -import '../../theme/t_theme.dart'; -import '../../util/context_extension.dart'; -import '../icon/t_icons.dart'; -import 't_popup_options.dart'; -import 't_popup_types.dart'; - -/// 构建 center 面板下方关闭控件。 -/// -/// - `closeBuilder` 为 sentinel [kPopupDefaultClose] → 内置圆形关闭图标。 -/// - 自定义 → 调用用户 builder。 -/// - 调用方需保证 `options.closeBuilder != null`。 +/// center 面板外关闭控件:默认图标 → [TPopupTrigger.closeBtn];自定义 → [TPopupTrigger.programmatic]。 Widget buildPopupCenterCloseControl({ required BuildContext context, required TPopupOptions options, - required VoidCallback onClose, + required VoidCallback onProgrammaticClose, + required void Function(TPopupTrigger trigger) onCloseWithTrigger, }) { - if (isPopupDefaultClose(options.closeBuilder)) { + if (_isPopupDefaultClose(options.closeBuilder)) { final theme = TTheme.of(context); return IconButton( tooltip: context.resource.close, @@ -26,13 +16,13 @@ Widget buildPopupCenterCloseControl({ color: theme.fontWhColor1, size: 32, ), - onPressed: onClose, + onPressed: () => onCloseWithTrigger(TPopupTrigger.closeBtn), ); } - return options.closeBuilder!(context, onClose); + return options.closeBuilder!(context, onProgrammaticClose); } -/// 居中浮层:白底内容区 + 面板**外下方**关闭控件(center 内置布局)。 +/// center 布局:内容面板 + 外置关闭区。 class PopupCenterUnderClose extends StatelessWidget { const PopupCenterUnderClose({ super.key, @@ -47,7 +37,7 @@ class PopupCenterUnderClose extends StatelessWidget { @override Widget build(BuildContext context) { - Widget panel = content; + var panel = content; if (options.width != null || options.height != null) { panel = SizedBox( width: options.width, @@ -56,12 +46,14 @@ class PopupCenterUnderClose extends StatelessWidget { ); } - void close() => onCloseWithTrigger(TPopupTrigger.programmatic); + void onProgrammaticClose() => + onCloseWithTrigger(TPopupTrigger.programmatic); final closeControl = buildPopupCenterCloseControl( context: context, options: options, - onClose: close, + onProgrammaticClose: onProgrammaticClose, + onCloseWithTrigger: onCloseWithTrigger, ); return Column( diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart index fb4ad65b4..d69c280a9 100644 --- a/tdesign-component/lib/src/components/popup/_popup_header.dart +++ b/tdesign-component/lib/src/components/popup/_popup_header.dart @@ -1,20 +1,6 @@ -import 'package:flutter/material.dart'; - -import '../../theme/t_colors.dart'; -import '../../theme/t_fonts.dart'; -import '../../theme/t_spacers.dart'; -import '../../theme/t_theme.dart'; -import '../../util/context_extension.dart'; -import '../../util/t_toolbar_pressable.dart'; -import '../text/t_text.dart'; -import 't_popup_options.dart'; -import 't_popup_types.dart'; - -/// 内置标题栏区域(仅 [TPopupPlacement.bottom])。 -/// -/// - `headerBuilder` 为 sentinel [kPopupDefaultHeader] → 内置三段式(cancel | title | confirm)。 -/// - `headerBuilder` 为自定义 → 整行替换,库内不再插入任何子 Widget。 -/// - `headerBuilder` 为 null → 不渲染头部。 +part of 't_popup.dart'; + +/// bottom 头部渲染;行为由 [TPopupOptions.hasBuiltInHeader]、[TPopupOptions.useCustomHeader] 决定。 class PopupHeader extends StatelessWidget { const PopupHeader({ super.key, @@ -33,8 +19,7 @@ class PopupHeader extends StatelessWidget { @override Widget build(BuildContext context) { - if (options.placement != TPopupPlacement.bottom || - options.headerBuilder == null) { + if (!options.hasBuiltInHeader) { return const SizedBox.shrink(); } @@ -42,12 +27,12 @@ class PopupHeader extends StatelessWidget { return options.headerBuilder!(context, _close); } - // 走内置三段式 return SizedBox( height: headerHeight, child: _DefaultHeader( options: options, close: _close, + onCloseWithTrigger: onCloseWithTrigger, ), ); } @@ -57,11 +42,18 @@ class _DefaultHeader extends StatelessWidget { const _DefaultHeader({ required this.options, required this.close, + required this.onCloseWithTrigger, }); final TPopupOptions options; + + /// 给「自定义 cancel/confirm builder」用的 close 回调(上报 [TPopupTrigger.programmatic])。 final VoidCallback close; + /// 给「内置 sentinel cancel/confirm 按钮」用的 close 入口, + /// 按钮自己传 [TPopupTrigger.cancelBtn] / [TPopupTrigger.confirmBtn]。 + final void Function(TPopupTrigger trigger) onCloseWithTrigger; + @override Widget build(BuildContext context) { final theme = TTheme.of(context); @@ -120,9 +112,9 @@ class _DefaultHeader extends StatelessWidget { } Widget _buildCancel(BuildContext context, TThemeData theme) { - if (isPopupDefaultCancel(options.cancelBuilder)) { + if (_isPopupDefaultCancel(options.cancelBuilder)) { return TToolbarPressable( - onTap: close, + onTap: () => onCloseWithTrigger(TPopupTrigger.cancelBtn), child: TText( context.resource.cancel, textColor: theme.textColorSecondary, @@ -134,9 +126,9 @@ class _DefaultHeader extends StatelessWidget { } Widget _buildConfirm(BuildContext context, TThemeData theme) { - if (isPopupDefaultConfirm(options.confirmBuilder)) { + if (_isPopupDefaultConfirm(options.confirmBuilder)) { return TToolbarPressable( - onTap: close, + onTap: () => onCloseWithTrigger(TPopupTrigger.confirmBtn), child: TText( context.resource.confirm, textColor: theme.brandNormalColor, diff --git a/tdesign-component/lib/src/components/popup/_popup_layout.dart b/tdesign-component/lib/src/components/popup/_popup_layout.dart index 986974275..aa326620e 100644 --- a/tdesign-component/lib/src/components/popup/_popup_layout.dart +++ b/tdesign-component/lib/src/components/popup/_popup_layout.dart @@ -1,11 +1,6 @@ -import 'package:flutter/material.dart'; +part of 't_popup.dart'; -import 't_popup_types.dart'; - -/// 根据 placement 计算 Positioned 约束。 -/// -/// center 模式只负责 [Positioned.fill] + [Center],**面板尺寸由 PopupShell 决定**, -/// 这里不再插入 SizedBox(避免和 shell 中的尺寸约束双重包裹)。 +/// 按 [TPopupPlacement] 计算 [Positioned];center 仅居中,尺寸由 [PopupShell] 约束。 class PopupLayout { PopupLayout({ required this.placement, diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart index 7d78f4b84..048ec966b 100644 --- a/tdesign-component/lib/src/components/popup/_popup_route.dart +++ b/tdesign-component/lib/src/components/popup/_popup_route.dart @@ -1,13 +1,8 @@ -import 'package:flutter/material.dart'; +part of 't_popup.dart'; -import '_popup_layout.dart'; -import '_popup_shell.dart'; -import 't_popup_options.dart'; -import 't_popup_types.dart'; - -/// 私有 Popup 路由。 -class TPopupNavigatorRoute extends PopupRoute { - TPopupNavigatorRoute({ +/// 库内 [PopupRoute];由 [TPopupHandle.open] push,勿在外部直接构造。 +class _PopupNavigatorRoute extends PopupRoute { + _PopupNavigatorRoute({ required this.options, required this.onCloseWithTrigger, }) : _layout = PopupLayout( @@ -57,16 +52,14 @@ class TPopupNavigatorRoute extends PopupRoute { @override Color get barrierColor => Colors.transparent; - /// 路由须非 opaque,否则透明区域会露出 Modal 默认黑底。 - /// - /// 无蒙层时滚动穿透由 [_scrollBlocker] 处理,不能靠 opaque=true(会整屏发黑)。 + /// 非 opaque,避免透明区域露出 Modal 默认底色。 @override bool get opaque => false; @override bool get maintainState => !options.destroyOnClose; - /// 关闭动画开始前回调(系统返回 / handle.close / 蒙层等统一入口)。 + /// 关闭开始前统一入口:触发 [TPopupOptions.onClose]、[onVisibleChange](false, …)。 void fireCloseStart(TPopupTrigger trigger) { if (_closeStartFired) { return; diff --git a/tdesign-component/lib/src/components/popup/_popup_shell.dart b/tdesign-component/lib/src/components/popup/_popup_shell.dart index 82eb655ed..97eec708f 100644 --- a/tdesign-component/lib/src/components/popup/_popup_shell.dart +++ b/tdesign-component/lib/src/components/popup/_popup_shell.dart @@ -1,12 +1,4 @@ -import 'package:flutter/material.dart'; - -import '../../theme/t_colors.dart'; -import '../../theme/t_radius.dart'; -import '../../theme/t_theme.dart'; -import '_popup_center_close.dart'; -import '_popup_header.dart'; -import 't_popup_options.dart'; -import 't_popup_types.dart'; +part of 't_popup.dart'; /// 浮层内容外壳:圆角、Header(仅 bottom)、child;center 由 [PopupCenterUnderClose] 接管下方关闭区。 class PopupShell extends StatelessWidget { diff --git a/tdesign-component/lib/src/components/popup/t_popup_tracker.dart b/tdesign-component/lib/src/components/popup/_popup_tracker.dart similarity index 95% rename from tdesign-component/lib/src/components/popup/t_popup_tracker.dart rename to tdesign-component/lib/src/components/popup/_popup_tracker.dart index 231b04036..962d74bcf 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_tracker.dart +++ b/tdesign-component/lib/src/components/popup/_popup_tracker.dart @@ -1,7 +1,7 @@ part of 't_popup.dart'; /// 库内:按 [Navigator] 记录 [TPopupHandle] 栈,用于嵌套与 [TPopup.show] 防重复打开。 -abstract class TPopupTracker { +abstract class _PopupTracker { static final Map> _stacks = {}; static void push(NavigatorState navigator, TPopupHandle handle) { diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart index 1f8aed576..b0b3134ef 100644 --- a/tdesign-component/lib/src/components/popup/t_popup.dart +++ b/tdesign-component/lib/src/components/popup/t_popup.dart @@ -1,48 +1,71 @@ -import 'package:flutter/material.dart'; +/// TDesign 弹出层(Popup)组件库。 +/// +/// 对外 API: +/// * [TPopup] — 命令式打开浮层 +/// * [TPopupOptions] — 配置(推荐命名工厂) +/// * [TPopupHandle] — 显隐控制 +/// * [TPopupPlacement]、[TPopupTrigger] — 方向与关闭来源 +/// * [TPopupHeaderBuilder]、[TPopupSlotBuilder]、[TPopupVisibleChangeCallback] — 构建器类型 +library; -import '_popup_route.dart'; -import 't_popup_options.dart'; -import 't_popup_types.dart'; +import 'package:flutter/material.dart'; -export 't_popup_options.dart'; -export 't_popup_types.dart'; +import '../../theme/t_colors.dart'; +import '../../theme/t_fonts.dart'; +import '../../theme/t_radius.dart'; +import '../../theme/t_spacers.dart'; +import '../../theme/t_theme.dart'; +import '../../util/context_extension.dart'; +import '../../util/t_toolbar_pressable.dart'; +import '../icon/t_icons.dart'; +import '../text/t_text.dart'; +part '_popup_center_close.dart'; +part '_popup_header.dart'; +part '_popup_layout.dart'; +part '_popup_route.dart'; +part '_popup_shell.dart'; +part '_popup_tracker.dart'; part 't_popup_handle.dart'; -part 't_popup_tracker.dart'; +part 't_popup_options.dart'; +part 't_popup_types.dart'; -/// 弹出层命名空间:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 +/// 弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 +/// +/// 通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。 /// -/// 仅提供静态入口 [show];返回的 [TPopupHandle] 控制本次浮层的显隐。 +/// **示例** /// /// ```dart /// final handle = TPopup.show( /// context, -/// options: TPopupOptions( -/// placement: TPopupPlacement.bottom, +/// options: TPopupOptions.bottom( /// titleBuilder: (_) => const Text('标题'), /// child: MyPanel(), /// ), /// ); -/// -/// // 关闭后再开(同一 handle) /// handle.close(); -/// handle.open(context); +/// handle.open(); /// ``` /// -/// 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 -/// [TPopupOptions.normalized] 中裁掉)。 +/// 配置项见 [TPopupOptions];方向见 [TPopupPlacement]。 final class TPopup { const TPopup._(); - /// 命令式打开浮层并压入独立路由。 + /// 打开浮层并压入独立 [PopupRoute]。 + /// + /// [context] 用于查找 [Navigator] 并展示浮层。 + /// + /// [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 + /// + /// 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 + /// [TPopupHandle.isShowing] 控制与查询。 /// - /// 返回 [TPopupHandle]:可用 [TPopupHandle.close] / [TPopupHandle.open] 控制显隐; - /// [TPopupHandle.isShowing] 可查询是否仍在展示。 + /// 同一 [Navigator] 上若已有展示中的浮层,重复调用会返回已有 handle(防连点)。 /// - /// 同一按钮在页面 context 上重复调用时,若已有展示中的 Popup 会返回已有 handle(防连点)。 + /// [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 /// - /// - [navigatorContext]:指定使用哪个 [Navigator],默认用 `context`。 - /// - [useRootNavigator]:是否使用根 [Navigator](嵌套导航场景)。 + /// [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 static TPopupHandle show( BuildContext context, { required TPopupOptions options, @@ -55,10 +78,10 @@ final class TPopup { rootNavigator: useRootNavigator, ); - final existing = TPopupTracker.top(navigator); + final existing = _PopupTracker.top(navigator); if (existing != null && existing.isShowing && - ModalRoute.of(context) is! TPopupNavigatorRoute) { + ModalRoute.of(context) is! _PopupNavigatorRoute) { return existing; } diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart index 3b85cca62..236d2ebd6 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart @@ -1,15 +1,16 @@ part of 't_popup.dart'; -/// [TPopup.show] 的返回值:可查询展示状态,并多次 [open] / [close]。 +/// [TPopup.show] 的返回值,用于控制同一份 [TPopupOptions] 的多次打开与关闭。 /// -/// 保存此对象;关闭后再次 [open] 会按创建时的 [TPopupOptions] 重新压入路由。 +/// **示例** /// /// ```dart -/// final handle = TPopup.show(context, options: opts); +/// final handle = TPopup.show( +/// context, +/// options: TPopupOptions.bottom(child: panel), +/// ); /// handle.close(); -/// if (!handle.isShowing) { -/// handle.open(context); -/// } +/// handle.open(); // 可省略 context,复用已缓存的 Navigator /// ``` class TPopupHandle { TPopupHandle._({ @@ -18,16 +19,17 @@ class TPopupHandle { this.useRootNavigator = false, }); - /// 打开/再次打开时使用的配置(每次 [open] 会 [TPopupOptions.normalized])。 + /// 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 final TPopupOptions options; - /// 与 [TPopup.show] 相同:指定 Navigator 的 context。 + /// 与 [TPopup.show] 的 [navigatorContext] 相同。 final BuildContext? navigatorContext; - /// 与 [TPopup.show] 相同:是否使用根 Navigator。 + /// 与 [TPopup.show] 的 [useRootNavigator] 相同。 final bool useRootNavigator; - TPopupNavigatorRoute? _route; + _PopupNavigatorRoute? _route; + NavigatorState? _lastNavigator; bool _isClosed = false; /// 浮层是否仍在展示(路由在栈中且未进入关闭流程)。 @@ -35,20 +37,33 @@ class TPopupHandle { /// 打开或重新打开浮层。 /// - /// 已展示时调用无副作用。关闭后再次调用会压入新的 [TPopupNavigatorRoute]。 - void open(BuildContext context) { + /// [context] 可选。首次调用须能解析 [Navigator](传入 [context] 或依赖 + /// [navigatorContext]);后续可省略以复用缓存的 [NavigatorState]。 + /// + /// 已展示时调用无副作用。Navigator 已销毁且未提供新 [context] 时,debug 下 assert, + /// release 下静默返回。 + void open([BuildContext? context]) { if (isShowing) { return; } - // 先用「原始」配置做 debug 期参数校验(保留 sentinel 与用户传值差异), - // 再 normalize 给路由使用(normalize 会按 placement 强制清空无效字段)。 + final navigator = _resolveNavigator(context); + if (navigator == null) { + assert( + false, + 'TPopupHandle.open: cannot resolve Navigator. ' + 'Either pass a valid context or ensure the handle was created ' + 'with a still-mounted navigatorContext.', + ); + return; + } + options.assertPlacementParams(); final normalized = options.normalized(); - final navigator = _navigatorOf(context); _isClosed = false; + _lastNavigator = navigator; - TPopupNavigatorRoute? route; + _PopupNavigatorRoute? route; void closeWithTrigger(TPopupTrigger trigger, [Object? result]) { if (!isShowing) { @@ -59,43 +74,45 @@ class TPopupHandle { navigator.pop(result); } - route = TPopupNavigatorRoute( + route = _PopupNavigatorRoute( options: normalized, onCloseWithTrigger: closeWithTrigger, ); _route = route; - TPopupTracker.push(navigator, this); + _PopupTracker.push(navigator, this); navigator.push(route).whenComplete(() { - TPopupTracker.remove(navigator, this); + _PopupTracker.remove(navigator, this); _detachRoute(); }); } - /// 关闭当前展示的浮层([TPopupTrigger.programmatic])。 + /// 关闭当前展示的浮层;[TPopupOptions.onVisibleChange] 的 [TPopupTrigger] 为 + /// [TPopupTrigger.programmatic]。 + /// + /// [result] 可选,作为 [Navigator.pop] 的返回值。 /// - /// 已关闭或未展示时调用无副作用。嵌套多层时须用**对应层**的 handle 关闭。 + /// 已关闭或未展示时调用无副作用。嵌套浮层须使用对应层的 handle 关闭。 void close([Object? result]) { if (!isShowing) { return; } _markClosing(); _route?.fireCloseStart(TPopupTrigger.programmatic); - _navigatorOfForClose().pop(result); + _route!.navigator?.pop(result); } - NavigatorState _navigatorOf(BuildContext context) { - final navContext = navigatorContext ?? context; - return Navigator.of( - navContext, - rootNavigator: useRootNavigator, - ); - } - - /// [close] 不依赖外部 context,使用路由所在 Navigator。 - NavigatorState _navigatorOfForClose() { - return _route!.navigator!; + NavigatorState? _resolveNavigator(BuildContext? context) { + final ctx = context ?? navigatorContext; + if (ctx != null) { + return Navigator.maybeOf(ctx, rootNavigator: useRootNavigator); + } + final cached = _lastNavigator; + if (cached != null && cached.mounted) { + return cached; + } + return null; } void _markClosing() { diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart index ac22c1b98..b833c7ab2 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_options.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart @@ -1,32 +1,43 @@ -import 'package:flutter/material.dart'; +part of 't_popup.dart'; -import 't_popup_types.dart'; +/// 用于 [TPopupOptions.copyWith] 区分"不传"与"显式 null"。 +const Object _unset = Object(); -/// 浮层配置:[TPopup.show] 的唯一参数来源。 +/// [TPopup.show] 的配置对象。 /// -/// ## 按 [placement] 用哪些字段 +/// ## 如何创建 /// -/// | placement | 头部 / 关闭区 | 尺寸字段 | -/// |-----------|----------------|----------| -/// | [TPopupPlacement.bottom] | `headerBuilder` / `titleBuilder` / `cancelBuilder` / `confirmBuilder` | `height`、`margin` | -/// | [TPopupPlacement.center] | `closeBuilder` | `width`、`height` | -/// | [TPopupPlacement.top] | — | `height`、`margin.top` / `left` / `right` | -/// | [TPopupPlacement.left] / [right] | — | `width`、对应方向 `margin` | +/// | 场景 | 推荐用法 | +/// |------|----------| +/// | 弹出方向已知 | [TPopupOptions.bottom]、[TPopupOptions.center]、[TPopupOptions.top]、[TPopupOptions.left]、[TPopupOptions.right] | +/// | 方向由变量决定 | 默认构造并设置 [placement];Debug 下传错字段会抛 [FlutterError] | /// -/// 非 bottom 上传 `headerBuilder` / `titleBuilder` / `cancelBuilder` / `confirmBuilder`、 -/// 或非 center 上传 `closeBuilder` 都会被 [normalized] 裁掉。 +/// 命名工厂只暴露当前方向生效的字段(例如 [TPopupOptions.bottom] 无 [width] 参数)。 /// -/// ## Builder 三态 +/// ## 字段与 [TPopupPlacement] /// -/// 每个 builder 字段都有三种使用方式: +/// | [TPopupPlacement] | 头部 / 关闭 | 尺寸 | +/// |-------------------|-------------|------| +/// | [TPopupPlacement.bottom] | [headerBuilder]、[titleBuilder]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] | +/// | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] | +/// | [TPopupPlacement.top] | — | [height]、[margin] | +/// | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[margin] | /// -/// - **不传**(保留默认)→ 使用内置 UI(如 `headerBuilder` 默认走三段式、`cancelBuilder` 默认显示「取消」、 -/// `closeBuilder` 默认显示圆形关闭图标)。 -/// - **显式传 `null`** → 隐藏该部分。 -/// - **传自定义函数** → 完全替换该部分;函数会拿到 `close` 回调,用于在 onTap 中关闭浮层。 +/// ## Builder 三态([headerBuilder]、[cancelBuilder]、[confirmBuilder]、[closeBuilder]) /// -/// `titleBuilder` 例外:默认为 `null` 表示无标题(不需要内置标题文案)。 +/// | 传参方式 | 效果 | +/// |----------|------| +/// | 省略(使用默认值) | 渲染内置 UI | +/// | 显式 `null` | 隐藏该区域 | +/// | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 | +/// +/// [titleBuilder] 默认为 `null`,表示无标题文案。 +/// +/// 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。 class TPopupOptions { + /// 通用构造;[placement] 在运行时才能确定时使用。 + /// + /// 方向已知时请优先使用 [TPopupOptions.bottom] 等命名工厂。 const TPopupOptions({ required this.child, this.placement = TPopupPlacement.bottom, @@ -42,11 +53,11 @@ class TPopupOptions { this.preventScrollThrough = true, this.destroyOnClose = false, this.duration = const Duration(milliseconds: 240), - this.headerBuilder = kPopupDefaultHeader, + this.headerBuilder = _kPopupDefaultHeader, this.titleBuilder, - this.cancelBuilder = kPopupDefaultCancel, - this.confirmBuilder = kPopupDefaultConfirm, - this.closeBuilder = kPopupDefaultClose, + this.cancelBuilder = _kPopupDefaultCancel, + this.confirmBuilder = _kPopupDefaultConfirm, + this.closeBuilder = _kPopupDefaultClose, this.onOpen, this.onOpened, this.onClose, @@ -55,24 +66,258 @@ class TPopupOptions { this.onOverlayClick, }); + /// 创建 [TPopupPlacement.bottom] 配置。 + /// + /// 固定 [placement] 为 [TPopupPlacement.bottom];默认带内置头部。 + /// 蒙层、动画、生命周期等字段语义见同名成员文档。 + factory TPopupOptions.bottom({ + required Widget child, + double? height, + EdgeInsets margin = EdgeInsets.zero, + TPopupHeaderBuilder? headerBuilder = _kPopupDefaultHeader, + WidgetBuilder? titleBuilder, + TPopupSlotBuilder? cancelBuilder = _kPopupDefaultCancel, + TPopupSlotBuilder? confirmBuilder = _kPopupDefaultConfirm, + double? radius, + Color? backgroundColor, + bool showOverlay = true, + bool closeOnOverlayClick = true, + Color? overlayColor, + double? overlayOpacity, + bool preventScrollThrough = true, + bool destroyOnClose = false, + Duration duration = const Duration(milliseconds: 240), + VoidCallback? onOpen, + VoidCallback? onOpened, + VoidCallback? onClose, + VoidCallback? onClosed, + TPopupVisibleChangeCallback? onVisibleChange, + VoidCallback? onOverlayClick, + }) => + TPopupOptions( + child: child, + placement: TPopupPlacement.bottom, + height: height, + margin: margin, + headerBuilder: headerBuilder, + titleBuilder: titleBuilder, + cancelBuilder: cancelBuilder, + confirmBuilder: confirmBuilder, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + + /// 创建 [TPopupPlacement.center] 配置。 + /// + /// 固定 [placement] 为 [TPopupPlacement.center];默认面板外下方圆形关闭按钮。 + factory TPopupOptions.center({ + required Widget child, + double? width, + double? height, + TPopupSlotBuilder? closeBuilder = _kPopupDefaultClose, + double? radius, + Color? backgroundColor, + bool showOverlay = true, + bool closeOnOverlayClick = true, + Color? overlayColor, + double? overlayOpacity, + bool preventScrollThrough = true, + bool destroyOnClose = false, + Duration duration = const Duration(milliseconds: 240), + VoidCallback? onOpen, + VoidCallback? onOpened, + VoidCallback? onClose, + VoidCallback? onClosed, + TPopupVisibleChangeCallback? onVisibleChange, + VoidCallback? onOverlayClick, + }) => + TPopupOptions( + child: child, + placement: TPopupPlacement.center, + width: width, + height: height, + closeBuilder: closeBuilder, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + + /// 创建 [TPopupPlacement.top] 配置。 + /// + /// 固定 [placement] 为 [TPopupPlacement.top];无内置头部。 + factory TPopupOptions.top({ + required Widget child, + double? height, + EdgeInsets margin = EdgeInsets.zero, + double? radius, + Color? backgroundColor, + bool showOverlay = true, + bool closeOnOverlayClick = true, + Color? overlayColor, + double? overlayOpacity, + bool preventScrollThrough = true, + bool destroyOnClose = false, + Duration duration = const Duration(milliseconds: 240), + VoidCallback? onOpen, + VoidCallback? onOpened, + VoidCallback? onClose, + VoidCallback? onClosed, + TPopupVisibleChangeCallback? onVisibleChange, + VoidCallback? onOverlayClick, + }) => + TPopupOptions( + child: child, + placement: TPopupPlacement.top, + height: height, + margin: margin, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + + /// 创建 [TPopupPlacement.left] 配置。 + /// + /// 固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。 + factory TPopupOptions.left({ + required Widget child, + double? width, + EdgeInsets margin = EdgeInsets.zero, + double? radius, + Color? backgroundColor, + bool showOverlay = true, + bool closeOnOverlayClick = true, + Color? overlayColor, + double? overlayOpacity, + bool preventScrollThrough = true, + bool destroyOnClose = false, + Duration duration = const Duration(milliseconds: 240), + VoidCallback? onOpen, + VoidCallback? onOpened, + VoidCallback? onClose, + VoidCallback? onClosed, + TPopupVisibleChangeCallback? onVisibleChange, + VoidCallback? onOverlayClick, + }) => + TPopupOptions( + child: child, + placement: TPopupPlacement.left, + width: width, + margin: margin, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + + /// 创建 [TPopupPlacement.right] 配置。 + /// + /// 固定 [placement] 为 [TPopupPlacement.right];未传 [width] 时布局默认宽度 280。 + factory TPopupOptions.right({ + required Widget child, + double? width, + EdgeInsets margin = EdgeInsets.zero, + double? radius, + Color? backgroundColor, + bool showOverlay = true, + bool closeOnOverlayClick = true, + Color? overlayColor, + double? overlayOpacity, + bool preventScrollThrough = true, + bool destroyOnClose = false, + Duration duration = const Duration(milliseconds: 240), + VoidCallback? onOpen, + VoidCallback? onOpened, + VoidCallback? onClose, + VoidCallback? onClosed, + TPopupVisibleChangeCallback? onVisibleChange, + VoidCallback? onOverlayClick, + }) => + TPopupOptions( + child: child, + placement: TPopupPlacement.right, + width: width, + margin: margin, + radius: radius, + backgroundColor: backgroundColor, + showOverlay: showOverlay, + closeOnOverlayClick: closeOnOverlayClick, + overlayColor: overlayColor, + overlayOpacity: overlayOpacity, + preventScrollThrough: preventScrollThrough, + destroyOnClose: destroyOnClose, + duration: duration, + onOpen: onOpen, + onOpened: onOpened, + onClose: onClose, + onClosed: onClosed, + onVisibleChange: onVisibleChange, + onOverlayClick: onOverlayClick, + ); + /// 浮层主体内容(必填)。 final Widget child; /// 出现位置,默认 [TPopupPlacement.bottom]。 final TPopupPlacement placement; - /// 宽度;对 left、right、center 生效。 + /// 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 final double? width; - /// 高度;对 top、bottom 生效;center 用于约束面板尺寸。 + /// 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 final double? height; - /// 外边距: - /// - top:`top` / `left` / `right` 生效。 - /// - bottom:`top` > 0 触发「贴顶模式」(日历式留白);否则贴底,`left` / `right` / `bottom` 生效。 - /// - left:`top` / `bottom` / `left` 生效。 - /// - right:`top` / `bottom` / `right` 生效。 - /// - center:全忽略。 + /// 外边距;生效边取决于 [placement]。 + /// + /// [TPopupPlacement.bottom]:`top > 0` 时贴顶留白(日历式),否则贴底。 + /// [TPopupPlacement.center] 忽略。 final EdgeInsets margin; /// 内容区圆角,默认主题大圆角。 @@ -96,60 +341,138 @@ class TPopupOptions { /// 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 final bool preventScrollThrough; - /// 为 true 时 Popup 路由 maintainState 为 false,关闭后不保留路由内 State。 + /// 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 final bool destroyOnClose; - /// 打开与关闭动画时长(一致)。 + /// 打开/关闭动画时长。 final Duration duration; - // ============ bottom 头部 ============ - - /// bottom 头部构建器: - /// - 默认 [kPopupDefaultHeader] → 渲染内置三段式(`cancelBuilder | titleBuilder | confirmBuilder`)。 - /// - `null` → 不显示头部。 - /// - 自定义 `(ctx, close) => Widget` → 完全替换整行头部([titleBuilder] / [cancelBuilder] / - /// [confirmBuilder] 被忽略)。 + /// bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 + /// + /// 自定义时忽略 [titleBuilder]、[cancelBuilder]、[confirmBuilder]。 final TPopupHeaderBuilder? headerBuilder; - /// bottom 标题槽(仅当 [headerBuilder] 为 [kPopupDefaultHeader] 时生效): - /// - `null` → 无标题。 - /// - 自定义 `(ctx) => Widget` → 显示自定义标题。 + /// bottom 标题;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 final WidgetBuilder? titleBuilder; - /// bottom 左槽(仅当 [headerBuilder] 为 [kPopupDefaultHeader] 时生效): - /// - 默认 [kPopupDefaultCancel] → 显示本地化「取消」按钮,点击关闭浮层。 - /// - `null` → 隐藏左槽。 - /// - 自定义 `(ctx, close) => Widget` → 替换左槽,自行决定是否调 `close()`。 + /// bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 + /// + /// 内置默认为「取消」,点击触发 [TPopupTrigger.cancelBtn]。 final TPopupSlotBuilder? cancelBuilder; - /// bottom 右槽(仅当 [headerBuilder] 为 [kPopupDefaultHeader] 时生效): - /// - 默认 [kPopupDefaultConfirm] → 显示本地化「确定」按钮,点击关闭浮层。 - /// - `null` → 隐藏右槽。 - /// - 自定义 `(ctx, close) => Widget` → 替换右槽。 + /// bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 + /// + /// 内置默认为「确定」,点击触发 [TPopupTrigger.confirmBtn]。 final TPopupSlotBuilder? confirmBuilder; - // ============ center 关闭区 ============ - - /// center 面板下方关闭区: - /// - 默认 [kPopupDefaultClose] → 显示圆形关闭图标,点击关闭浮层。 - /// - `null` → 不显示关闭区。 - /// - 自定义 `(ctx, close) => Widget` → 替换关闭区。 + /// center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 + /// + /// 内置默认点击触发 [TPopupTrigger.closeBtn]。 final TPopupSlotBuilder? closeBuilder; - // ============ 生命周期 ============ - + /// 路由 push 时(打开动画开始前)。 final VoidCallback? onOpen; + + /// 打开动画结束。 final VoidCallback? onOpened; + + /// 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 final VoidCallback? onClose; + + /// 路由 pop 且关闭动画结束。 final VoidCallback? onClosed; + + /// 显隐变化;第二个参数为 [TPopupTrigger]。 final TPopupVisibleChangeCallback? onVisibleChange; + + /// 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 final VoidCallback? onOverlayClick; - /// 按 [placement] 裁剪无效字段,得到路由实际使用的配置副本。 + /// 返回配置副本。 /// - /// - bottom 才保留 `headerBuilder` / `titleBuilder` / `cancelBuilder` / `confirmBuilder`; - /// 其它 placement 上这些字段强制重置为 sentinel(不渲染头部,因为没渲染入口)。 - /// - center 才保留 `closeBuilder`;其它 placement 重置为 sentinel。 + /// 未传入的字段保持原值;对 builder 显式传入 `null` 表示隐藏该区域。 + TPopupOptions copyWith({ + Widget? child, + TPopupPlacement? placement, + Object? width = _unset, + Object? height = _unset, + EdgeInsets? margin, + Object? radius = _unset, + Object? backgroundColor = _unset, + bool? showOverlay, + bool? closeOnOverlayClick, + Object? overlayColor = _unset, + Object? overlayOpacity = _unset, + bool? preventScrollThrough, + bool? destroyOnClose, + Duration? duration, + Object? headerBuilder = _unset, + Object? titleBuilder = _unset, + Object? cancelBuilder = _unset, + Object? confirmBuilder = _unset, + Object? closeBuilder = _unset, + Object? onOpen = _unset, + Object? onOpened = _unset, + Object? onClose = _unset, + Object? onClosed = _unset, + Object? onVisibleChange = _unset, + Object? onOverlayClick = _unset, + }) { + return TPopupOptions( + child: child ?? this.child, + placement: placement ?? this.placement, + width: identical(width, _unset) ? this.width : (width as num?)?.toDouble(), + height: identical(height, _unset) ? this.height : (height as num?)?.toDouble(), + margin: margin ?? this.margin, + radius: identical(radius, _unset) ? this.radius : (radius as num?)?.toDouble(), + backgroundColor: identical(backgroundColor, _unset) + ? this.backgroundColor + : backgroundColor as Color?, + showOverlay: showOverlay ?? this.showOverlay, + closeOnOverlayClick: closeOnOverlayClick ?? this.closeOnOverlayClick, + overlayColor: identical(overlayColor, _unset) + ? this.overlayColor + : overlayColor as Color?, + overlayOpacity: identical(overlayOpacity, _unset) + ? this.overlayOpacity + : (overlayOpacity as num?)?.toDouble(), + preventScrollThrough: preventScrollThrough ?? this.preventScrollThrough, + destroyOnClose: destroyOnClose ?? this.destroyOnClose, + duration: duration ?? this.duration, + headerBuilder: identical(headerBuilder, _unset) + ? this.headerBuilder + : headerBuilder as TPopupHeaderBuilder?, + titleBuilder: identical(titleBuilder, _unset) + ? this.titleBuilder + : titleBuilder as WidgetBuilder?, + cancelBuilder: identical(cancelBuilder, _unset) + ? this.cancelBuilder + : cancelBuilder as TPopupSlotBuilder?, + confirmBuilder: identical(confirmBuilder, _unset) + ? this.confirmBuilder + : confirmBuilder as TPopupSlotBuilder?, + closeBuilder: identical(closeBuilder, _unset) + ? this.closeBuilder + : closeBuilder as TPopupSlotBuilder?, + onOpen: identical(onOpen, _unset) ? this.onOpen : onOpen as VoidCallback?, + onOpened: identical(onOpened, _unset) + ? this.onOpened + : onOpened as VoidCallback?, + onClose: + identical(onClose, _unset) ? this.onClose : onClose as VoidCallback?, + onClosed: identical(onClosed, _unset) + ? this.onClosed + : onClosed as VoidCallback?, + onVisibleChange: identical(onVisibleChange, _unset) + ? this.onVisibleChange + : onVisibleChange as TPopupVisibleChangeCallback?, + onOverlayClick: identical(onOverlayClick, _unset) + ? this.onOverlayClick + : onOverlayClick as VoidCallback?, + ); + } + + /// {@nodoc} TPopupOptions normalized() { final isBottom = placement == TPopupPlacement.bottom; final isCenter = placement == TPopupPlacement.center; @@ -183,32 +506,39 @@ class TPopupOptions { ); } - // ============ 派生 ============ + /// {@nodoc} + bool get usesDefaultHeader => _isPopupDefaultHeader(headerBuilder); + /// {@nodoc} + bool get usesDefaultCancel => _isPopupDefaultCancel(cancelBuilder); + /// {@nodoc} + bool get usesDefaultConfirm => _isPopupDefaultConfirm(confirmBuilder); + /// {@nodoc} + bool get usesDefaultClose => _isPopupDefaultClose(closeBuilder); - /// 是否走自定义 header([headerBuilder] 非 null 且非默认 sentinel)。 + /// {@nodoc} bool get useCustomHeader => placement == TPopupPlacement.bottom && headerBuilder != null && - !isPopupDefaultHeader(headerBuilder); + !_isPopupDefaultHeader(headerBuilder); - /// 是否走内置三段式头部([headerBuilder] 为默认 sentinel)。 + /// {@nodoc} bool get useDefaultHeader => placement == TPopupPlacement.bottom && - isPopupDefaultHeader(headerBuilder); + _isPopupDefaultHeader(headerBuilder); - /// 内置三段式中,左槽是否要画(非 null)。 + /// {@nodoc} bool get showCancelSlot => placement == TPopupPlacement.bottom && useDefaultHeader && cancelBuilder != null; - /// 内置三段式中,右槽是否要画(非 null)。 + /// {@nodoc} bool get showConfirmSlot => placement == TPopupPlacement.bottom && useDefaultHeader && confirmBuilder != null; - /// bottom 是否实际渲染头部(自定义 / 内置三段中至少有一项可见)。 + /// {@nodoc} bool get hasBuiltInHeader { if (placement != TPopupPlacement.bottom || headerBuilder == null) { return false; @@ -216,67 +546,72 @@ class TPopupOptions { if (useCustomHeader) { return true; } - // 默认三段:任一槽位(cancel/title/confirm)非 null 都算 return cancelBuilder != null || confirmBuilder != null || titleBuilder != null; } - /// Debug 下检查易误用参数(如 bottom 传 `width`、center 传 `margin`),仅 `debugPrint` 不抛错。 + /// {@nodoc} void assertPlacementParams() { assert(() { - switch (placement) { - case TPopupPlacement.top: - if (width != null) { - debugPrint('TPopup: width is ignored for placement=top'); - } - if (margin.bottom > 0) { - debugPrint('TPopup: margin.bottom is ignored for placement=top'); - } - break; - case TPopupPlacement.bottom: - if (width != null) { - debugPrint('TPopup: width is ignored for placement=bottom'); - } - break; - case TPopupPlacement.left: - if (height != null) { - debugPrint('TPopup: height is ignored for placement=left'); - } - if (margin.right > 0) { - debugPrint('TPopup: margin.right is ignored for placement=left'); - } - break; - case TPopupPlacement.right: - if (height != null) { - debugPrint('TPopup: height is ignored for placement=right'); - } - if (margin.left > 0) { - debugPrint('TPopup: margin.left is ignored for placement=right'); - } - break; - case TPopupPlacement.center: - if (margin != EdgeInsets.zero) { - debugPrint('TPopup: margin is ignored for placement=center'); - } - break; - } - // 非 bottom 设了 header / 三段相关字段(非默认 sentinel)→ 提示 - final hasBottomHeaderCustom = !isPopupDefaultHeader(headerBuilder) || - titleBuilder != null || - !isPopupDefaultCancel(cancelBuilder) || - !isPopupDefaultConfirm(confirmBuilder); - if (placement != TPopupPlacement.bottom && hasBottomHeaderCustom) { - debugPrint( - 'TPopup: header/title/cancel/confirmBuilder only apply to placement=bottom', - ); - } - // 非 center 设了 closeBuilder 自定义 → 提示 - if (placement != TPopupPlacement.center && - !isPopupDefaultClose(closeBuilder)) { - debugPrint('TPopup: closeBuilder only applies to placement=center'); + final err = _validatePlacementParams(); + if (err != null) { + throw FlutterError('TPopupOptions: $err'); } return true; }()); } + + String? _validatePlacementParams() { + switch (placement) { + case TPopupPlacement.top: + if (width != null) { + return 'width is not valid for placement=top; use height + margin.'; + } + if (margin.bottom > 0) { + return 'margin.bottom is not valid for placement=top.'; + } + break; + case TPopupPlacement.bottom: + if (width != null) { + return 'width is not valid for placement=bottom; use height + margin.'; + } + break; + case TPopupPlacement.left: + if (height != null) { + return 'height is not valid for placement=left; use width + margin.'; + } + if (margin.right > 0) { + return 'margin.right is not valid for placement=left.'; + } + break; + case TPopupPlacement.right: + if (height != null) { + return 'height is not valid for placement=right; use width + margin.'; + } + if (margin.left > 0) { + return 'margin.left is not valid for placement=right.'; + } + break; + case TPopupPlacement.center: + if (margin != EdgeInsets.zero) { + return 'margin is not valid for placement=center.'; + } + break; + } + final hasBottomHeaderCustom = !_isPopupDefaultHeader(headerBuilder) || + titleBuilder != null || + !_isPopupDefaultCancel(cancelBuilder) || + !_isPopupDefaultConfirm(confirmBuilder); + if (placement != TPopupPlacement.bottom && hasBottomHeaderCustom) { + return 'header/title/cancel/confirmBuilder only apply to ' + 'placement=bottom (got placement=$placement).'; + } + if (placement != TPopupPlacement.center && + !_isPopupDefaultClose(closeBuilder)) { + return 'closeBuilder only applies to placement=center ' + '(got placement=$placement).'; + } + return null; + } } diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart index 212893cd1..73ea6da9c 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_types.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart @@ -1,101 +1,99 @@ -import 'package:flutter/widgets.dart'; +part of 't_popup.dart'; -/// 浮层从哪个方向出现;决定哪些 [TPopupOptions] 字段生效。 +/// 浮层出现方向;决定 [TPopupOptions] 中哪些字段生效。 /// -/// - [top] / [bottom]:纵向滑入,用 `height`、`margin`(bottom 可用 `margin.top` 做日历式留白)。 -/// - [left] / [right]:侧栏,用 `width`、`margin`。 -/// - [center]:居中缩放,用 `closeBuilder` 控制下方关闭区。 +/// 与 [TPopupOptions] 类文档中的「字段与 placement」表对应。 +/// 方向固定时请用 [TPopupOptions.bottom]、[TPopupOptions.center] 等命名工厂。 enum TPopupPlacement { - /// 自屏幕顶部滑入;`height` 与 `margin` 的 top/left/right 生效。 + /// 自顶部滑入;使用 [TPopupOptions.height]、[TPopupOptions.margin](top/left/right)。 top, - /// 自屏幕左侧滑入;`width` 与 margin 的 left/top/bottom 生效。 + /// 自左侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.margin](left/top/bottom)。 left, - /// 自屏幕右侧滑入;`width` 与 margin 的 right/top/bottom 生效。 + /// 自右侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.margin](right/top/bottom)。 right, - /// 自屏幕底部滑入;默认内置「取消 | 标题 | 确定」头部,`height`、`margin` 生效。 + /// 自底部滑入;默认内置头部;使用 [TPopupOptions.height]、[TPopupOptions.margin]。 bottom, - /// 屏幕居中弹出;默认面板下方圆形关闭图标;`closeBuilder: null` 隐藏关闭区。 + /// 屏幕居中;使用 [TPopupOptions.closeBuilder] 控制面板外下方关闭区。 center, } -/// bottom 头部完全自定义构建器签名。 +/// bottom 整行头部自定义构建器。 /// -/// - [close]:调用即关闭浮层(触发 [TPopupTrigger.programmatic])。 +/// * [context] 构建上下文 +/// * [close] 关闭浮层,触发源为 [TPopupTrigger.programmatic] typedef TPopupHeaderBuilder = Widget Function( BuildContext context, VoidCallback close, ); -/// bottom 槽位 / center 关闭区构建器签名。 +/// bottom 左右操作槽或 center 关闭区构建器。 /// -/// - [close]:调用即关闭浮层(触发 [TPopupTrigger.programmatic])。 +/// * [context] 构建上下文 +/// * [close] 关闭浮层,触发源为 [TPopupTrigger.programmatic] typedef TPopupSlotBuilder = Widget Function( BuildContext context, VoidCallback close, ); -/// **内置三段式头部**占位常量(bottom 默认): -/// 实际渲染为 `cancelBuilder | titleBuilder | confirmBuilder` 三段。 -/// -/// 直接调用返回空 Widget;库内通过 `identical` 判断是否走内置布局。 -/// 与 `headerBuilder: null`(无头部)语义不同。 -Widget kPopupDefaultHeader(BuildContext context, VoidCallback close) => +// 库内 sentinel:识别 builder「未传 = 内置默认」。业务三态见 [TPopupOptions]。 + +Widget _kPopupDefaultHeader(BuildContext context, VoidCallback close) => const SizedBox.shrink(); -/// **内置「取消」按钮**占位常量(bottom 默认左槽)。 -/// -/// 实际渲染为本地化「取消」文本,点击调用 [close]。 -/// 与 `cancelBuilder: null`(隐藏左槽)语义不同。 -Widget kPopupDefaultCancel(BuildContext context, VoidCallback close) => +Widget _kPopupDefaultCancel(BuildContext context, VoidCallback close) => const SizedBox.shrink(); -/// **内置「确定」按钮**占位常量(bottom 默认右槽)。 -/// -/// 实际渲染为本地化「确定」文本,点击调用 [close]。 -/// 与 `confirmBuilder: null`(隐藏右槽)语义不同。 -Widget kPopupDefaultConfirm(BuildContext context, VoidCallback close) => +Widget _kPopupDefaultConfirm(BuildContext context, VoidCallback close) => const SizedBox.shrink(); -/// **内置「关闭」图标**占位常量(center 默认关闭区)。 -/// -/// 实际渲染为圆形关闭图标,点击调用 [close]。 -/// 与 `closeBuilder: null`(隐藏关闭区)语义不同。 -Widget kPopupDefaultClose(BuildContext context, VoidCallback close) => +Widget _kPopupDefaultClose(BuildContext context, VoidCallback close) => const SizedBox.shrink(); -/// 是否为「使用内置三段式头部」占位(bottom)。 -bool isPopupDefaultHeader(TPopupHeaderBuilder? builder) => - identical(builder, kPopupDefaultHeader); +bool _isPopupDefaultHeader(TPopupHeaderBuilder? builder) => + identical(builder, _kPopupDefaultHeader); -/// 是否为「使用内置取消按钮」占位。 -bool isPopupDefaultCancel(TPopupSlotBuilder? builder) => - identical(builder, kPopupDefaultCancel); +bool _isPopupDefaultCancel(TPopupSlotBuilder? builder) => + identical(builder, _kPopupDefaultCancel); -/// 是否为「使用内置确定按钮」占位。 -bool isPopupDefaultConfirm(TPopupSlotBuilder? builder) => - identical(builder, kPopupDefaultConfirm); +bool _isPopupDefaultConfirm(TPopupSlotBuilder? builder) => + identical(builder, _kPopupDefaultConfirm); -/// 是否为「使用内置圆形关闭图标」占位(center)。 -bool isPopupDefaultClose(TPopupSlotBuilder? builder) => - identical(builder, kPopupDefaultClose); +bool _isPopupDefaultClose(TPopupSlotBuilder? builder) => + identical(builder, _kPopupDefaultClose); -/// 浮层被关闭时的触发来源,见 [TPopupOptions.onVisibleChange]。 +/// 浮层关闭或显隐变化时的触发来源。 /// -/// 注意:从库内的内置按钮触发关闭统一上报 [programmatic](自定义 builder 走自己的 `close`), -/// 「点击蒙层」仍单独上报 [overlay]。 +/// 作为 [TPopupVisibleChangeCallback] 的第二个参数,以及关闭流程中的语义标记。 +/// +/// 内置控件会映射为 [TPopupTrigger.overlay]、[TPopupTrigger.cancelBtn]、 +/// [TPopupTrigger.confirmBtn]、[TPopupTrigger.closeBtn]; +/// [TPopupHandle.close]、系统返回、自定义 builder 内调用 `close` 均为 +/// [TPopupTrigger.programmatic]。 enum TPopupTrigger { - /// 点击蒙层(且 [closeOnOverlayClick] 为 true)。 + /// 点击蒙层,且 [TPopupOptions.closeOnOverlayClick] 为 true。 overlay, - /// [TPopupHandle.close]、内置按钮、系统返回键等。 + /// 点击 bottom 内置「取消」按钮([TPopupOptions.cancelBuilder] 为内置默认时)。 + cancelBtn, + + /// 点击 bottom 内置「确定」按钮([TPopupOptions.confirmBuilder] 为内置默认时)。 + confirmBtn, + + /// 点击 center 内置关闭图标([TPopupOptions.closeBuilder] 为内置默认时)。 + closeBtn, + + /// [TPopupHandle.close]、系统返回键、自定义 builder 调用 `close` 等。 programmatic, } -/// 显隐变化:`onVisibleChange(visible, trigger)`。 +/// 浮层显隐变化回调。 +/// +/// * [visible] 为 true 表示打开,false 表示开始关闭 +/// * [trigger] 关闭来源,见 [TPopupTrigger];打开时为 [TPopupTrigger.programmatic] typedef TPopupVisibleChangeCallback = void Function( bool visible, TPopupTrigger trigger, diff --git a/tdesign-component/lib/tdesign_flutter.dart b/tdesign-component/lib/tdesign_flutter.dart index 5c42d90fa..462344551 100644 --- a/tdesign-component/lib/tdesign_flutter.dart +++ b/tdesign-component/lib/tdesign_flutter.dart @@ -54,7 +54,16 @@ export 'src/components/picker/t_picker_option.dart'; export 'src/components/picker/t_picker_value.dart'; export 'src/components/popover/t_popover.dart'; export 'src/components/popover/t_popover_widget.dart'; -export 'src/components/popup/t_popup.dart'; +export 'src/components/popup/t_popup.dart' + show + TPopup, + TPopupHandle, + TPopupOptions, + TPopupPlacement, + TPopupTrigger, + TPopupHeaderBuilder, + TPopupSlotBuilder, + TPopupVisibleChangeCallback; export 'src/components/progress/t_progress.dart'; export 'src/components/radio/t_radio.dart'; export 'src/components/rate/t_rate.dart'; diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart index b8496e1d3..9798aede2 100644 --- a/tdesign-component/test/t_popup_coverage_test.dart +++ b/tdesign-component/test/t_popup_coverage_test.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tdesign_flutter/src/components/popup/_popup_layout.dart'; +import 'package:tdesign_flutter/src/components/popup/t_popup.dart' + show PopupHeader, PopupLayout; import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'helpers/popup_test_helpers.dart'; @@ -327,14 +328,16 @@ void main() { }); test('assertPlacementParams 覆盖各 placement 的字段提示', () { + // bottom + width → 抛 FlutterError expect( () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, width: 200, ).assertPlacementParams(), - returnsNormally, + throwsA(isA()), ); + // top 默认配置 → 不抛 expect( () => TPopupOptions( child: const SizedBox(), @@ -342,6 +345,7 @@ void main() { ).assertPlacementParams(), returnsNormally, ); + // center 合法字段 → 不抛 expect( () => TPopupOptions( child: const SizedBox(), @@ -465,7 +469,7 @@ void main() { await tester.tap(find.text('取消')); await tester.pumpAndSettle(); - expect(hideTriggers.last, TPopupTrigger.programmatic); + expect(hideTriggers.last, TPopupTrigger.cancelBtn); await openPopup( tester, @@ -510,7 +514,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.byIcon(TIcons.close_circle)); await tester.pumpAndSettle(); - expect(hideTriggers.last, TPopupTrigger.programmatic); + expect(hideTriggers.last, TPopupTrigger.closeBtn); }); testWidgets('Popup 内嵌套 show 可再开一层且先关内层', (tester) async { @@ -714,4 +718,535 @@ void main() { ); }); }); + + // ============================================================ + // TPopupOptions 命名工厂(B 方案):编译期挡误用 + 运行期与默认构造等价 + // ============================================================ + group('TPopupOptions 命名工厂', () { + test('.bottom 生成的 options 等价于默认构造(含默认 sentinel)', () { + final factoryOpts = TPopupOptions.bottom( + child: const SizedBox(), + height: 300, + ); + final baseOpts = TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.bottom, + height: 300, + ); + expect(factoryOpts.placement, baseOpts.placement); + expect(factoryOpts.height, 300); + expect(factoryOpts.width, isNull); + expect(factoryOpts.usesDefaultHeader, isTrue); + expect(factoryOpts.usesDefaultCancel, isTrue); + expect(factoryOpts.usesDefaultConfirm, isTrue); + }); + + test('.center 生成 placement=center + 默认 closeBuilder', () { + final opts = TPopupOptions.center( + child: const SizedBox(), + width: 220, + height: 220, + ); + expect(opts.placement, TPopupPlacement.center); + expect(opts.width, 220); + expect(opts.height, 220); + expect(opts.usesDefaultClose, isTrue); + // center factory 不暴露 margin → 默认零 + expect(opts.margin, EdgeInsets.zero); + }); + + test('.top / .left / .right 生成对应 placement + 默认无头部', () { + final top = TPopupOptions.top(child: const SizedBox(), height: 100); + expect(top.placement, TPopupPlacement.top); + expect(top.height, 100); + expect(top.width, isNull); + + final left = TPopupOptions.left(child: const SizedBox(), width: 280); + expect(left.placement, TPopupPlacement.left); + expect(left.width, 280); + expect(left.height, isNull); + + final right = TPopupOptions.right(child: const SizedBox(), width: 280); + expect(right.placement, TPopupPlacement.right); + expect(right.width, 280); + expect(right.height, isNull); + }); + + test('factory 输出的合法配置在 assertPlacementParams 下零异常', () { + final variants = [ + TPopupOptions.bottom(child: const SizedBox(), height: 300), + TPopupOptions.center( + child: const SizedBox(), width: 220, height: 220), + TPopupOptions.top(child: const SizedBox(), height: 100), + TPopupOptions.left(child: const SizedBox(), width: 280), + TPopupOptions.right(child: const SizedBox(), width: 280), + ]; + for (final opts in variants) { + expect(opts.assertPlacementParams, returnsNormally, + reason: 'placement=${opts.placement}'); + } + }); + + test('factory 透传通用字段:duration / overlay / callbacks', () { + var visibleChanges = 0; + final opts = TPopupOptions.bottom( + child: const SizedBox(), + duration: const Duration(milliseconds: 500), + showOverlay: false, + overlayOpacity: 0.5, + destroyOnClose: true, + onVisibleChange: (_, __) => visibleChanges++, + ); + expect(opts.duration, const Duration(milliseconds: 500)); + expect(opts.showOverlay, isFalse); + expect(opts.overlayOpacity, 0.5); + expect(opts.destroyOnClose, isTrue); + opts.onVisibleChange?.call(false, TPopupTrigger.programmatic); + expect(visibleChanges, 1); + }); + }); + + // ============================================================ + // TPopupOptions.copyWith:处理 sentinel 三态 + // ============================================================ + group('TPopupOptions.copyWith', () { + test('不传字段 → 完全继承原值(含 sentinel 默认 builder)', () { + final base = TPopupOptions(child: const SizedBox()); + final next = base.copyWith(); + expect(next.placement, base.placement); + expect(next.height, base.height); + // sentinel 身份延续 + expect(next.usesDefaultHeader, isTrue); + expect(next.usesDefaultCancel, isTrue); + expect(next.usesDefaultConfirm, isTrue); + expect(next.usesDefaultClose, isTrue); + }); + + test('显式传 null → 把对应 builder 置为「隐藏」', () { + final base = TPopupOptions(child: const SizedBox()); + final next = base.copyWith(headerBuilder: null, cancelBuilder: null); + expect(next.headerBuilder, isNull); + expect(next.cancelBuilder, isNull); + // 未传的字段仍是 sentinel + expect(next.usesDefaultConfirm, isTrue); + expect(next.usesDefaultClose, isTrue); + }); + + test('传自定义 builder → 替换为自定义', () { + final base = TPopupOptions(child: const SizedBox()); + titleFn(BuildContext _) => const Text('t'); + final next = base.copyWith(titleBuilder: titleFn); + expect(next.titleBuilder, same(titleFn)); + // 其它继承 + expect(next.usesDefaultHeader, isTrue); + }); + + test('值类字段(width / height / radius / 颜色 / Color?)三态正确', () { + final base = TPopupOptions( + child: const SizedBox(), + width: 100, + height: 200, + radius: 8, + backgroundColor: const Color(0xFF000000), + ); + // 不传:继承 + expect(base.copyWith().width, 100); + // 显式 null:清空 + expect(base.copyWith(width: null).width, isNull); + expect(base.copyWith(backgroundColor: null).backgroundColor, isNull); + // 替换 + expect(base.copyWith(width: 50).width, 50); + }); + + test('回调字段同样支持三态', () { + hookA(bool _, TPopupTrigger __) {} + final base = TPopupOptions( + child: const SizedBox(), + onVisibleChange: hookA, + ); + expect(base.copyWith().onVisibleChange, same(hookA)); + expect(base.copyWith(onVisibleChange: null).onVisibleChange, isNull); + }); + }); + + // ============================================================ + // TPopupHandle.open(): 缓存 navigator,无 context 再开 + // ============================================================ + group('TPopupHandle.open() 无参复用缓存 navigator', () { + testWidgets('close 后 open() 不传 context 仍可重新打开', (tester) async { + late TPopupHandle handle; + await openPopup( + tester, + onPressed: () { + handle = TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + titleBuilder: (_) => const Text('再开测试'), + child: const SizedBox(height: 60)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(handle.isShowing, isTrue); + + handle.close(); + await tester.pumpAndSettle(); + expect(handle.isShowing, isFalse); + + // 关键:不传 context,使用首次缓存的 navigator + handle.open(); + await tester.pumpAndSettle(); + expect(handle.isShowing, isTrue); + expect(find.text('再开测试'), findsOneWidget); + }); + + testWidgets('已展示状态下 open() 重复调用无副作用', (tester) async { + late TPopupHandle handle; + await openPopup( + tester, + onPressed: () { + handle = TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + child: const SizedBox(height: 60)), + ); + }, + ); + await tester.pumpAndSettle(); + handle.open(); // 无参数重复 + await tester.pumpAndSettle(); + expect(handle.isShowing, isTrue); + // 只有一个 popup route + expect(find.byType(SizedBox), findsWidgets); + }); + }); + + // ============================================================ + // Header 三态行为 + 触发源精细化(覆盖 _popup_header.dart 短路 fix) + // ============================================================ + group('Popup Header 三态完整对照', () { + /// 拿到 popup 内 PopupHeader 实际占用的高度。 + /// - 无头部(headerBuilder=null 或三槽全 null):== 0 + /// - 默认 sentinel header:== PopupHeader.headerHeight(58) + /// - 自定义 headerBuilder:取决于自定义 widget 的高度 + double popupHeaderHeight(WidgetTester tester) { + return tester.getSize(find.byType(PopupHeader)).height; + } + + testWidgets('headerBuilder=null 同时传 title/cancel/confirmBuilder 也不渲染', + (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + headerBuilder: null, // 一票否决 + titleBuilder: (_) => const Text('应隐藏-标题'), + cancelBuilder: (_, close) => + GestureDetector(onTap: close, child: const Text('应隐藏-左')), + confirmBuilder: (_, close) => + GestureDetector(onTap: close, child: const Text('应隐藏-右')), + child: const SizedBox( + key: ValueKey('popup-child'), height: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('应隐藏-标题'), findsNothing); + expect(find.text('应隐藏-左'), findsNothing); + expect(find.text('应隐藏-右'), findsNothing); + // 也不应该有内置文案 + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsNothing); + }); + + testWidgets('headerBuilder=null → PopupHeader 高度精确为 0', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + headerBuilder: null, + child: const SizedBox(height: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(popupHeaderHeight(tester), 0); + }); + + testWidgets( + '默认 sentinel + 三槽全 null → PopupHeader 高度精确为 0(_popup_header.dart 修复回归)', + (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + cancelBuilder: null, + confirmBuilder: null, + child: const SizedBox(height: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(popupHeaderHeight(tester), 0); + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsNothing); + }); + + testWidgets('对照:默认 sentinel header(含标题)PopupHeader 高度 == 58', + (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + titleBuilder: (_) => const Text('标题'), + child: const SizedBox(height: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(popupHeaderHeight(tester), PopupHeader.headerHeight); + }); + + testWidgets('仅 cancelBuilder=null → 隐藏左槽,标题与右槽仍渲染', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + cancelBuilder: null, + titleBuilder: (_) => const Text('标题在'), + child: const SizedBox(height: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('标题在'), findsOneWidget); + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsOneWidget); + }); + + testWidgets('仅 confirmBuilder=null → 隐藏右槽,标题与左槽仍渲染', (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + confirmBuilder: null, + titleBuilder: (_) => const Text('标题在'), + child: const SizedBox(height: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('标题在'), findsOneWidget); + expect(find.text('取消'), findsOneWidget); + expect(find.text('确定'), findsNothing); + }); + + testWidgets('headerBuilder 自定义 → titleBuilder / cancelBuilder / confirmBuilder 全部被忽略', + (tester) async { + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 180, + headerBuilder: (_, __) => const Text('唯一头部'), + titleBuilder: (_) => const Text('不该出现-标题'), + cancelBuilder: (_, __) => const Text('不该出现-左'), + confirmBuilder: (_, __) => const Text('不该出现-右'), + child: const SizedBox(height: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + expect(find.text('唯一头部'), findsOneWidget); + expect(find.text('不该出现-标题'), findsNothing); + expect(find.text('不该出现-左'), findsNothing); + expect(find.text('不该出现-右'), findsNothing); + // 也找不到内置默认文案 + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsNothing); + }); + }); + + // ============================================================ + // 触发源精细化:sentinel vs 自定义 builder 的 close 上报区分 + // ============================================================ + group('Popup 触发源细分(sentinel vs 自定义 builder)', () { + testWidgets('内置 cancel 按钮点击 → cancelBtn(与 confirmBtn 区分)', + (tester) async { + TPopupTrigger? lastTrigger; + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + onVisibleChange: (visible, trigger) { + if (!visible) { + lastTrigger = trigger; + } + }, + child: const SizedBox(height: 60)), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('取消')); + await tester.pumpAndSettle(); + expect(lastTrigger, TPopupTrigger.cancelBtn); + }); + + testWidgets('自定义 cancelBuilder 内调 close → programmatic(不是 cancelBtn)', + (tester) async { + TPopupTrigger? lastTrigger; + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + cancelBuilder: (_, close) => GestureDetector( + onTap: close, + child: const Text('我自己的取消'), + ), + onVisibleChange: (visible, trigger) { + if (!visible) { + lastTrigger = trigger; + } + }, + child: const SizedBox(height: 60)), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('我自己的取消')); + await tester.pumpAndSettle(); + expect(lastTrigger, TPopupTrigger.programmatic); + }); + + testWidgets('自定义 confirmBuilder 内调 close → programmatic', + (tester) async { + TPopupTrigger? lastTrigger; + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + confirmBuilder: (_, close) => GestureDetector( + onTap: close, + child: const Text('我自己的确定'), + ), + onVisibleChange: (visible, trigger) { + if (!visible) { + lastTrigger = trigger; + } + }, + child: const SizedBox(height: 60)), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('我自己的确定')); + await tester.pumpAndSettle(); + expect(lastTrigger, TPopupTrigger.programmatic); + }); + + testWidgets('center 自定义 closeBuilder 内调 close → programmatic(不是 closeBtn)', + (tester) async { + TPopupTrigger? lastTrigger; + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.center, + width: 200, + height: 200, + closeBuilder: (_, close) => GestureDetector( + onTap: close, + child: const Text('我自己的关闭'), + ), + onVisibleChange: (visible, trigger) { + if (!visible) { + lastTrigger = trigger; + } + }, + child: const SizedBox(height: 80, width: 80)), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('我自己的关闭')); + await tester.pumpAndSettle(); + expect(lastTrigger, TPopupTrigger.programmatic); + }); + + testWidgets('headerBuilder 自定义内调 close → programmatic(无 sentinel 细分)', + (tester) async { + TPopupTrigger? lastTrigger; + await openPopup( + tester, + onPressed: () { + TPopup.show( + tester.element(find.text('open')), + options: TPopupOptions( + placement: TPopupPlacement.bottom, + height: 160, + headerBuilder: (_, close) => GestureDetector( + onTap: close, + child: const Text('整行自定义头部'), + ), + onVisibleChange: (visible, trigger) { + if (!visible) { + lastTrigger = trigger; + } + }, + child: const SizedBox(height: 60)), + ); + }, + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('整行自定义头部')); + await tester.pumpAndSettle(); + expect(lastTrigger, TPopupTrigger.programmatic); + }); + }); } diff --git a/tdesign-component/test/t_popup_layout_test.dart b/tdesign-component/test/t_popup_layout_test.dart index ea573084b..4735b9591 100644 --- a/tdesign-component/test/t_popup_layout_test.dart +++ b/tdesign-component/test/t_popup_layout_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tdesign_flutter/src/components/popup/_popup_layout.dart'; +import 'package:tdesign_flutter/src/components/popup/t_popup.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; void main() { diff --git a/tdesign-component/test/t_popup_options_test.dart b/tdesign-component/test/t_popup_options_test.dart index 121c045a6..a0cc64a15 100644 --- a/tdesign-component/test/t_popup_options_test.dart +++ b/tdesign-component/test/t_popup_options_test.dart @@ -7,9 +7,9 @@ void main() { test('默认 placement 为 bottom,4 个 builder 默认 sentinel', () { final options = TPopupOptions(child: const SizedBox()).normalized(); expect(options.placement, TPopupPlacement.bottom); - expect(isPopupDefaultHeader(options.headerBuilder), isTrue); - expect(isPopupDefaultCancel(options.cancelBuilder), isTrue); - expect(isPopupDefaultConfirm(options.confirmBuilder), isTrue); + expect(options.usesDefaultHeader, isTrue); + expect(options.usesDefaultCancel, isTrue); + expect(options.usesDefaultConfirm, isTrue); expect(options.titleBuilder, isNull); }); @@ -72,7 +72,7 @@ void main() { child: const SizedBox(), placement: TPopupPlacement.center, ).normalized(); - expect(isPopupDefaultClose(options.closeBuilder), isTrue); + expect(options.usesDefaultClose, isTrue); }); test('center closeBuilder=null 不显示关闭区', () { @@ -147,35 +147,35 @@ void main() { ); }); - test('assertPlacementParams 在 debug 模式不抛错', () { + test('assertPlacementParams debug 期对错位字段抛 FlutterError', () { + // left 不该有 height expect( () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.left, height: 100, - width: 200, - margin: const EdgeInsets.only(right: 10), ).assertPlacementParams(), - returnsNormally, + throwsA(isA()), ); + // center 不该有 titleBuilder(属于 bottom 头部字段) expect( () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, titleBuilder: (_) => const Text('x'), ).assertPlacementParams(), - returnsNormally, + throwsA(isA()), ); }); - test('assertPlacementParams 各 placement 的 margin 警告项', () { + test('assertPlacementParams 各 placement 的 margin 越界项也抛错', () { expect( () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, margin: const EdgeInsets.only(bottom: 10), ).assertPlacementParams(), - returnsNormally, + throwsA(isA()), ); expect( () => TPopupOptions( @@ -183,7 +183,7 @@ void main() { placement: TPopupPlacement.right, margin: const EdgeInsets.only(left: 10), ).assertPlacementParams(), - returnsNormally, + throwsA(isA()), ); expect( () => TPopupOptions( @@ -191,6 +191,30 @@ void main() { placement: TPopupPlacement.center, margin: const EdgeInsets.all(10), ).assertPlacementParams(), + throwsA(isA()), + ); + }); + + test('assertPlacementParams 合法配置不抛错', () { + // 各 placement 用对应合法字段 + expect(() => TPopupOptions(child: const SizedBox()).assertPlacementParams(), + returnsNormally); + expect( + () => TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.center, + width: 200, + height: 200, + ).assertPlacementParams(), + returnsNormally, + ); + expect( + () => TPopupOptions( + child: const SizedBox(), + placement: TPopupPlacement.left, + width: 280, + margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10), + ).assertPlacementParams(), returnsNormally, ); }); diff --git a/tdesign-component/test/t_popup_route_test.dart b/tdesign-component/test/t_popup_route_test.dart index 68dfbbc13..9bcd156ed 100644 --- a/tdesign-component/test/t_popup_route_test.dart +++ b/tdesign-component/test/t_popup_route_test.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tdesign_flutter/src/components/popup/_popup_route.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import 'helpers/popup_test_helpers.dart'; @@ -10,32 +9,7 @@ import 'helpers/popup_test_resource.dart'; void main() { tearDown(resetPopupTestResource); - group('TPopupNavigatorRoute', () { - testWidgets('buildPage 返回占位', (tester) async { - late TPopupNavigatorRoute route; - await tester.pumpWidget( - wrapPopupTest( - Builder( - builder: (context) { - route = TPopupNavigatorRoute( - options: TPopupOptions( - child: const SizedBox(), - placement: TPopupPlacement.bottom, - ).normalized(), - onCloseWithTrigger: (_, [__]) {}, - ); - return route.buildPage( - context, - kAlwaysCompleteAnimation, - kAlwaysCompleteAnimation, - ); - }, - ), - ), - ); - expect(find.byType(SizedBox), findsWidgets); - }); - + group('Popup 路由层行为(通过 TPopup.show 验证)', () { testWidgets('蒙层 ScrollNotification 被拦截', (tester) async { await openPopup( tester, diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart index 74b423995..ed45ea62b 100644 --- a/tdesign-component/test/t_popup_test.dart +++ b/tdesign-component/test/t_popup_test.dart @@ -661,22 +661,24 @@ void main() { }); group('TPopup 扩展场景', () { - testWidgets('top 忽略 title 仅 child', (tester) async { + testWidgets('top 不渲染头部、仅显示 child(用 .top factory)', (tester) async { await openPopup( tester, onPressed: () { TPopup.show( tester.element(find.text('open')), - options: TPopupOptions( - placement: TPopupPlacement.top, - height: 120, - titleBuilder: (_) => TText('顶部标题'), - child: const Text('内容')), + // .top factory 根本不暴露 titleBuilder:编译期就杜绝错位 + options: TPopupOptions.top( + height: 120, + child: const Text('内容'), + ), ); }, ); await tester.pumpAndSettle(); - expect(find.text('顶部标题'), findsNothing); + // 不应出现默认头部文案(zh 资源) + expect(find.text('取消'), findsNothing); + expect(find.text('确定'), findsNothing); expect(find.text('内容'), findsOneWidget); }); @@ -854,7 +856,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('确定')); await tester.pumpAndSettle(); - expect(hideTrigger, TPopupTrigger.programmatic); + expect(hideTrigger, TPopupTrigger.confirmBtn); }); testWidgets('destroyOnClose 路由关闭后可再次 show', (tester) async { From 9fe6695a19541f56369c591e3a68233e290a27e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Fri, 22 May 2026 17:35:25 +0800 Subject: [PATCH 10/27] docs(popup): update TPopup command to include TPopupHandle and improve documentation generation --- tdesign-component/demo_tool/all_build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tdesign-component/demo_tool/all_build.sh b/tdesign-component/demo_tool/all_build.sh index 67e6d86ad..1a8f8977d 100644 --- a/tdesign-component/demo_tool/all_build.sh +++ b/tdesign-component/demo_tool/all_build.sh @@ -128,8 +128,8 @@ dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/compo # overlay # popover dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popover" --name TPopover,TPopoverWidget --folder-name popover --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments -# popup(API 由源码 /// 生成 example/assets/api/popup_api.md;Handle 见 TPopup.show 返回值说明) -dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popup" --name TPopup,TPopupOptions,TPopupHeaderData,TPopupPlacement,TPopupTrigger --folder-name popup --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments +# popup +dart run tdesign_flutter_tools:main generate --folder "$PARENT_DIR/lib/src/components/popup" --name TPopup,TPopupOptions,TPopupHandle,TPopupPlacement,TPopupTrigger --folder-name popup --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments # refresh dart run tdesign_flutter_tools:main generate --file "$PARENT_DIR/lib/src/components/refresh/t_refresh_header.dart" --name TRefreshHeader --folder-name pull-down-refresh --output "$PARENT_DIR/example/assets/api/" --only-api --get-comments # swipecell From d0854fc7795f9ed869dda365a8f9dee601ff80b2 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 09:44:57 +0000 Subject: [PATCH 11/27] [autofix.ci] apply automated fixes --- .../example/assets/api/popup_api.md | 177 +++++++------- tdesign-site/src/indexes/README.md | 12 +- tdesign-site/src/popup/README.md | 222 ++++++++++-------- 3 files changed, 220 insertions(+), 191 deletions(-) diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md index 91cf76640..51d85dd6f 100644 --- a/tdesign-component/example/assets/api/popup_api.md +++ b/tdesign-component/example/assets/api/popup_api.md @@ -1,130 +1,147 @@ ## API ### TPopup #### 简介 -弹出层:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 +弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 - ## 怎么用 + 通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。 - **命令式(推荐)** — 先组配置,再 `show`,用返回的 [TPopupHandle] 关闭: + **示例** ```dart - final handle = TPopup( - options: TPopupOptions( - placement: TPopupPlacement.bottom, - title: '标题', + final handle = TPopup.show( + context, + options: TPopupOptions.bottom( + titleBuilder: (_) => const Text('标题'), child: MyPanel(), ), - ).show(context); - - // 关闭这一层(须保留 handle,不要用 context 猜栈顶) + ); handle.close(); + handle.open(); ``` - **声明式** — 包住子树,`initialVisible: true` 时首帧自动 [show];[build] 只渲染 [options.child]: + 配置项见 [TPopupOptions];方向见 [TPopupPlacement]。 - ```dart - TPopup( - options: TPopupOptions(child: body), - initialVisible: true, - ) - ``` +#### 工厂构造方法 - 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 - [TPopupOptions.normalized] 中裁掉)。 -#### 默认构造方法 +| 名称 | 说明 | +| --- | --- | +| TPopup._ | | -| 参数 | 类型 | 默认值 | 说明 | + +#### 静态方法 + +| 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | -| initialVisible | bool | false | 为 true 时,挂载后首帧自动调用 [show](仅声明式)。 | -| key | | - | | -| navigatorContext | BuildContext? | - | 指定使用哪个 [Navigator];默认 [show] 传入的 `context` 所在 Navigator。 | -| options | TPopupOptions | - | 浮层内容与行为配置,见 [TPopupOptions]。 | -| useRootNavigator | bool | false | 为 true 时使用根 [Navigator](嵌套导航场景)。 | +| show | | required BuildContext context, required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。 [context] 用于查找 [Navigator] 并展示浮层。 [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 [TPopupHandle.isShowing] 控制与查询。 同一 [Navigator] 上若已有展示中的浮层,重复调用会返回已有 handle(防连点)。 [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 | ``` ``` ### TPopupOptions #### 简介 -浮层配置:[TPopup] 构造与 [TPopup.show] 的唯一参数来源。 +[TPopup.show] 的配置对象。 + + ## 如何创建 + + | 场景 | 推荐用法 | + |------|----------| + | 弹出方向已知 | [TPopupOptions.bottom]、[TPopupOptions.center]、[TPopupOptions.top]、[TPopupOptions.left]、[TPopupOptions.right] | + | 方向由变量决定 | 默认构造并设置 [placement];Debug 下传错字段会抛 [FlutterError] | + + 命名工厂只暴露当前方向生效的字段(例如 [TPopupOptions.bottom] 无 [width] 参数)。 - ## 按 [placement] 用哪些字段 + ## 字段与 [TPopupPlacement] - | placement | 常用字段 | - |-----------|----------| - | [TPopupPlacement.bottom] | `title` / `cancel` / `confirm` / `headerBuilder`、`height`、`margin` | - | [TPopupPlacement.center] | `closeBuilder`、`width`、`height`(有下方关闭时) | - | [TPopupPlacement.top] / [left] / [right] | 主要 `child`、`margin`、方向对应 `width` 或 `height` | + | [TPopupPlacement] | 头部 / 关闭 | 尺寸 | + |-------------------|-------------|------| + | [TPopupPlacement.bottom] | [headerBuilder]、[titleBuilder]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] | + | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] | + | [TPopupPlacement.top] | — | [height]、[margin] | + | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[margin] | - 传给其它 placement 的 bottom / center 专用字段会在 [normalized] 里裁掉。 + ## Builder 三态([headerBuilder]、[cancelBuilder]、[confirmBuilder]、[closeBuilder]) - ## 三态占位(bottom / center) + | 传参方式 | 效果 | + |----------|------| + | 省略(使用默认值) | 渲染内置 UI | + | 显式 `null` | 隐藏该区域 | + | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 | - - **未传参数**:使用默认 UI(如默认取消/确定文案、默认关闭图标)。 - - **显式 `null`**:隐藏该槽位(如 `cancel: null` 隐藏左侧;`closeBuilder: null` 无关闭按钮)。 - - **自定义 Widget / Builder**:完全自定义该区域。 + [titleBuilder] 默认为 `null`,表示无标题文案。 - [TPopup.show] 内部会先 [normalized] 再绘制。 + 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。 #### 默认构造方法 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | -| autoCloseOnCancel | bool | true | 点击取消后是否自动关闭,默认 true。 | -| autoCloseOnConfirm | bool | true | 点击确定后是否自动关闭,默认 true。 | | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 | -| cancel | Widget? | kPopupActionDefault | bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。 | -| cancelBtn | String? | - | bottom 左侧按钮文案,覆盖默认「取消」。 | -| cancelBuilder | WidgetBuilder? | - | bottom 左侧按钮构建器,优先级高于 [cancel]。 | +| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 | | child | Widget | - | 浮层主体内容(必填)。 | -| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];bottom 与三边忽略。 | +| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 | | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 | -| confirm | Widget? | kPopupActionDefault | bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 | -| confirmBtn | String? | - | bottom 右侧按钮文案,覆盖默认「确定」。 | -| confirmBuilder | WidgetBuilder? | - | bottom 右侧按钮构建器,优先级高于 [confirm]。 | -| destroyOnClose | bool | false | 为 true 时 Popup 路由 maintainState 为 false,关闭后不保留路由内 State。 | -| duration | Duration | const Duration(milliseconds: 240) | 打开与关闭动画时长(一致)。 | -| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader];自定义见 [TPopupHeaderBuilder]。 | -| height | double? | - | 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 | -| margin | EdgeInsets | EdgeInsets.zero | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 | -| onCancel | VoidCallback? | - | 点击 bottom 左侧按钮回调。 | -| onClose | VoidCallback? | - | 开始关闭时回调。 | -| onCloseBtn | VoidCallback? | - | center 点击关闭控件前的回调。 | -| onClosed | VoidCallback? | - | 关闭动画结束且路由移除后回调。 | -| onConfirm | VoidCallback? | - | 点击 bottom 右侧按钮回调。 | -| onOpen | VoidCallback? | - | 开始打开时回调(路由入栈)。 | -| onOpened | VoidCallback? | - | 打开动画结束后回调。 | -| onOverlayClick | VoidCallback? | - | 点击蒙层时回调(在是否关闭判断之前)。 | -| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化及触发来源。 | +| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 | +| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 | +| duration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 | +| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 | +| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 | +| margin | EdgeInsets | EdgeInsets.zero | 外边距;生效边取决于 [placement]。 | +| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 | +| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 | +| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 | +| onOpened | VoidCallback? | - | 打开动画结束。 | +| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 | +| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 | | overlayColor | Color? | - | 蒙层颜色,默认 black54。 | | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 | | placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 | | preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 | | radius | double? | - | 内容区圆角,默认主题大圆角。 | | showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 | -| title | String? | - | bottom 操作栏中间标题文案。 | -| titleAlignLeft | bool | false | bottom 仅标题行时是否左对齐,默认居中。 | -| titleWidget | Widget? | - | bottom 操作栏中间标题组件,优先级高于 [title]。 | -| width | double? | - | 宽度;对 left、right、center 生效。 | +| titleBuilder | WidgetBuilder? | - | bottom 标题;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 | +| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 | -#### 静态方法 +#### 工厂构造方法 -| 名称 | 返回类型 | 参数 | 说明 | -| --- | --- | --- | --- | -| isActionDefault | | required Widget? action, | | +| 名称 | 说明 | +| --- | --- | +| TPopupOptions.bottom | 创建 [TPopupPlacement.bottom] 配置。 + + 固定 [placement] 为 [TPopupPlacement.bottom];默认带内置头部。 + 蒙层、动画、生命周期等字段语义见同名成员文档。 | +| TPopupOptions.center | 创建 [TPopupPlacement.center] 配置。 + + 固定 [placement] 为 [TPopupPlacement.center];默认面板外下方圆形关闭按钮。 | +| TPopupOptions.left | 创建 [TPopupPlacement.left] 配置。 + + 固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。 | +| TPopupOptions.right | 创建 [TPopupPlacement.right] 配置。 + + 固定 [placement] 为 [TPopupPlacement.right];未传 [width] 时布局默认宽度 280。 | +| TPopupOptions.top | 创建 [TPopupPlacement.top] 配置。 + + 固定 [placement] 为 [TPopupPlacement.top];无内置头部。 | ``` ``` -### TPopupHeaderData +### TPopupHandle #### 简介 -传给自定义 [TPopupOptions.headerBuilder] 的标题栏数据(库内已组装好各槽 Widget)。 -#### 默认构造方法 +[TPopup.show] 的返回值,用于控制同一份 [TPopupOptions] 的多次打开与关闭。 -| 参数 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| cancel | Widget? | - | 左侧区域 Widget(null 表示该侧已隐藏)。 | -| confirm | Widget? | - | 右侧区域 Widget(null 表示该侧已隐藏)。 | -| onCancel | VoidCallback? | - | 点击左侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnCancel] 决定)。 | -| onConfirm | VoidCallback? | - | 点击右侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnConfirm] 决定)。 | -| title | Widget? | - | 中间标题(可为 null)。 | + **示例** + + ```dart + final handle = TPopup.show( + context, + options: TPopupOptions.bottom(child: panel), + ); + handle.close(); + handle.open(); // 可省略 context,复用已缓存的 Navigator + ``` + +#### 工厂构造方法 + +| 名称 | 说明 | +| --- | --- | +| TPopupHandle._ | | diff --git a/tdesign-site/src/indexes/README.md b/tdesign-site/src/indexes/README.md index 95475bf3f..2d1d214f7 100644 --- a/tdesign-site/src/indexes/README.md +++ b/tdesign-site/src/indexes/README.md @@ -38,8 +38,7 @@ Widget _buildSimple(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( @@ -77,8 +76,7 @@ Widget _buildSimple(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( @@ -119,8 +117,7 @@ Widget _buildOther(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( @@ -159,8 +156,7 @@ Widget _buildOther(BuildContext context) { onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, margin: EdgeInsets.only(top: renderBox?.size.height ?? 0), child: TIndexes( diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md index aee503ea3..e2857d808 100644 --- a/tdesign-site/src/popup/README.md +++ b/tdesign-site/src/popup/README.md @@ -35,8 +35,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.top, + options: TPopupOptions.top( height: 240, onOpen: () => print('open'), onOpened: () => print('opened'), @@ -67,8 +66,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.left, + options: TPopupOptions.left( width: 280, child: Container( color: TTheme.of(context).bgColorContainer, @@ -96,8 +94,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeBuilder: null, child: Container( decoration: BoxDecoration( @@ -131,8 +128,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 240, headerBuilder: null, child: Container( @@ -162,8 +158,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.right, + options: TPopupOptions.right( width: 280, child: Container( color: TTheme.of(context).bgColorContainer, @@ -192,11 +187,9 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, - title: '标题文字', - onConfirm: () => TToast.showText('确定', context: context), + titleBuilder: (_) => TText('标题文字'), child: Container(height: 200)), ); }, @@ -221,15 +214,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, - cancel: TText( + cancelBuilder: (_, __) => TText( '关闭', textColor: TTheme.of(context).textColorSecondary, font: TTheme.of(context).fontBodyLarge, ), - titleWidget: Row( + titleBuilder: (_) => Row( mainAxisSize: MainAxisSize.min, children: [ Icon(TIcons.info_circle, @@ -242,7 +234,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ), ], ), - confirm: TText( + confirmBuilder: (_, __) => TText( '完成', textColor: TTheme.of(context).brandNormalColor, font: TTheme.of(context).fontTitleMedium, @@ -272,8 +264,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeOnOverlayClick: false, width: 240, height: 240, @@ -309,8 +300,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.center, + options: TPopupOptions.center( closeOnOverlayClick: true, width: 240, height: 200, @@ -351,8 +341,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; TPopupHandle? outerHandle; outerHandle = TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 360, headerBuilder: null, child: Builder( @@ -375,10 +364,9 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( innerContext, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, - title: '内层标题', + titleBuilder: (_) => const TText('内层标题'), child: Container( height: 160, color: TTheme.of(innerContext) @@ -425,11 +413,10 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 320, margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - title: '日历式留白', + titleBuilder: (_) => TText('日历式留白'), child: Container( height: 240, color: TTheme.of(context).bgColorContainer, @@ -457,12 +444,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 280, showOverlay: false, // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) - title: '无蒙层', + titleBuilder: (_) => const TText('无蒙层'), child: Container( height: 200, color: TTheme.of(context).bgColorContainer, @@ -490,8 +476,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 260, onOverlayClick: () => TToast.showText('点击蒙层', context: context), child: Container( @@ -521,8 +506,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; onTap: () { TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, + options: TPopupOptions.bottom( height: 240, duration: const Duration(milliseconds: 600), child: Container( @@ -541,118 +525,150 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; ## API ### TPopup #### 简介 -弹出层命名空间:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。仅提供静态入口 [show];返回的 [TPopupHandle] 控制本次浮层的显隐。 +弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。 - ## 怎么用 + 通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。 + + **示例** ```dart final handle = TPopup.show( context, - options: TPopupOptions( - placement: TPopupPlacement.bottom, - title: '标题', + options: TPopupOptions.bottom( + titleBuilder: (_) => const Text('标题'), child: MyPanel(), ), ); - - // 关闭后再开(同一 handle) handle.close(); - handle.open(context); + handle.open(); ``` - 字段说明见 [TPopupOptions];按 [TPopupPlacement] 只有部分参数生效(无效参数会在 - [TPopupOptions.normalized] 中裁掉)。 + 配置项见 [TPopupOptions];方向见 [TPopupPlacement]。 + +#### 工厂构造方法 + +| 名称 | 说明 | +| --- | --- | +| TPopup._ | | + #### 静态方法 | 名称 | 返回类型 | 参数 | 说明 | | --- | --- | --- | --- | -| show | TPopupHandle | `BuildContext context`, `{required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator = false}` | 命令式打开浮层并压入独立路由,返回 [TPopupHandle] 控制显隐。 | +| show | | required BuildContext context, required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。 [context] 用于查找 [Navigator] 并展示浮层。 [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 [TPopupHandle.isShowing] 控制与查询。 同一 [Navigator] 上若已有展示中的浮层,重复调用会返回已有 handle(防连点)。 [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 | + +``` +``` ### TPopupOptions #### 简介 -浮层配置:[TPopup] 构造与 [TPopup.show] 的唯一参数来源。 +[TPopup.show] 的配置对象。 - ## 按 [placement] 用哪些字段 + ## 如何创建 - | placement | 常用字段 | - |-----------|----------| - | [TPopupPlacement.bottom] | `title` / `cancel` / `confirm` / `headerBuilder`、`height`、`margin` | - | [TPopupPlacement.center] | `closeBuilder`、`width`、`height`(有下方关闭时) | - | [TPopupPlacement.top] / [left] / [right] | 主要 `child`、`margin`、方向对应 `width` 或 `height` | + | 场景 | 推荐用法 | + |------|----------| + | 弹出方向已知 | [TPopupOptions.bottom]、[TPopupOptions.center]、[TPopupOptions.top]、[TPopupOptions.left]、[TPopupOptions.right] | + | 方向由变量决定 | 默认构造并设置 [placement];Debug 下传错字段会抛 [FlutterError] | - 传给其它 placement 的 bottom / center 专用字段会在 [normalized] 里裁掉。 + 命名工厂只暴露当前方向生效的字段(例如 [TPopupOptions.bottom] 无 [width] 参数)。 - ## 三态占位(bottom / center) + ## 字段与 [TPopupPlacement] - - **未传参数**:使用默认 UI(如默认取消/确定文案、默认关闭图标)。 - - **显式 `null`**:隐藏该槽位(如 `cancel: null` 隐藏左侧;`closeBuilder: null` 无关闭按钮)。 - - **自定义 Widget / Builder**:完全自定义该区域。 + | [TPopupPlacement] | 头部 / 关闭 | 尺寸 | + |-------------------|-------------|------| + | [TPopupPlacement.bottom] | [headerBuilder]、[titleBuilder]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] | + | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] | + | [TPopupPlacement.top] | — | [height]、[margin] | + | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[margin] | - [TPopup.show] 内部会先 [normalized] 再绘制。 + ## Builder 三态([headerBuilder]、[cancelBuilder]、[confirmBuilder]、[closeBuilder]) + + | 传参方式 | 效果 | + |----------|------| + | 省略(使用默认值) | 渲染内置 UI | + | 显式 `null` | 隐藏该区域 | + | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 | + + [titleBuilder] 默认为 `null`,表示无标题文案。 + + 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。 #### 默认构造方法 | 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | -| autoCloseOnCancel | bool | true | 点击取消后是否自动关闭,默认 true。 | -| autoCloseOnConfirm | bool | true | 点击确定后是否自动关闭,默认 true。 | | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 | -| cancel | Widget? | kPopupActionDefault | bottom 左侧按钮;默认 [kPopupActionDefault] 表示默认文案,传 null 隐藏左侧。 | -| cancelBtn | String? | - | bottom 左侧按钮文案,覆盖默认「取消」。 | -| cancelBuilder | WidgetBuilder? | - | bottom 左侧按钮构建器,优先级高于 [cancel]。 | +| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 | | child | Widget | - | 浮层主体内容(必填)。 | -| closeBuilder | TPopupCloseBuilder? | kPopupDefaultClose | center 关闭区:`null` 不显示;未传则用 [kPopupDefaultClose];bottom 与三边忽略。 | +| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 | | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 | -| confirm | Widget? | kPopupActionDefault | bottom 右侧按钮;默认 [kPopupActionDefault],传 null 隐藏右侧。 | -| confirmBtn | String? | - | bottom 右侧按钮文案,覆盖默认「确定」。 | -| confirmBuilder | WidgetBuilder? | - | bottom 右侧按钮构建器,优先级高于 [confirm]。 | -| destroyOnClose | bool | false | 为 true 时 Popup 路由 maintainState 为 false,关闭后不保留路由内 State。 | -| duration | Duration | const Duration(milliseconds: 240) | 打开与关闭动画时长(一致)。 | -| headerBuilder | TPopupHeaderBuilder? | kPopupDefaultHeader | bottom 头部:`null` 无头部;未传则用 [kPopupDefaultHeader];自定义见 [TPopupHeaderBuilder]。 | -| height | double? | - | 高度;对 top、bottom 生效;center 且下方关闭时约束内容区高度。 | -| margin | EdgeInsets | EdgeInsets.zero | 外边距;center 忽略。bottom 的 top 可用来做日历式距顶留白。 | -| onCancel | VoidCallback? | - | 点击 bottom 左侧按钮回调。 | -| onClose | VoidCallback? | - | 开始关闭时回调。 | -| onCloseBtn | VoidCallback? | - | center 点击关闭控件前的回调。 | -| onClosed | VoidCallback? | - | 关闭动画结束且路由移除后回调。 | -| onConfirm | VoidCallback? | - | 点击 bottom 右侧按钮回调。 | -| onOpen | VoidCallback? | - | 开始打开时回调(路由入栈)。 | -| onOpened | VoidCallback? | - | 打开动画结束后回调。 | -| onOverlayClick | VoidCallback? | - | 点击蒙层时回调(在是否关闭判断之前)。 | -| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化及触发来源。 | +| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 | +| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 | +| duration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 | +| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 | +| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 | +| margin | EdgeInsets | EdgeInsets.zero | 外边距;生效边取决于 [placement]。 | +| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 | +| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 | +| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 | +| onOpened | VoidCallback? | - | 打开动画结束。 | +| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 | +| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 | | overlayColor | Color? | - | 蒙层颜色,默认 black54。 | | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 | | placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 | | preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 | | radius | double? | - | 内容区圆角,默认主题大圆角。 | | showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 | -| title | String? | - | bottom 操作栏中间标题文案。 | -| titleAlignLeft | bool | false | bottom 仅标题行时是否左对齐,默认居中。 | -| titleWidget | Widget? | - | bottom 操作栏中间标题组件,优先级高于 [title]。 | -| width | double? | - | 宽度;对 left、right、center 生效。 | +| titleBuilder | WidgetBuilder? | - | bottom 标题;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 | +| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 | -#### 静态方法 +#### 工厂构造方法 -| 名称 | 返回类型 | 参数 | 说明 | -| --- | --- | --- | --- | -| isActionDefault | | required Widget? action, | | +| 名称 | 说明 | +| --- | --- | +| TPopupOptions.bottom | 创建 [TPopupPlacement.bottom] 配置。 + + 固定 [placement] 为 [TPopupPlacement.bottom];默认带内置头部。 + 蒙层、动画、生命周期等字段语义见同名成员文档。 | +| TPopupOptions.center | 创建 [TPopupPlacement.center] 配置。 + + 固定 [placement] 为 [TPopupPlacement.center];默认面板外下方圆形关闭按钮。 | +| TPopupOptions.left | 创建 [TPopupPlacement.left] 配置。 + + 固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。 | +| TPopupOptions.right | 创建 [TPopupPlacement.right] 配置。 + + 固定 [placement] 为 [TPopupPlacement.right];未传 [width] 时布局默认宽度 280。 | +| TPopupOptions.top | 创建 [TPopupPlacement.top] 配置。 + + 固定 [placement] 为 [TPopupPlacement.top];无内置头部。 | ``` ``` -### TPopupHeaderData +### TPopupHandle #### 简介 -传给自定义 [TPopupOptions.headerBuilder] 的标题栏数据(库内已组装好各槽 Widget)。 -#### 默认构造方法 +[TPopup.show] 的返回值,用于控制同一份 [TPopupOptions] 的多次打开与关闭。 -| 参数 | 类型 | 默认值 | 说明 | -| --- | --- | --- | --- | -| cancel | Widget? | - | 左侧区域 Widget(null 表示该侧已隐藏)。 | -| confirm | Widget? | - | 右侧区域 Widget(null 表示该侧已隐藏)。 | -| onCancel | VoidCallback? | - | 点击左侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnCancel] 决定)。 | -| onConfirm | VoidCallback? | - | 点击右侧区域时回调(是否关闭由 [TPopupOptions.autoCloseOnConfirm] 决定)。 | -| title | Widget? | - | 中间标题(可为 null)。 | + **示例** + + ```dart + final handle = TPopup.show( + context, + options: TPopupOptions.bottom(child: panel), + ); + handle.close(); + handle.open(); // 可省略 context,复用已缓存的 Navigator + ``` + +#### 工厂构造方法 + +| 名称 | 说明 | +| --- | --- | +| TPopupHandle._ | | \ No newline at end of file From b6e6206a6373ed437e18962f33bb842a5e64267f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Mon, 25 May 2026 11:43:48 +0800 Subject: [PATCH 12/27] refactor(popup): replace titleBuilder with titleWidget in TPopup options and components --- .../lib/component_test/popup_test.dart | 2 +- .../example/lib/page/t_popup_page.dart | 14 +++---- .../components/popup/_popup_center_close.dart | 11 +++-- .../src/components/popup/_popup_header.dart | 19 +++++---- .../lib/src/components/popup/t_popup.dart | 2 +- .../src/components/popup/t_popup_options.dart | 34 +++++++-------- .../src/components/popup/t_popup_types.dart | 6 +-- .../test/t_popup_coverage_test.dart | 42 +++++++++---------- .../test/t_popup_options_test.dart | 16 +++---- tdesign-component/test/t_popup_test.dart | 12 +++--- 10 files changed, 79 insertions(+), 79 deletions(-) diff --git a/tdesign-component/example/lib/component_test/popup_test.dart b/tdesign-component/example/lib/component_test/popup_test.dart index 475f820d0..b0f9bea3c 100644 --- a/tdesign-component/example/lib/component_test/popup_test.dart +++ b/tdesign-component/example/lib/component_test/popup_test.dart @@ -28,7 +28,7 @@ class _TestPageState extends State { TPopup.show( context, options: TPopupOptions.bottom( - titleBuilder: (_) => TText('title'), + titleWidget: TText('title'), radius: 20, backgroundColor: const Color(0xFFFAFFFC), child: Container( diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart index 11da7ae47..93554dc9a 100644 --- a/tdesign-component/example/lib/page/t_popup_page.dart +++ b/tdesign-component/example/lib/page/t_popup_page.dart @@ -102,7 +102,7 @@ class TPopupPage extends StatelessWidget { context, options: TPopupOptions.bottom( height: 280, - titleBuilder: (_) => TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), + titleWidget: TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), cancelBuilder: (_, __) => TText( '点这里确认!', textColor: TTheme.of(context).brandNormalColor, @@ -181,7 +181,7 @@ class TPopupPage extends StatelessWidget { options: TPopupOptions.bottom( height: 280, radius: 6, - titleBuilder: (_) => TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), + titleWidget: TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), cancelBuilder: (_, __) => TText( '点这里确认!', textColor: TTheme.of(context).brandNormalColor, @@ -431,7 +431,7 @@ class TPopupPage extends StatelessWidget { innerContext, options: TPopupOptions.bottom( height: 280, - titleBuilder: (_) => const TText('内层标题'), + titleWidget: const TText('内层标题'), child: Container( height: 160, color: TTheme.of(innerContext) @@ -472,7 +472,7 @@ class TPopupPage extends StatelessWidget { context, options: TPopupOptions.bottom( height: 280, - titleBuilder: (_) => TText('标题文字'), + titleWidget: TText('标题文字'), child: Container(height: 200)), ); }, @@ -497,7 +497,7 @@ class TPopupPage extends StatelessWidget { textColor: TTheme.of(context).textColorSecondary, font: TTheme.of(context).fontBodyLarge, ), - titleBuilder: (_) => Row( + titleWidget: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(TIcons.info_circle, @@ -600,7 +600,7 @@ class TPopupPage extends StatelessWidget { options: TPopupOptions.bottom( height: 320, margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - titleBuilder: (_) => TText('日历式留白'), + titleWidget: TText('日历式留白'), child: Container( height: 240, color: TTheme.of(context).bgColorContainer, @@ -625,7 +625,7 @@ class TPopupPage extends StatelessWidget { height: 280, showOverlay: false, // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口) - titleBuilder: (_) => const TText('无蒙层'), + titleWidget: const TText('无蒙层'), child: Container( height: 200, color: TTheme.of(context).bgColorContainer, diff --git a/tdesign-component/lib/src/components/popup/_popup_center_close.dart b/tdesign-component/lib/src/components/popup/_popup_center_close.dart index 2a795cc44..cf53a4693 100644 --- a/tdesign-component/lib/src/components/popup/_popup_center_close.dart +++ b/tdesign-component/lib/src/components/popup/_popup_center_close.dart @@ -1,10 +1,10 @@ part of 't_popup.dart'; -/// center 面板外关闭控件:默认图标 → [TPopupTrigger.closeBtn];自定义 → [TPopupTrigger.programmatic]。 +/// center 面板外关闭控件:默认图标与自定义关闭槽位都上报 [TPopupTrigger.closeBtn]。 Widget buildPopupCenterCloseControl({ required BuildContext context, required TPopupOptions options, - required VoidCallback onProgrammaticClose, + required VoidCallback onCloseSlotTap, required void Function(TPopupTrigger trigger) onCloseWithTrigger, }) { if (_isPopupDefaultClose(options.closeBuilder)) { @@ -19,7 +19,7 @@ Widget buildPopupCenterCloseControl({ onPressed: () => onCloseWithTrigger(TPopupTrigger.closeBtn), ); } - return options.closeBuilder!(context, onProgrammaticClose); + return options.closeBuilder!(context, onCloseSlotTap); } /// center 布局:内容面板 + 外置关闭区。 @@ -46,13 +46,12 @@ class PopupCenterUnderClose extends StatelessWidget { ); } - void onProgrammaticClose() => - onCloseWithTrigger(TPopupTrigger.programmatic); + void onCloseSlotTap() => onCloseWithTrigger(TPopupTrigger.closeBtn); final closeControl = buildPopupCenterCloseControl( context: context, options: options, - onProgrammaticClose: onProgrammaticClose, + onCloseSlotTap: onCloseSlotTap, onCloseWithTrigger: onCloseWithTrigger, ); diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart index d69c280a9..187973a9f 100644 --- a/tdesign-component/lib/src/components/popup/_popup_header.dart +++ b/tdesign-component/lib/src/components/popup/_popup_header.dart @@ -31,7 +31,6 @@ class PopupHeader extends StatelessWidget { height: headerHeight, child: _DefaultHeader( options: options, - close: _close, onCloseWithTrigger: onCloseWithTrigger, ), ); @@ -41,15 +40,11 @@ class PopupHeader extends StatelessWidget { class _DefaultHeader extends StatelessWidget { const _DefaultHeader({ required this.options, - required this.close, required this.onCloseWithTrigger, }); final TPopupOptions options; - /// 给「自定义 cancel/confirm builder」用的 close 回调(上报 [TPopupTrigger.programmatic])。 - final VoidCallback close; - /// 给「内置 sentinel cancel/confirm 按钮」用的 close 入口, /// 按钮自己传 [TPopupTrigger.cancelBtn] / [TPopupTrigger.confirmBtn]。 final void Function(TPopupTrigger trigger) onCloseWithTrigger; @@ -60,7 +55,7 @@ class _DefaultHeader extends StatelessWidget { final showCancel = options.cancelBuilder != null; final showConfirm = options.confirmBuilder != null; - final title = options.titleBuilder?.call(context); + final title = options.titleWidget; return Row( children: [ @@ -98,7 +93,7 @@ class _DefaultHeader extends StatelessWidget { } Widget _titleWrap(BuildContext context, TThemeData theme, Widget child) { - // 标题由用户 builder 决定样式,这里只做布局约束。 + // 标题内容由用户插槽决定样式,这里只做布局约束。 return DefaultTextStyle.merge( style: TextStyle( color: theme.textColorPrimary, @@ -122,7 +117,10 @@ class _DefaultHeader extends StatelessWidget { ), ); } - return options.cancelBuilder!(context, close); + return options.cancelBuilder!( + context, + () => onCloseWithTrigger(TPopupTrigger.cancelBtn), + ); } Widget _buildConfirm(BuildContext context, TThemeData theme) { @@ -137,7 +135,10 @@ class _DefaultHeader extends StatelessWidget { ), ); } - return options.confirmBuilder!(context, close); + return options.confirmBuilder!( + context, + () => onCloseWithTrigger(TPopupTrigger.confirmBtn), + ); } } diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart index b0b3134ef..1702716ac 100644 --- a/tdesign-component/lib/src/components/popup/t_popup.dart +++ b/tdesign-component/lib/src/components/popup/t_popup.dart @@ -40,7 +40,7 @@ part 't_popup_types.dart'; /// final handle = TPopup.show( /// context, /// options: TPopupOptions.bottom( -/// titleBuilder: (_) => const Text('标题'), +/// titleWidget: const Text('标题'), /// child: MyPanel(), /// ), /// ); diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart index b833c7ab2..3d31275bc 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_options.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart @@ -18,7 +18,7 @@ const Object _unset = Object(); /// /// | [TPopupPlacement] | 头部 / 关闭 | 尺寸 | /// |-------------------|-------------|------| -/// | [TPopupPlacement.bottom] | [headerBuilder]、[titleBuilder]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] | +/// | [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] | /// | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] | /// | [TPopupPlacement.top] | — | [height]、[margin] | /// | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[margin] | @@ -31,7 +31,7 @@ const Object _unset = Object(); /// | 显式 `null` | 隐藏该区域 | /// | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 | /// -/// [titleBuilder] 默认为 `null`,表示无标题文案。 +/// [titleWidget] 默认为 `null`,表示无标题文案。 /// /// 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。 class TPopupOptions { @@ -54,7 +54,7 @@ class TPopupOptions { this.destroyOnClose = false, this.duration = const Duration(milliseconds: 240), this.headerBuilder = _kPopupDefaultHeader, - this.titleBuilder, + this.titleWidget, this.cancelBuilder = _kPopupDefaultCancel, this.confirmBuilder = _kPopupDefaultConfirm, this.closeBuilder = _kPopupDefaultClose, @@ -75,7 +75,7 @@ class TPopupOptions { double? height, EdgeInsets margin = EdgeInsets.zero, TPopupHeaderBuilder? headerBuilder = _kPopupDefaultHeader, - WidgetBuilder? titleBuilder, + Widget? titleWidget, TPopupSlotBuilder? cancelBuilder = _kPopupDefaultCancel, TPopupSlotBuilder? confirmBuilder = _kPopupDefaultConfirm, double? radius, @@ -100,7 +100,7 @@ class TPopupOptions { height: height, margin: margin, headerBuilder: headerBuilder, - titleBuilder: titleBuilder, + titleWidget: titleWidget, cancelBuilder: cancelBuilder, confirmBuilder: confirmBuilder, radius: radius, @@ -349,11 +349,11 @@ class TPopupOptions { /// bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 /// - /// 自定义时忽略 [titleBuilder]、[cancelBuilder]、[confirmBuilder]。 + /// 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 final TPopupHeaderBuilder? headerBuilder; - /// bottom 标题;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 - final WidgetBuilder? titleBuilder; + /// bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 + final Widget? titleWidget; /// bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 /// @@ -390,7 +390,7 @@ class TPopupOptions { /// 返回配置副本。 /// - /// 未传入的字段保持原值;对 builder 显式传入 `null` 表示隐藏该区域。 + /// 未传入的字段保持原值;对头部/关闭相关插槽显式传入 `null` 表示隐藏该区域。 TPopupOptions copyWith({ Widget? child, TPopupPlacement? placement, @@ -407,7 +407,7 @@ class TPopupOptions { bool? destroyOnClose, Duration? duration, Object? headerBuilder = _unset, - Object? titleBuilder = _unset, + Object? titleWidget = _unset, Object? cancelBuilder = _unset, Object? confirmBuilder = _unset, Object? closeBuilder = _unset, @@ -442,9 +442,9 @@ class TPopupOptions { headerBuilder: identical(headerBuilder, _unset) ? this.headerBuilder : headerBuilder as TPopupHeaderBuilder?, - titleBuilder: identical(titleBuilder, _unset) - ? this.titleBuilder - : titleBuilder as WidgetBuilder?, + titleWidget: identical(titleWidget, _unset) + ? this.titleWidget + : titleWidget as Widget?, cancelBuilder: identical(cancelBuilder, _unset) ? this.cancelBuilder : cancelBuilder as TPopupSlotBuilder?, @@ -493,7 +493,7 @@ class TPopupOptions { destroyOnClose: destroyOnClose, duration: duration, headerBuilder: isBottom ? headerBuilder : null, - titleBuilder: isBottom ? titleBuilder : null, + titleWidget: isBottom ? titleWidget : null, cancelBuilder: isBottom ? cancelBuilder : null, confirmBuilder: isBottom ? confirmBuilder : null, closeBuilder: isCenter ? closeBuilder : null, @@ -548,7 +548,7 @@ class TPopupOptions { } return cancelBuilder != null || confirmBuilder != null || - titleBuilder != null; + titleWidget != null; } /// {@nodoc} @@ -600,11 +600,11 @@ class TPopupOptions { break; } final hasBottomHeaderCustom = !_isPopupDefaultHeader(headerBuilder) || - titleBuilder != null || + titleWidget != null || !_isPopupDefaultCancel(cancelBuilder) || !_isPopupDefaultConfirm(confirmBuilder); if (placement != TPopupPlacement.bottom && hasBottomHeaderCustom) { - return 'header/title/cancel/confirmBuilder only apply to ' + return 'header/titleWidget/cancel/confirmBuilder only apply to ' 'placement=bottom (got placement=$placement).'; } if (placement != TPopupPlacement.center && diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart index 73ea6da9c..851a2ce22 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_types.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart @@ -33,7 +33,7 @@ typedef TPopupHeaderBuilder = Widget Function( /// bottom 左右操作槽或 center 关闭区构建器。 /// /// * [context] 构建上下文 -/// * [close] 关闭浮层,触发源为 [TPopupTrigger.programmatic] +/// * [close] 关闭浮层;触发源与槽位语义保持一致 typedef TPopupSlotBuilder = Widget Function( BuildContext context, VoidCallback close, @@ -71,7 +71,7 @@ bool _isPopupDefaultClose(TPopupSlotBuilder? builder) => /// /// 内置控件会映射为 [TPopupTrigger.overlay]、[TPopupTrigger.cancelBtn]、 /// [TPopupTrigger.confirmBtn]、[TPopupTrigger.closeBtn]; -/// [TPopupHandle.close]、系统返回、自定义 builder 内调用 `close` 均为 +/// [TPopupHandle.close]、系统返回、[headerBuilder] 内调用 `close` 等为 /// [TPopupTrigger.programmatic]。 enum TPopupTrigger { /// 点击蒙层,且 [TPopupOptions.closeOnOverlayClick] 为 true。 @@ -86,7 +86,7 @@ enum TPopupTrigger { /// 点击 center 内置关闭图标([TPopupOptions.closeBuilder] 为内置默认时)。 closeBtn, - /// [TPopupHandle.close]、系统返回键、自定义 builder 调用 `close` 等。 + /// [TPopupHandle.close]、系统返回键、[headerBuilder] 自定义内调用 `close` 等。 programmatic, } diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart index 9798aede2..912ecadd9 100644 --- a/tdesign-component/test/t_popup_coverage_test.dart +++ b/tdesign-component/test/t_popup_coverage_test.dart @@ -20,7 +20,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - titleBuilder: (_) => TText('仅标题行'), + titleWidget: TText('仅标题行'), cancelBuilder: null, confirmBuilder: null, child: const SizedBox(height: 60)), @@ -42,7 +42,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - titleBuilder: (_) => TText('左对齐标题'), + titleWidget: TText('左对齐标题'), cancelBuilder: null, confirmBuilder: null, child: const SizedBox(height: 60)), @@ -298,7 +298,7 @@ void main() { TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - titleBuilder: (_) => const Text('w'), + titleWidget: const Text('w'), cancelBuilder: null, confirmBuilder: null, ).hasBuiltInHeader, @@ -318,7 +318,7 @@ void main() { final titleOnly = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - titleBuilder: (_) => TText('仅标题'), + titleWidget: TText('仅标题'), cancelBuilder: null, confirmBuilder: null, ); @@ -455,7 +455,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - titleBuilder: (_) => TText('标题'), + titleWidget: TText('标题'), onVisibleChange: (visible, trigger) { if (!visible) { hideTriggers.add(trigger); @@ -834,9 +834,9 @@ void main() { test('传自定义 builder → 替换为自定义', () { final base = TPopupOptions(child: const SizedBox()); - titleFn(BuildContext _) => const Text('t'); - final next = base.copyWith(titleBuilder: titleFn); - expect(next.titleBuilder, same(titleFn)); + const titleWidget = Text('t'); + final next = base.copyWith(titleWidget: titleWidget); + expect(next.titleWidget, same(titleWidget)); // 其它继承 expect(next.usesDefaultHeader, isTrue); }); @@ -883,7 +883,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 160, - titleBuilder: (_) => const Text('再开测试'), + titleWidget: const Text('再开测试'), child: const SizedBox(height: 60)), ); }, @@ -948,7 +948,7 @@ void main() { placement: TPopupPlacement.bottom, height: 180, headerBuilder: null, // 一票否决 - titleBuilder: (_) => const Text('应隐藏-标题'), + titleWidget: const Text('应隐藏-标题'), cancelBuilder: (_, close) => GestureDetector(onTap: close, child: const Text('应隐藏-左')), confirmBuilder: (_, close) => @@ -1018,7 +1018,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 180, - titleBuilder: (_) => const Text('标题'), + titleWidget: const Text('标题'), child: const SizedBox(height: 80)), ); }, @@ -1037,7 +1037,7 @@ void main() { placement: TPopupPlacement.bottom, height: 180, cancelBuilder: null, - titleBuilder: (_) => const Text('标题在'), + titleWidget: const Text('标题在'), child: const SizedBox(height: 80)), ); }, @@ -1058,7 +1058,7 @@ void main() { placement: TPopupPlacement.bottom, height: 180, confirmBuilder: null, - titleBuilder: (_) => const Text('标题在'), + titleWidget: const Text('标题在'), child: const SizedBox(height: 80)), ); }, @@ -1069,7 +1069,7 @@ void main() { expect(find.text('确定'), findsNothing); }); - testWidgets('headerBuilder 自定义 → titleBuilder / cancelBuilder / confirmBuilder 全部被忽略', + testWidgets('headerBuilder 自定义 → titleWidget / cancelBuilder / confirmBuilder 全部被忽略', (tester) async { await openPopup( tester, @@ -1080,7 +1080,7 @@ void main() { placement: TPopupPlacement.bottom, height: 180, headerBuilder: (_, __) => const Text('唯一头部'), - titleBuilder: (_) => const Text('不该出现-标题'), + titleWidget: const Text('不该出现-标题'), cancelBuilder: (_, __) => const Text('不该出现-左'), confirmBuilder: (_, __) => const Text('不该出现-右'), child: const SizedBox(height: 80)), @@ -1128,7 +1128,7 @@ void main() { expect(lastTrigger, TPopupTrigger.cancelBtn); }); - testWidgets('自定义 cancelBuilder 内调 close → programmatic(不是 cancelBtn)', + testWidgets('自定义 cancelBuilder 内调 close → cancelBtn', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1155,10 +1155,10 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('我自己的取消')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.programmatic); + expect(lastTrigger, TPopupTrigger.cancelBtn); }); - testWidgets('自定义 confirmBuilder 内调 close → programmatic', + testWidgets('自定义 confirmBuilder 内调 close → confirmBtn', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1185,10 +1185,10 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('我自己的确定')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.programmatic); + expect(lastTrigger, TPopupTrigger.confirmBtn); }); - testWidgets('center 自定义 closeBuilder 内调 close → programmatic(不是 closeBtn)', + testWidgets('center 自定义 closeBuilder 内调 close → closeBtn', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1216,7 +1216,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('我自己的关闭')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.programmatic); + expect(lastTrigger, TPopupTrigger.closeBtn); }); testWidgets('headerBuilder 自定义内调 close → programmatic(无 sentinel 细分)', diff --git a/tdesign-component/test/t_popup_options_test.dart b/tdesign-component/test/t_popup_options_test.dart index a0cc64a15..96fa4e9d7 100644 --- a/tdesign-component/test/t_popup_options_test.dart +++ b/tdesign-component/test/t_popup_options_test.dart @@ -10,7 +10,7 @@ void main() { expect(options.usesDefaultHeader, isTrue); expect(options.usesDefaultCancel, isTrue); expect(options.usesDefaultConfirm, isTrue); - expect(options.titleBuilder, isNull); + expect(options.titleWidget, isNull); }); test('bottom 默认走内置三段式(useDefaultHeader)', () { @@ -34,7 +34,7 @@ void main() { ).normalized(); expect(options.showCancelSlot, isFalse); expect(options.showConfirmSlot, isFalse); - expect(options.hasBuiltInHeader, isFalse); // titleBuilder 也为 null + expect(options.hasBuiltInHeader, isFalse); // titleWidget 也为 null }); test('headerBuilder null 不显示头部', () { @@ -88,11 +88,11 @@ void main() { final options = TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, - titleBuilder: (_) => const Text('x'), + titleWidget: const Text('x'), headerBuilder: (_, __) => const Text('h'), ).normalized(); expect(options.headerBuilder, isNull); - expect(options.titleBuilder, isNull); + expect(options.titleWidget, isNull); expect(options.cancelBuilder, isNull); expect(options.confirmBuilder, isNull); expect(options.hasBuiltInHeader, isFalse); @@ -108,12 +108,12 @@ void main() { }); test('hasBuiltInHeader 内置三段中任一槽非 null 即 true', () { - // titleBuilder 单独存在 + // titleWidget 单独存在 expect( TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.bottom, - titleBuilder: (_) => const Text('x'), + titleWidget: const Text('x'), cancelBuilder: null, confirmBuilder: null, ).normalized().hasBuiltInHeader, @@ -157,12 +157,12 @@ void main() { ).assertPlacementParams(), throwsA(isA()), ); - // center 不该有 titleBuilder(属于 bottom 头部字段) + // center 不该有 titleWidget(属于 bottom 头部字段) expect( () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, - titleBuilder: (_) => const Text('x'), + titleWidget: const Text('x'), ).assertPlacementParams(), throwsA(isA()), ); diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart index ed45ea62b..ab9592577 100644 --- a/tdesign-component/test/t_popup_test.dart +++ b/tdesign-component/test/t_popup_test.dart @@ -235,7 +235,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 200, - titleBuilder: (_) => TText('标题'), + titleWidget: TText('标题'), child: const SizedBox(height: 80)), ); }, @@ -301,7 +301,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 180, - titleBuilder: (_) => TText('不应出现'), + titleWidget: TText('不应出现'), headerBuilder: null, child: const SizedBox(height: 80)), ); @@ -321,7 +321,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 180, - titleBuilder: (_) => TText('仅标题'), + titleWidget: TText('仅标题'), cancelBuilder: null, confirmBuilder: null, child: const SizedBox(height: 80)), @@ -667,7 +667,7 @@ void main() { onPressed: () { TPopup.show( tester.element(find.text('open')), - // .top factory 根本不暴露 titleBuilder:编译期就杜绝错位 + // .top factory 根本不暴露 titleWidget:编译期就杜绝错位 options: TPopupOptions.top( height: 120, child: const Text('内容'), @@ -709,7 +709,7 @@ void main() { } }); - testWidgets('titleBuilder + 自定义 cancel/confirm Builder 文案', (tester) async { + testWidgets('titleWidget + 自定义 cancel/confirm Builder 文案', (tester) async { await openPopup( tester, onPressed: () { @@ -718,7 +718,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 180, - titleBuilder: (_) => const Text('Widget标题'), + titleWidget: const Text('Widget标题'), cancelBuilder: (_, close) => GestureDetector(onTap: close, child: const Text('左')), confirmBuilder: (_, close) => From 7b026a724de8e289b74ffc1608a5c4f25582b113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Mon, 25 May 2026 14:36:01 +0800 Subject: [PATCH 13/27] refactor(popup): update TPopupTrigger values for consistency across components --- .../components/popup/_popup_center_close.dart | 6 ++-- .../src/components/popup/_popup_header.dart | 12 +++---- .../src/components/popup/_popup_route.dart | 4 +-- .../src/components/popup/t_popup_handle.dart | 4 +-- .../src/components/popup/t_popup_options.dart | 6 ++-- .../src/components/popup/t_popup_types.dart | 35 +++++++++++-------- .../test/t_popup_coverage_test.dart | 26 +++++++------- tdesign-component/test/t_popup_test.dart | 8 ++--- 8 files changed, 54 insertions(+), 47 deletions(-) diff --git a/tdesign-component/lib/src/components/popup/_popup_center_close.dart b/tdesign-component/lib/src/components/popup/_popup_center_close.dart index cf53a4693..4a2b0dcdf 100644 --- a/tdesign-component/lib/src/components/popup/_popup_center_close.dart +++ b/tdesign-component/lib/src/components/popup/_popup_center_close.dart @@ -1,6 +1,6 @@ part of 't_popup.dart'; -/// center 面板外关闭控件:默认图标与自定义关闭槽位都上报 [TPopupTrigger.closeBtn]。 +/// center 面板外关闭控件:默认图标与自定义关闭槽位都上报 [TPopupTrigger.close]。 Widget buildPopupCenterCloseControl({ required BuildContext context, required TPopupOptions options, @@ -16,7 +16,7 @@ Widget buildPopupCenterCloseControl({ color: theme.fontWhColor1, size: 32, ), - onPressed: () => onCloseWithTrigger(TPopupTrigger.closeBtn), + onPressed: () => onCloseWithTrigger(TPopupTrigger.close), ); } return options.closeBuilder!(context, onCloseSlotTap); @@ -46,7 +46,7 @@ class PopupCenterUnderClose extends StatelessWidget { ); } - void onCloseSlotTap() => onCloseWithTrigger(TPopupTrigger.closeBtn); + void onCloseSlotTap() => onCloseWithTrigger(TPopupTrigger.close); final closeControl = buildPopupCenterCloseControl( context: context, diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart index 187973a9f..ba5acb1c5 100644 --- a/tdesign-component/lib/src/components/popup/_popup_header.dart +++ b/tdesign-component/lib/src/components/popup/_popup_header.dart @@ -14,7 +14,7 @@ class PopupHeader extends StatelessWidget { static const double headerHeight = 58; void _close() { - onCloseWithTrigger(TPopupTrigger.programmatic); + onCloseWithTrigger(TPopupTrigger.custom); } @override @@ -46,7 +46,7 @@ class _DefaultHeader extends StatelessWidget { final TPopupOptions options; /// 给「内置 sentinel cancel/confirm 按钮」用的 close 入口, - /// 按钮自己传 [TPopupTrigger.cancelBtn] / [TPopupTrigger.confirmBtn]。 + /// 按钮自己传 [TPopupTrigger.cancel] / [TPopupTrigger.confirm]。 final void Function(TPopupTrigger trigger) onCloseWithTrigger; @override @@ -109,7 +109,7 @@ class _DefaultHeader extends StatelessWidget { Widget _buildCancel(BuildContext context, TThemeData theme) { if (_isPopupDefaultCancel(options.cancelBuilder)) { return TToolbarPressable( - onTap: () => onCloseWithTrigger(TPopupTrigger.cancelBtn), + onTap: () => onCloseWithTrigger(TPopupTrigger.cancel), child: TText( context.resource.cancel, textColor: theme.textColorSecondary, @@ -119,14 +119,14 @@ class _DefaultHeader extends StatelessWidget { } return options.cancelBuilder!( context, - () => onCloseWithTrigger(TPopupTrigger.cancelBtn), + () => onCloseWithTrigger(TPopupTrigger.cancel), ); } Widget _buildConfirm(BuildContext context, TThemeData theme) { if (_isPopupDefaultConfirm(options.confirmBuilder)) { return TToolbarPressable( - onTap: () => onCloseWithTrigger(TPopupTrigger.confirmBtn), + onTap: () => onCloseWithTrigger(TPopupTrigger.confirm), child: TText( context.resource.confirm, textColor: theme.brandNormalColor, @@ -137,7 +137,7 @@ class _DefaultHeader extends StatelessWidget { } return options.confirmBuilder!( context, - () => onCloseWithTrigger(TPopupTrigger.confirmBtn), + () => onCloseWithTrigger(TPopupTrigger.confirm), ); } } diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart index 048ec966b..0210e0f5d 100644 --- a/tdesign-component/lib/src/components/popup/_popup_route.dart +++ b/tdesign-component/lib/src/components/popup/_popup_route.dart @@ -205,7 +205,7 @@ class _PopupNavigatorRoute extends PopupRoute { @override TickerFuture didPush() { options.onOpen?.call(); - options.onVisibleChange?.call(true, TPopupTrigger.programmatic); + options.onVisibleChange?.call(true, TPopupTrigger.api); final future = super.didPush(); future.whenComplete(_attachAnimationListener); return future; @@ -213,7 +213,7 @@ class _PopupNavigatorRoute extends PopupRoute { @override bool didPop(T? result) { - fireCloseStart(TPopupTrigger.programmatic); + fireCloseStart(TPopupTrigger.systemBack); return super.didPop(result); } diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart index 236d2ebd6..8e0906603 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart @@ -89,7 +89,7 @@ class TPopupHandle { } /// 关闭当前展示的浮层;[TPopupOptions.onVisibleChange] 的 [TPopupTrigger] 为 - /// [TPopupTrigger.programmatic]。 + /// [TPopupTrigger.api]。 /// /// [result] 可选,作为 [Navigator.pop] 的返回值。 /// @@ -99,7 +99,7 @@ class TPopupHandle { return; } _markClosing(); - _route?.fireCloseStart(TPopupTrigger.programmatic); + _route?.fireCloseStart(TPopupTrigger.api); _route!.navigator?.pop(result); } diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart index 3d31275bc..39d8bb2fd 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_options.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart @@ -357,17 +357,17 @@ class TPopupOptions { /// bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 /// - /// 内置默认为「取消」,点击触发 [TPopupTrigger.cancelBtn]。 + /// 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 final TPopupSlotBuilder? cancelBuilder; /// bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 /// - /// 内置默认为「确定」,点击触发 [TPopupTrigger.confirmBtn]。 + /// 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 final TPopupSlotBuilder? confirmBuilder; /// center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 /// - /// 内置默认点击触发 [TPopupTrigger.closeBtn]。 + /// 内置默认点击触发 [TPopupTrigger.close]。 final TPopupSlotBuilder? closeBuilder; /// 路由 push 时(打开动画开始前)。 diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart index 851a2ce22..659cf4fc1 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_types.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart @@ -24,7 +24,7 @@ enum TPopupPlacement { /// bottom 整行头部自定义构建器。 /// /// * [context] 构建上下文 -/// * [close] 关闭浮层,触发源为 [TPopupTrigger.programmatic] +/// * [close] 关闭浮层,触发源为 [TPopupTrigger.custom] typedef TPopupHeaderBuilder = Widget Function( BuildContext context, VoidCallback close, @@ -69,31 +69,38 @@ bool _isPopupDefaultClose(TPopupSlotBuilder? builder) => /// /// 作为 [TPopupVisibleChangeCallback] 的第二个参数,以及关闭流程中的语义标记。 /// -/// 内置控件会映射为 [TPopupTrigger.overlay]、[TPopupTrigger.cancelBtn]、 -/// [TPopupTrigger.confirmBtn]、[TPopupTrigger.closeBtn]; -/// [TPopupHandle.close]、系统返回、[headerBuilder] 内调用 `close` 等为 -/// [TPopupTrigger.programmatic]。 +/// 内置控件会映射为 [TPopupTrigger.overlay]、[TPopupTrigger.cancel]、 +/// [TPopupTrigger.confirm]、[TPopupTrigger.close]; +/// [TPopupHandle.close] 为 [TPopupTrigger.api];系统返回为 +/// [TPopupTrigger.systemBack];[headerBuilder] 内调用 `close` 等为 +/// [TPopupTrigger.custom]。 enum TPopupTrigger { /// 点击蒙层,且 [TPopupOptions.closeOnOverlayClick] 为 true。 overlay, - /// 点击 bottom 内置「取消」按钮([TPopupOptions.cancelBuilder] 为内置默认时)。 - cancelBtn, + /// 点击 bottom 取消语义槽位(含默认与自定义 [TPopupOptions.cancelBuilder])。 + cancel, - /// 点击 bottom 内置「确定」按钮([TPopupOptions.confirmBuilder] 为内置默认时)。 - confirmBtn, + /// 点击 bottom 确认语义槽位(含默认与自定义 [TPopupOptions.confirmBuilder])。 + confirm, - /// 点击 center 内置关闭图标([TPopupOptions.closeBuilder] 为内置默认时)。 - closeBtn, + /// 点击 center 关闭语义槽位(含默认与自定义 [TPopupOptions.closeBuilder])。 + close, - /// [TPopupHandle.close]、系统返回键、[headerBuilder] 自定义内调用 `close` 等。 - programmatic, + /// 外部 API 主动触发的显隐变化,如 [TPopupHandle.close] 或打开事件。 + api, + + /// 系统返回键或系统路由返回触发的关闭。 + systemBack, + + /// 无框架预设动作语义的自定义关闭,如 [headerBuilder] 内调用 `close`。 + custom, } /// 浮层显隐变化回调。 /// /// * [visible] 为 true 表示打开,false 表示开始关闭 -/// * [trigger] 关闭来源,见 [TPopupTrigger];打开时为 [TPopupTrigger.programmatic] +/// * [trigger] 关闭来源,见 [TPopupTrigger];打开时为 [TPopupTrigger.api] typedef TPopupVisibleChangeCallback = void Function( bool visible, TPopupTrigger trigger, diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart index 912ecadd9..529b3c22a 100644 --- a/tdesign-component/test/t_popup_coverage_test.dart +++ b/tdesign-component/test/t_popup_coverage_test.dart @@ -469,7 +469,7 @@ void main() { await tester.tap(find.text('取消')); await tester.pumpAndSettle(); - expect(hideTriggers.last, TPopupTrigger.cancelBtn); + expect(hideTriggers.last, TPopupTrigger.cancel); await openPopup( tester, @@ -514,7 +514,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.byIcon(TIcons.close_circle)); await tester.pumpAndSettle(); - expect(hideTriggers.last, TPopupTrigger.closeBtn); + expect(hideTriggers.last, TPopupTrigger.close); }); testWidgets('Popup 内嵌套 show 可再开一层且先关内层', (tester) async { @@ -801,7 +801,7 @@ void main() { expect(opts.showOverlay, isFalse); expect(opts.overlayOpacity, 0.5); expect(opts.destroyOnClose, isTrue); - opts.onVisibleChange?.call(false, TPopupTrigger.programmatic); + opts.onVisibleChange?.call(false, TPopupTrigger.api); expect(visibleChanges, 1); }); }); @@ -1102,7 +1102,7 @@ void main() { // 触发源精细化:sentinel vs 自定义 builder 的 close 上报区分 // ============================================================ group('Popup 触发源细分(sentinel vs 自定义 builder)', () { - testWidgets('内置 cancel 按钮点击 → cancelBtn(与 confirmBtn 区分)', + testWidgets('内置 cancel 按钮点击 → cancel(与 confirm 区分)', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1125,10 +1125,10 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('取消')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.cancelBtn); + expect(lastTrigger, TPopupTrigger.cancel); }); - testWidgets('自定义 cancelBuilder 内调 close → cancelBtn', + testWidgets('自定义 cancelBuilder 内调 close → cancel', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1155,10 +1155,10 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('我自己的取消')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.cancelBtn); + expect(lastTrigger, TPopupTrigger.cancel); }); - testWidgets('自定义 confirmBuilder 内调 close → confirmBtn', + testWidgets('自定义 confirmBuilder 内调 close → confirm', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1185,10 +1185,10 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('我自己的确定')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.confirmBtn); + expect(lastTrigger, TPopupTrigger.confirm); }); - testWidgets('center 自定义 closeBuilder 内调 close → closeBtn', + testWidgets('center 自定义 closeBuilder 内调 close → close', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1216,10 +1216,10 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('我自己的关闭')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.closeBtn); + expect(lastTrigger, TPopupTrigger.close); }); - testWidgets('headerBuilder 自定义内调 close → programmatic(无 sentinel 细分)', + testWidgets('headerBuilder 自定义内调 close → custom(无预设动作语义)', (tester) async { TPopupTrigger? lastTrigger; await openPopup( @@ -1246,7 +1246,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('整行自定义头部')); await tester.pumpAndSettle(); - expect(lastTrigger, TPopupTrigger.programmatic); + expect(lastTrigger, TPopupTrigger.custom); }); }); } diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart index ab9592577..9c2fa9839 100644 --- a/tdesign-component/test/t_popup_test.dart +++ b/tdesign-component/test/t_popup_test.dart @@ -804,7 +804,7 @@ void main() { }); group('TPopup 触发源与配置', () { - testWidgets('handle.close 触发 programmatic', (tester) async { + testWidgets('handle.close 触发 api', (tester) async { TPopupTrigger? hideTrigger; TPopupHandle? handle; @@ -828,10 +828,10 @@ void main() { await tester.pumpAndSettle(); handle!.close(); await tester.pumpAndSettle(); - expect(hideTrigger, TPopupTrigger.programmatic); + expect(hideTrigger, TPopupTrigger.api); }); - testWidgets('confirm 点击触发 confirmBtn', (tester) async { + testWidgets('confirm 点击触发 confirm', (tester) async { TPopupTrigger? hideTrigger; late BuildContext hostContext; @@ -856,7 +856,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.text('确定')); await tester.pumpAndSettle(); - expect(hideTrigger, TPopupTrigger.confirmBtn); + expect(hideTrigger, TPopupTrigger.confirm); }); testWidgets('destroyOnClose 路由关闭后可再次 show', (tester) async { From 3ecbfe7661e729dd860d95a82c086702425bb169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= Date: Mon, 25 May 2026 16:57:11 +0800 Subject: [PATCH 14/27] refactor(popup): replace margin with inset parameters in TPopup options and components for better layout control --- .../example/lib/page/t_popup_page.dart | 62 +++++----- .../components/calendar/t_calendar_popup.dart | 14 ++- .../lib/src/components/drawer/t_drawer.dart | 12 +- .../src/components/popup/_popup_layout.dart | 72 +++++------ .../src/components/popup/_popup_route.dart | 7 +- .../lib/src/components/popup/t_popup.dart | 1 + .../src/components/popup/t_popup_inset.dart | 50 ++++++++ .../src/components/popup/t_popup_options.dart | 82 +++++++------ .../src/components/popup/t_popup_types.dart | 8 +- tdesign-component/lib/tdesign_flutter.dart | 5 + .../test/t_popup_coverage_test.dart | 49 +++----- .../test/t_popup_layout_test.dart | 112 ++++++++---------- .../test/t_popup_options_test.dart | 13 +- tdesign-component/test/t_popup_test.dart | 6 +- tdesign-site/src/popup/README.md | 18 +-- 15 files changed, 278 insertions(+), 233 deletions(-) create mode 100644 tdesign-component/lib/src/components/popup/t_popup_inset.dart diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart index 93554dc9a..086ef2419 100644 --- a/tdesign-component/example/lib/page/t_popup_page.dart +++ b/tdesign-component/example/lib/page/t_popup_page.dart @@ -80,7 +80,7 @@ class TPopupPage extends StatelessWidget { ExampleModule( title: '更多 API', children: [ - ExampleItem(builder: _buildApiMarginTop), + ExampleItem(builder: _buildApiInset), ExampleItem(builder: _buildApiShowOverlayFalse), ExampleItem(builder: _buildApiOnOverlayClick), ExampleItem(builder: _buildApiDuration), @@ -104,15 +104,15 @@ class TPopupPage extends StatelessWidget { height: 280, titleWidget: TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), cancelBuilder: (_, __) => TText( - '点这里确认!', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), + '点这里确认!', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), confirmBuilder: (_, __) => TText( - '关闭', - textColor: TTheme.of(context).errorNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), + '关闭', + textColor: TTheme.of(context).errorNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), child: Container(height: 200)), ); }, @@ -183,15 +183,15 @@ class TPopupPage extends StatelessWidget { radius: 6, titleWidget: TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'), cancelBuilder: (_, __) => TText( - '点这里确认!', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), + '点这里确认!', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), confirmBuilder: (_, __) => TText( - '关闭', - textColor: TTheme.of(context).errorNormalColor, - font: TTheme.of(context).fontBodyLarge, - ), + '关闭', + textColor: TTheme.of(context).errorNormalColor, + font: TTheme.of(context).fontBodyLarge, + ), child: Container(height: 200)), ); }, @@ -260,7 +260,7 @@ class TPopupPage extends StatelessWidget { context, options: TPopupOptions.right( width: 280, - margin: EdgeInsets.only(top: renderBox.size.height), + inset: TPopupRightInset(top: renderBox.size.height), child: Container( color: TTheme.of(context).bgColorContainer, )), @@ -493,10 +493,10 @@ class TPopupPage extends StatelessWidget { options: TPopupOptions.bottom( height: 280, cancelBuilder: (_, __) => TText( - '关闭', - textColor: TTheme.of(context).textColorSecondary, - font: TTheme.of(context).fontBodyLarge, - ), + '关闭', + textColor: TTheme.of(context).textColorSecondary, + font: TTheme.of(context).fontBodyLarge, + ), titleWidget: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -511,11 +511,11 @@ class TPopupPage extends StatelessWidget { ], ), confirmBuilder: (_, __) => TText( - '完成', - textColor: TTheme.of(context).brandNormalColor, - font: TTheme.of(context).fontTitleMedium, - fontWeight: FontWeight.w600, - ), + '完成', + textColor: TTheme.of(context).brandNormalColor, + font: TTheme.of(context).fontTitleMedium, + fontWeight: FontWeight.w600, + ), child: Container(height: 200)), ); }, @@ -587,9 +587,9 @@ class TPopupPage extends StatelessWidget { // --- 更多 API --- @Demo(group: 'popup') - Widget _buildApiMarginTop(BuildContext context) { + Widget _buildApiInset(BuildContext context) { return TButton( - text: 'bottom margin.top', + text: 'bottom inset.left/right', isBlock: true, theme: TButtonTheme.primary, type: TButtonType.outline, @@ -599,8 +599,8 @@ class TPopupPage extends StatelessWidget { context, options: TPopupOptions.bottom( height: 320, - margin: const EdgeInsets.only(top: 120, left: 16, right: 16), - titleWidget: TText('日历式留白'), + inset: const TPopupBottomInset(left: 16, right: 16), + titleWidget: TText('左右留白'), child: Container( height: 240, color: TTheme.of(context).bgColorContainer, diff --git a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart index d29028ee9..c812c3cee 100644 --- a/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart +++ b/tdesign-component/lib/src/components/calendar/t_calendar_popup.dart @@ -69,12 +69,17 @@ class TCalendarPopup { return; } final childWidget = builder?.call(context) ?? child; + final topInset = top?.clamp(0.0, double.infinity).toDouble(); + final maxHeight = topInset == null + ? null + : (MediaQuery.sizeOf(context).height - topInset) + .clamp(0.0, double.infinity) + .toDouble(); _calendarHandle = TPopup.show( context, options: TPopupOptions.bottom( cancelBuilder: null, confirmBuilder: null, - margin: EdgeInsets.only(top: top ?? 0), closeOnOverlayClick: false, onOverlayClick: () { if (_autoClose) { @@ -88,7 +93,12 @@ class TCalendarPopup { confirmBtn: confirmBtn, onClose: _onClose, onConfirm: _onConfirm, - child: childWidget!, + child: maxHeight == null + ? childWidget! + : ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxHeight), + child: childWidget!, + ), ), ), ); diff --git a/tdesign-component/lib/src/components/drawer/t_drawer.dart b/tdesign-component/lib/src/components/drawer/t_drawer.dart index c255fe685..ac322e8f0 100644 --- a/tdesign-component/lib/src/components/drawer/t_drawer.dart +++ b/tdesign-component/lib/src/components/drawer/t_drawer.dart @@ -107,15 +107,19 @@ class TDrawer { final overlayEnabled = showOverlay ?? true; final dismissible = overlayEnabled && (closeOnOverlayClick ?? true); + final popupPlacement = placement == TDrawerPlacement.right + ? TPopupPlacement.right + : TPopupPlacement.left; + final popupInset = placement == TDrawerPlacement.right + ? TPopupRightInset(top: drawerTop ?? 0) + : TPopupLeftInset(top: drawerTop ?? 0); _drawerHandle = TPopup.show( context, options: TPopupOptions( - placement: placement == TDrawerPlacement.right - ? TPopupPlacement.right - : TPopupPlacement.left, + placement: popupPlacement, width: width, - margin: EdgeInsets.only(top: drawerTop ?? 0), + inset: popupInset, showOverlay: overlayEnabled, closeOnOverlayClick: dismissible, overlayColor: overlayEnabled ? null : Colors.transparent, diff --git a/tdesign-component/lib/src/components/popup/_popup_layout.dart b/tdesign-component/lib/src/components/popup/_popup_layout.dart index aa326620e..d55207c02 100644 --- a/tdesign-component/lib/src/components/popup/_popup_layout.dart +++ b/tdesign-component/lib/src/components/popup/_popup_layout.dart @@ -4,78 +4,69 @@ part of 't_popup.dart'; class PopupLayout { PopupLayout({ required this.placement, - required this.screenSize, - required this.margin, + this.inset, this.width, this.height, }); final TPopupPlacement placement; - final Size screenSize; - final EdgeInsets margin; + final TPopupInset? inset; final double? width; final double? height; static const double defaultDrawerWidth = 280; - EdgeInsets resolvedMargin() { - if (placement == TPopupPlacement.center) { - return EdgeInsets.zero; - } - return margin; - } - Widget wrapPositioned({required Widget child}) { - final m = resolvedMargin(); switch (placement) { case TPopupPlacement.top: + final inset = + this.inset is TPopupTopInset ? this.inset as TPopupTopInset : null; return Positioned( - top: m.top, - left: m.left, - right: m.right, + top: 0, + left: inset?.left ?? 0, + right: inset?.right ?? 0, height: height, child: child, ); case TPopupPlacement.bottom: - final bottomHeight = _bottomHeight(m); - if (bottomHeight != null && m.top > 0) { - return Positioned( - top: m.top, - left: m.left, - right: m.right, - height: bottomHeight, - child: child, - ); - } + final inset = this.inset is TPopupBottomInset + ? this.inset as TPopupBottomInset + : null; + final bottomHeight = _bottomHeight(); if (bottomHeight != null) { return Positioned( - left: m.left, - right: m.right, - bottom: m.bottom, + left: inset?.left ?? 0, + right: inset?.right ?? 0, + bottom: 0, height: bottomHeight, child: child, ); } return Positioned( - top: m.top > 0 ? m.top : null, - left: m.left, - right: m.right, - bottom: m.bottom, + left: inset?.left ?? 0, + right: inset?.right ?? 0, + bottom: 0, child: child, ); case TPopupPlacement.left: + final inset = this.inset is TPopupLeftInset + ? this.inset as TPopupLeftInset + : null; return Positioned( - top: m.top, - bottom: m.bottom, - left: m.left, + top: inset?.top ?? 0, + bottom: inset?.bottom ?? 0, + left: 0, width: width ?? defaultDrawerWidth, child: child, ); case TPopupPlacement.right: + final inset = this.inset is TPopupRightInset + ? this.inset as TPopupRightInset + : null; return Positioned( - top: m.top, - bottom: m.bottom, - right: m.right, + top: inset?.top ?? 0, + bottom: inset?.bottom ?? 0, + right: 0, width: width ?? defaultDrawerWidth, child: child, ); @@ -86,13 +77,10 @@ class PopupLayout { } } - double? _bottomHeight(EdgeInsets m) { + double? _bottomHeight() { if (height != null) { return height; } - if (m.top > 0) { - return screenSize.height - m.top - m.bottom; - } return null; } diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart index 0210e0f5d..10adf4ede 100644 --- a/tdesign-component/lib/src/components/popup/_popup_route.dart +++ b/tdesign-component/lib/src/components/popup/_popup_route.dart @@ -7,8 +7,7 @@ class _PopupNavigatorRoute extends PopupRoute { required this.onCloseWithTrigger, }) : _layout = PopupLayout( placement: options.placement, - screenSize: Size.zero, - margin: options.margin, + inset: options.inset, width: options.width, height: options.height, ); @@ -91,15 +90,13 @@ class _PopupNavigatorRoute extends PopupRoute { reverseCurve: Curves.easeOut, ); - final mediaQuery = MediaQuery.of(context); if (options.showOverlay) { _barrierSemanticsLabel ??= MaterialLocalizations.of(context).modalBarrierDismissLabel; } _layout = PopupLayout( placement: options.placement, - screenSize: mediaQuery.size, - margin: options.margin, + inset: options.inset, width: options.width, height: options.height, ); diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart index 1702716ac..39f5ff44a 100644 --- a/tdesign-component/lib/src/components/popup/t_popup.dart +++ b/tdesign-component/lib/src/components/popup/t_popup.dart @@ -27,6 +27,7 @@ part '_popup_route.dart'; part '_popup_shell.dart'; part '_popup_tracker.dart'; part 't_popup_handle.dart'; +part 't_popup_inset.dart'; part 't_popup_options.dart'; part 't_popup_types.dart'; diff --git a/tdesign-component/lib/src/components/popup/t_popup_inset.dart b/tdesign-component/lib/src/components/popup/t_popup_inset.dart new file mode 100644 index 000000000..388564acd --- /dev/null +++ b/tdesign-component/lib/src/components/popup/t_popup_inset.dart @@ -0,0 +1,50 @@ +part of 't_popup.dart'; + +/// Popup 在交叉轴方向的边缘留白基类。 +abstract class TPopupInset { + const TPopupInset(); +} + +/// bottom 方向的左右留白。 +class TPopupBottomInset extends TPopupInset { + const TPopupBottomInset({ + this.left = 0, + this.right = 0, + }); + + final double left; + final double right; +} + +/// top 方向的左右留白。 +class TPopupTopInset extends TPopupInset { + const TPopupTopInset({ + this.left = 0, + this.right = 0, + }); + + final double left; + final double right; +} + +/// left 方向的上下留白。 +class TPopupLeftInset extends TPopupInset { + const TPopupLeftInset({ + this.top = 0, + this.bottom = 0, + }); + + final double top; + final double bottom; +} + +/// right 方向的上下留白。 +class TPopupRightInset extends TPopupInset { + const TPopupRightInset({ + this.top = 0, + this.bottom = 0, + }); + + final double top; + final double bottom; +} diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart index 39d8bb2fd..890a8ab3f 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_options.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart @@ -18,10 +18,10 @@ const Object _unset = Object(); /// /// | [TPopupPlacement] | 头部 / 关闭 | 尺寸 | /// |-------------------|-------------|------| -/// | [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] | +/// | [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[inset] | /// | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] | -/// | [TPopupPlacement.top] | — | [height]、[margin] | -/// | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[margin] | +/// | [TPopupPlacement.top] | — | [height]、[inset] | +/// | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[inset] | /// /// ## Builder 三态([headerBuilder]、[cancelBuilder]、[confirmBuilder]、[closeBuilder]) /// @@ -43,7 +43,7 @@ class TPopupOptions { this.placement = TPopupPlacement.bottom, this.width, this.height, - this.margin = EdgeInsets.zero, + this.inset, this.radius, this.backgroundColor, this.showOverlay = true, @@ -73,7 +73,7 @@ class TPopupOptions { factory TPopupOptions.bottom({ required Widget child, double? height, - EdgeInsets margin = EdgeInsets.zero, + TPopupBottomInset? inset, TPopupHeaderBuilder? headerBuilder = _kPopupDefaultHeader, Widget? titleWidget, TPopupSlotBuilder? cancelBuilder = _kPopupDefaultCancel, @@ -98,7 +98,7 @@ class TPopupOptions { child: child, placement: TPopupPlacement.bottom, height: height, - margin: margin, + inset: inset, headerBuilder: headerBuilder, titleWidget: titleWidget, cancelBuilder: cancelBuilder, @@ -173,7 +173,7 @@ class TPopupOptions { factory TPopupOptions.top({ required Widget child, double? height, - EdgeInsets margin = EdgeInsets.zero, + TPopupTopInset? inset, double? radius, Color? backgroundColor, bool showOverlay = true, @@ -194,7 +194,7 @@ class TPopupOptions { child: child, placement: TPopupPlacement.top, height: height, - margin: margin, + inset: inset, radius: radius, backgroundColor: backgroundColor, showOverlay: showOverlay, @@ -218,7 +218,7 @@ class TPopupOptions { factory TPopupOptions.left({ required Widget child, double? width, - EdgeInsets margin = EdgeInsets.zero, + TPopupLeftInset? inset, double? radius, Color? backgroundColor, bool showOverlay = true, @@ -239,7 +239,7 @@ class TPopupOptions { child: child, placement: TPopupPlacement.left, width: width, - margin: margin, + inset: inset, radius: radius, backgroundColor: backgroundColor, showOverlay: showOverlay, @@ -263,7 +263,7 @@ class TPopupOptions { factory TPopupOptions.right({ required Widget child, double? width, - EdgeInsets margin = EdgeInsets.zero, + TPopupRightInset? inset, double? radius, Color? backgroundColor, bool showOverlay = true, @@ -284,7 +284,7 @@ class TPopupOptions { child: child, placement: TPopupPlacement.right, width: width, - margin: margin, + inset: inset, radius: radius, backgroundColor: backgroundColor, showOverlay: showOverlay, @@ -314,11 +314,14 @@ class TPopupOptions { /// 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 final double? height; - /// 外边距;生效边取决于 [placement]。 + /// 交叉轴边缘留白;具体类型由 [placement] 决定。 /// - /// [TPopupPlacement.bottom]:`top > 0` 时贴顶留白(日历式),否则贴底。 - /// [TPopupPlacement.center] 忽略。 - final EdgeInsets margin; + /// * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] + /// * [TPopupPlacement.top] 使用 [TPopupTopInset] + /// * [TPopupPlacement.left] 使用 [TPopupLeftInset] + /// * [TPopupPlacement.right] 使用 [TPopupRightInset] + /// * [TPopupPlacement.center] 不支持 + final TPopupInset? inset; /// 内容区圆角,默认主题大圆角。 final double? radius; @@ -396,7 +399,7 @@ class TPopupOptions { TPopupPlacement? placement, Object? width = _unset, Object? height = _unset, - EdgeInsets? margin, + Object? inset = _unset, Object? radius = _unset, Object? backgroundColor = _unset, bool? showOverlay, @@ -421,10 +424,15 @@ class TPopupOptions { return TPopupOptions( child: child ?? this.child, placement: placement ?? this.placement, - width: identical(width, _unset) ? this.width : (width as num?)?.toDouble(), - height: identical(height, _unset) ? this.height : (height as num?)?.toDouble(), - margin: margin ?? this.margin, - radius: identical(radius, _unset) ? this.radius : (radius as num?)?.toDouble(), + width: + identical(width, _unset) ? this.width : (width as num?)?.toDouble(), + height: identical(height, _unset) + ? this.height + : (height as num?)?.toDouble(), + inset: identical(inset, _unset) ? this.inset : inset as TPopupInset?, + radius: identical(radius, _unset) + ? this.radius + : (radius as num?)?.toDouble(), backgroundColor: identical(backgroundColor, _unset) ? this.backgroundColor : backgroundColor as Color?, @@ -482,7 +490,7 @@ class TPopupOptions { placement: placement, width: width, height: height, - margin: margin, + inset: inset, radius: radius, backgroundColor: backgroundColor, showOverlay: showOverlay, @@ -508,10 +516,13 @@ class TPopupOptions { /// {@nodoc} bool get usesDefaultHeader => _isPopupDefaultHeader(headerBuilder); + /// {@nodoc} bool get usesDefaultCancel => _isPopupDefaultCancel(cancelBuilder); + /// {@nodoc} bool get usesDefaultConfirm => _isPopupDefaultConfirm(confirmBuilder); + /// {@nodoc} bool get usesDefaultClose => _isPopupDefaultClose(closeBuilder); @@ -566,36 +577,39 @@ class TPopupOptions { switch (placement) { case TPopupPlacement.top: if (width != null) { - return 'width is not valid for placement=top; use height + margin.'; + return 'width is not valid for placement=top; use height + inset.'; } - if (margin.bottom > 0) { - return 'margin.bottom is not valid for placement=top.'; + if (inset != null && inset is! TPopupTopInset) { + return 'inset must be TPopupTopInset for placement=top.'; } break; case TPopupPlacement.bottom: if (width != null) { - return 'width is not valid for placement=bottom; use height + margin.'; + return 'width is not valid for placement=bottom; use height + inset.'; + } + if (inset != null && inset is! TPopupBottomInset) { + return 'inset must be TPopupBottomInset for placement=bottom.'; } break; case TPopupPlacement.left: if (height != null) { - return 'height is not valid for placement=left; use width + margin.'; + return 'height is not valid for placement=left; use width + inset.'; } - if (margin.right > 0) { - return 'margin.right is not valid for placement=left.'; + if (inset != null && inset is! TPopupLeftInset) { + return 'inset must be TPopupLeftInset for placement=left.'; } break; case TPopupPlacement.right: if (height != null) { - return 'height is not valid for placement=right; use width + margin.'; + return 'height is not valid for placement=right; use width + inset.'; } - if (margin.left > 0) { - return 'margin.left is not valid for placement=right.'; + if (inset != null && inset is! TPopupRightInset) { + return 'inset must be TPopupRightInset for placement=right.'; } break; case TPopupPlacement.center: - if (margin != EdgeInsets.zero) { - return 'margin is not valid for placement=center.'; + if (inset != null) { + return 'inset is not valid for placement=center.'; } break; } diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart index 659cf4fc1..b9d1e909f 100644 --- a/tdesign-component/lib/src/components/popup/t_popup_types.dart +++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart @@ -5,16 +5,16 @@ part of 't_popup.dart'; /// 与 [TPopupOptions] 类文档中的「字段与 placement」表对应。 /// 方向固定时请用 [TPopupOptions.bottom]、[TPopupOptions.center] 等命名工厂。 enum TPopupPlacement { - /// 自顶部滑入;使用 [TPopupOptions.height]、[TPopupOptions.margin](top/left/right)。 + /// 自顶部滑入;使用 [TPopupOptions.height]、[TPopupOptions.inset]([TPopupTopInset])。 top, - /// 自左侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.margin](left/top/bottom)。 + /// 自左侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.inset]([TPopupLeftInset])。 left, - /// 自右侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.margin](right/top/bottom)。 + /// 自右侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.inset]([TPopupRightInset])。 right, - /// 自底部滑入;默认内置头部;使用 [TPopupOptions.height]、[TPopupOptions.margin]。 + /// 自底部滑入;默认内置头部;使用 [TPopupOptions.height]、[TPopupOptions.inset]([TPopupBottomInset])。 bottom, /// 屏幕居中;使用 [TPopupOptions.closeBuilder] 控制面板外下方关闭区。 diff --git a/tdesign-component/lib/tdesign_flutter.dart b/tdesign-component/lib/tdesign_flutter.dart index 462344551..548aa5a5b 100644 --- a/tdesign-component/lib/tdesign_flutter.dart +++ b/tdesign-component/lib/tdesign_flutter.dart @@ -61,6 +61,11 @@ export 'src/components/popup/t_popup.dart' TPopupOptions, TPopupPlacement, TPopupTrigger, + TPopupInset, + TPopupTopInset, + TPopupBottomInset, + TPopupLeftInset, + TPopupRightInset, TPopupHeaderBuilder, TPopupSlotBuilder, TPopupVisibleChangeCallback; diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart index 529b3c22a..ff3c1d67a 100644 --- a/tdesign-component/test/t_popup_coverage_test.dart +++ b/tdesign-component/test/t_popup_coverage_test.dart @@ -614,7 +614,7 @@ void main() { expect(hasRedPanel, isTrue); }); - testWidgets('bottom margin.bottom 与无 overlay 仍可关闭', (tester) async { + testWidgets('bottom inset.left/right 与无 overlay 仍可关闭', (tester) async { TPopupHandle? handle; await openPopup( tester, @@ -624,7 +624,7 @@ void main() { options: TPopupOptions( placement: TPopupPlacement.bottom, height: 100, - margin: const EdgeInsets.only(bottom: 16), + inset: const TPopupBottomInset(left: 16, right: 16), showOverlay: false, closeOnOverlayClick: false, cancelBuilder: null, @@ -640,13 +640,9 @@ void main() { }); group('PopupLayout 覆盖率补充', () { - const screen = Size(400, 800); - - testWidgets('bottom 无 height 且无 margin.top 贴底', (tester) async { + testWidgets('bottom 无 height 时贴底', (tester) async { final layout = PopupLayout( placement: TPopupPlacement.bottom, - screenSize: screen, - margin: EdgeInsets.zero, ); await tester.pumpWidget( MaterialApp( @@ -669,18 +665,15 @@ void main() { expect( PopupLayout( placement: TPopupPlacement.center, - screenSize: screen, - margin: EdgeInsets.zero, ).alignment, Alignment.center, ); }); - testWidgets('right 默认 drawer 宽度与 margin', (tester) async { + testWidgets('right 默认 drawer 宽度与 inset', (tester) async { final layout = PopupLayout( placement: TPopupPlacement.right, - screenSize: screen, - margin: const EdgeInsets.only(top: 8, bottom: 8, right: 4), + inset: const TPopupRightInset(top: 8, bottom: 8), ); await tester.pumpWidget( MaterialApp( @@ -693,15 +686,13 @@ void main() { ); final positioned = tester.widget(find.byType(Positioned)); expect(positioned.width, PopupLayout.defaultDrawerWidth); - expect(positioned.right, 4); + expect(positioned.right, 0); expect(positioned.top, 8); }); testWidgets('left 默认 drawer 宽度', (tester) async { final layout = PopupLayout( placement: TPopupPlacement.left, - screenSize: screen, - margin: EdgeInsets.zero, ); await tester.pumpWidget( MaterialApp( @@ -751,8 +742,7 @@ void main() { expect(opts.width, 220); expect(opts.height, 220); expect(opts.usesDefaultClose, isTrue); - // center factory 不暴露 margin → 默认零 - expect(opts.margin, EdgeInsets.zero); + expect(opts.inset, isNull); }); test('.top / .left / .right 生成对应 placement + 默认无头部', () { @@ -775,8 +765,7 @@ void main() { test('factory 输出的合法配置在 assertPlacementParams 下零异常', () { final variants = [ TPopupOptions.bottom(child: const SizedBox(), height: 300), - TPopupOptions.center( - child: const SizedBox(), width: 220, height: 220), + TPopupOptions.center(child: const SizedBox(), width: 220, height: 220), TPopupOptions.top(child: const SizedBox(), height: 100), TPopupOptions.left(child: const SizedBox(), width: 280), TPopupOptions.right(child: const SizedBox(), width: 280), @@ -953,8 +942,8 @@ void main() { GestureDetector(onTap: close, child: const Text('应隐藏-左')), confirmBuilder: (_, close) => GestureDetector(onTap: close, child: const Text('应隐藏-右')), - child: const SizedBox( - key: ValueKey('popup-child'), height: 80)), + child: + const SizedBox(key: ValueKey('popup-child'), height: 80)), ); }, ); @@ -1069,7 +1058,8 @@ void main() { expect(find.text('确定'), findsNothing); }); - testWidgets('headerBuilder 自定义 → titleWidget / cancelBuilder / confirmBuilder 全部被忽略', + testWidgets( + 'headerBuilder 自定义 → titleWidget / cancelBuilder / confirmBuilder 全部被忽略', (tester) async { await openPopup( tester, @@ -1102,8 +1092,7 @@ void main() { // 触发源精细化:sentinel vs 自定义 builder 的 close 上报区分 // ============================================================ group('Popup 触发源细分(sentinel vs 自定义 builder)', () { - testWidgets('内置 cancel 按钮点击 → cancel(与 confirm 区分)', - (tester) async { + testWidgets('内置 cancel 按钮点击 → cancel(与 confirm 区分)', (tester) async { TPopupTrigger? lastTrigger; await openPopup( tester, @@ -1128,8 +1117,7 @@ void main() { expect(lastTrigger, TPopupTrigger.cancel); }); - testWidgets('自定义 cancelBuilder 内调 close → cancel', - (tester) async { + testWidgets('自定义 cancelBuilder 内调 close → cancel', (tester) async { TPopupTrigger? lastTrigger; await openPopup( tester, @@ -1158,8 +1146,7 @@ void main() { expect(lastTrigger, TPopupTrigger.cancel); }); - testWidgets('自定义 confirmBuilder 内调 close → confirm', - (tester) async { + testWidgets('自定义 confirmBuilder 内调 close → confirm', (tester) async { TPopupTrigger? lastTrigger; await openPopup( tester, @@ -1188,8 +1175,7 @@ void main() { expect(lastTrigger, TPopupTrigger.confirm); }); - testWidgets('center 自定义 closeBuilder 内调 close → close', - (tester) async { + testWidgets('center 自定义 closeBuilder 内调 close → close', (tester) async { TPopupTrigger? lastTrigger; await openPopup( tester, @@ -1219,8 +1205,7 @@ void main() { expect(lastTrigger, TPopupTrigger.close); }); - testWidgets('headerBuilder 自定义内调 close → custom(无预设动作语义)', - (tester) async { + testWidgets('headerBuilder 自定义内调 close → custom(无预设动作语义)', (tester) async { TPopupTrigger? lastTrigger; await openPopup( tester, diff --git a/tdesign-component/test/t_popup_layout_test.dart b/tdesign-component/test/t_popup_layout_test.dart index 4735b9591..915520b70 100644 --- a/tdesign-component/test/t_popup_layout_test.dart +++ b/tdesign-component/test/t_popup_layout_test.dart @@ -5,13 +5,10 @@ import 'package:tdesign_flutter/tdesign_flutter.dart'; void main() { group('PopupLayout', () { - const screen = Size(400, 800); - - testWidgets('top placement 使用 height 与 margin', (tester) async { + testWidgets('top placement 使用 height 与左右 inset', (tester) async { final layout = PopupLayout( placement: TPopupPlacement.top, - screenSize: screen, - margin: const EdgeInsets.only(top: 8, left: 4, right: 4), + inset: const TPopupTopInset(left: 4, right: 6), height: 100, ); await tester.pumpWidget( @@ -24,15 +21,16 @@ void main() { ), ); final positioned = tester.widget(find.byType(Positioned)); - expect(positioned.top, 8); + expect(positioned.top, 0); + expect(positioned.left, 4); + expect(positioned.right, 6); expect(positioned.height, 100); }); - testWidgets('bottom placement 含 margin.top 与固定 height', (tester) async { + testWidgets('bottom placement 使用左右 inset 与固定 height', (tester) async { final layout = PopupLayout( placement: TPopupPlacement.bottom, - screenSize: screen, - margin: const EdgeInsets.only(top: 100), + inset: const TPopupBottomInset(left: 12, right: 20), height: 200, ); await tester.pumpWidget( @@ -45,15 +43,15 @@ void main() { ), ); final positioned = tester.widget(find.byType(Positioned)); - expect(positioned.top, 100); + expect(positioned.left, 12); + expect(positioned.right, 20); + expect(positioned.bottom, 0); expect(positioned.height, 200); }); - testWidgets('bottom 无 height 有 margin.top 计算高度', (tester) async { + testWidgets('bottom 无 height 时贴底', (tester) async { final layout = PopupLayout( placement: TPopupPlacement.bottom, - screenSize: screen, - margin: const EdgeInsets.only(top: 50, bottom: 10), ); await tester.pumpWidget( MaterialApp( @@ -67,38 +65,54 @@ void main() { ), ); final positioned = tester.widget(find.byType(Positioned)); - expect(positioned.top, 50); - expect(positioned.height, screen.height - 50 - 10); + expect(positioned.bottom, 0); + expect(positioned.top, isNull); + expect(positioned.height, isNull); }); - testWidgets('left / right 使用默认或自定义 width', (tester) async { - for (final p in [TPopupPlacement.left, TPopupPlacement.right]) { - final layout = PopupLayout( - placement: p, - screenSize: screen, - margin: const EdgeInsets.only(top: 56), - width: 300, - ); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: Stack( - children: [layout.wrapPositioned(child: const SizedBox())]), + testWidgets('left / right 使用默认或自定义 width 与上下 inset', (tester) async { + final left = PopupLayout( + placement: TPopupPlacement.left, + inset: const TPopupLeftInset(top: 56, bottom: 12), + width: 300, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: + Stack(children: [left.wrapPositioned(child: const SizedBox())]), + ), + ), + ); + var positioned = tester.widget(find.byType(Positioned)); + expect(positioned.width, 300); + expect(positioned.top, 56); + expect(positioned.bottom, 12); + + final right = PopupLayout( + placement: TPopupPlacement.right, + inset: const TPopupRightInset(top: 8, bottom: 10), + width: 260, + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Stack( + children: [right.wrapPositioned(child: const SizedBox())], ), ), - ); - final positioned = tester.widget(find.byType(Positioned)); - expect(positioned.width, 300); - expect(positioned.top, 56); - } + ), + ); + positioned = tester.widget(find.byType(Positioned)); + expect(positioned.width, 260); + expect(positioned.top, 8); + expect(positioned.bottom, 10); }); testWidgets('center placement 仅 Center 包裹(尺寸由 PopupShell 控制)', (tester) async { final layout = PopupLayout( placement: TPopupPlacement.center, - screenSize: screen, - margin: EdgeInsets.zero, width: 200, height: 150, ); @@ -120,7 +134,8 @@ void main() { ), ); expect(find.byType(Center), findsOneWidget); - final box = tester.widget(find.byKey(const ValueKey('content'))); + final box = + tester.widget(find.byKey(const ValueKey('content'))); expect(box.width, 200); expect(box.height, 150); }); @@ -128,37 +143,27 @@ void main() { test('slideOffset 五向偏移', () { final layout = PopupLayout( placement: TPopupPlacement.top, - screenSize: screen, - margin: EdgeInsets.zero, ); expect(layout.slideOffset(0), const Offset(0, -1)); expect(layout.slideOffset(1), const Offset(0, 0)); final bottom = PopupLayout( placement: TPopupPlacement.bottom, - screenSize: screen, - margin: EdgeInsets.zero, ); expect(bottom.slideOffset(0), const Offset(0, 1)); final left = PopupLayout( placement: TPopupPlacement.left, - screenSize: screen, - margin: EdgeInsets.zero, ); expect(left.slideOffset(0.5), const Offset(-0.5, 0)); final right = PopupLayout( placement: TPopupPlacement.right, - screenSize: screen, - margin: EdgeInsets.zero, ); expect(right.slideOffset(0.5), const Offset(0.5, 0)); final center = PopupLayout( placement: TPopupPlacement.center, - screenSize: screen, - margin: EdgeInsets.zero, ); expect(center.slideOffset(0.5), Offset.zero); }); @@ -167,8 +172,6 @@ void main() { (tester) async { final layout = PopupLayout( placement: TPopupPlacement.center, - screenSize: screen, - margin: EdgeInsets.zero, width: 100, height: 80, ); @@ -187,29 +190,16 @@ void main() { expect(center.child, isA()); }); - test('resolvedMargin center 为零', () { - final layout = PopupLayout( - placement: TPopupPlacement.center, - screenSize: screen, - margin: const EdgeInsets.all(20), - ); - expect(layout.resolvedMargin(), EdgeInsets.zero); - }); - test('alignment 各方向', () { expect( PopupLayout( placement: TPopupPlacement.top, - screenSize: screen, - margin: EdgeInsets.zero, ).alignment, Alignment.topCenter, ); expect( PopupLayout( placement: TPopupPlacement.right, - screenSize: screen, - margin: EdgeInsets.zero, ).alignment, Alignment.centerRight, ); diff --git a/tdesign-component/test/t_popup_options_test.dart b/tdesign-component/test/t_popup_options_test.dart index 96fa4e9d7..0a4d5032e 100644 --- a/tdesign-component/test/t_popup_options_test.dart +++ b/tdesign-component/test/t_popup_options_test.dart @@ -168,12 +168,12 @@ void main() { ); }); - test('assertPlacementParams 各 placement 的 margin 越界项也抛错', () { + test('assertPlacementParams 各 placement 的 inset 类型错位也抛错', () { expect( () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.top, - margin: const EdgeInsets.only(bottom: 10), + inset: const TPopupBottomInset(left: 10), ).assertPlacementParams(), throwsA(isA()), ); @@ -181,7 +181,7 @@ void main() { () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.right, - margin: const EdgeInsets.only(left: 10), + inset: const TPopupLeftInset(top: 10), ).assertPlacementParams(), throwsA(isA()), ); @@ -189,7 +189,7 @@ void main() { () => TPopupOptions( child: const SizedBox(), placement: TPopupPlacement.center, - margin: const EdgeInsets.all(10), + inset: const TPopupTopInset(left: 10), ).assertPlacementParams(), throwsA(isA()), ); @@ -197,7 +197,8 @@ void main() { test('assertPlacementParams 合法配置不抛错', () { // 各 placement 用对应合法字段 - expect(() => TPopupOptions(child: const SizedBox()).assertPlacementParams(), + expect( + () => TPopupOptions(child: const SizedBox()).assertPlacementParams(), returnsNormally); expect( () => TPopupOptions( @@ -213,7 +214,7 @@ void main() { child: const SizedBox(), placement: TPopupPlacement.left, width: 280, - margin: const EdgeInsets.only(top: 10, bottom: 10, left: 10), + inset: const TPopupLeftInset(top: 10, bottom: 10), ).assertPlacementParams(), returnsNormally, ); diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart index 9c2fa9839..c91317f91 100644 --- a/tdesign-component/test/t_popup_test.dart +++ b/tdesign-component/test/t_popup_test.dart @@ -528,15 +528,15 @@ void main() { await tester.pump(); }); - testWidgets('margin.top 底部日历式布局', (tester) async { + testWidgets('right inset.top 可控制顶部留白', (tester) async { await openPopup( tester, onPressed: () { TPopup.show( tester.element(find.text('open')), options: TPopupOptions( - placement: TPopupPlacement.bottom, - margin: const EdgeInsets.only(top: 80), + placement: TPopupPlacement.right, + inset: const TPopupRightInset(top: 80), child: const SizedBox(height: 200)), ); }, diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md index e2857d808..45ae8d044 100644 --- a/tdesign-site/src/popup/README.md +++ b/tdesign-site/src/popup/README.md @@ -403,9 +403,9 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
-  Widget _buildApiMarginTop(BuildContext context) {
+  Widget _buildApiInset(BuildContext context) {
     return TButton(
-      text: 'bottom margin.top',
+      text: 'bottom inset.left/right',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
@@ -415,8 +415,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           context,
           options: TPopupOptions.bottom(
               height: 320,
-              margin: const EdgeInsets.only(top: 120, left: 16, right: 16),
-              titleBuilder: (_) => TText('日历式留白'),
+              inset: const TPopupBottomInset(left: 16, right: 16),
+              titleWidget: TText('左右留白'),
               child: Container(
                 height: 240,
                 color: TTheme.of(context).bgColorContainer,
@@ -578,10 +578,10 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
  | [TPopupPlacement] | 头部 / 关闭 | 尺寸 |
  |-------------------|-------------|------|
- | [TPopupPlacement.bottom] | [headerBuilder]、[titleBuilder]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] |
+| [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[inset] |
  | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] |
- | [TPopupPlacement.top] | — | [height]、[margin] |
- | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[margin] |
+| [TPopupPlacement.top] | — | [height]、[inset] |
+| [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[inset] |
 
  ## Builder 三态([headerBuilder]、[cancelBuilder]、[confirmBuilder]、[closeBuilder])
 
@@ -591,7 +591,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  | 显式 `null` | 隐藏该区域 |
  | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
 
- [titleBuilder] 默认为 `null`,表示无标题文案。
+[titleWidget] 默认为 `null`,表示无标题文案。
 
  生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 #### 默认构造方法
@@ -608,7 +608,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | duration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 |
 | height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
-| margin | EdgeInsets | EdgeInsets.zero | 外边距;生效边取决于 [placement]。 |
+| inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
 | onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |

From 235f250b1c1652012b4d1e30bc6ca1f96b1420b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Mon, 25 May 2026 17:36:52 +0800
Subject: [PATCH 15/27] refactor(popup): update TPopup and _PopupTracker to
 allow multiple handles and improve documentation

---
 .../src/components/popup/_popup_tracker.dart  | 10 +-----
 .../lib/src/components/popup/t_popup.dart     |  9 -----
 tdesign-component/test/t_popup_test.dart      | 33 ++++++++++++++-----
 tdesign-site/src/popup/README.md              |  2 +-
 4 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/tdesign-component/lib/src/components/popup/_popup_tracker.dart b/tdesign-component/lib/src/components/popup/_popup_tracker.dart
index 962d74bcf..72bfbb9bd 100644
--- a/tdesign-component/lib/src/components/popup/_popup_tracker.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_tracker.dart
@@ -1,6 +1,6 @@
 part of 't_popup.dart';
 
-/// 库内:按 [Navigator] 记录 [TPopupHandle] 栈,用于嵌套与 [TPopup.show] 防重复打开。
+/// 库内:按 [Navigator] 记录 [TPopupHandle] 栈,用于嵌套与关闭清理。
 abstract class _PopupTracker {
   static final Map> _stacks = {};
 
@@ -14,12 +14,4 @@ abstract class _PopupTracker {
       _stacks.remove(navigator);
     }
   }
-
-  static TPopupHandle? top(NavigatorState navigator) {
-    final stack = _stacks[navigator];
-    if (stack == null || stack.isEmpty) {
-      return null;
-    }
-    return stack.last;
-  }
 }
diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart
index 39f5ff44a..8ed7352ec 100644
--- a/tdesign-component/lib/src/components/popup/t_popup.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup.dart
@@ -62,8 +62,6 @@ final class TPopup {
   /// 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、
   /// [TPopupHandle.isShowing] 控制与查询。
   ///
-  /// 同一 [Navigator] 上若已有展示中的浮层,重复调用会返回已有 handle(防连点)。
-  ///
   /// [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。
   ///
   /// [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。
@@ -79,13 +77,6 @@ final class TPopup {
       rootNavigator: useRootNavigator,
     );
 
-    final existing = _PopupTracker.top(navigator);
-    if (existing != null &&
-        existing.isShowing &&
-        ModalRoute.of(context) is! _PopupNavigatorRoute) {
-      return existing;
-    }
-
     final handle = TPopupHandle._(
       options: options,
       navigatorContext: navigatorContext,
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index c91317f91..1a394b3b7 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -546,8 +546,9 @@ void main() {
   });
 
   group('TPopupHandle / Tracker', () {
-    testWidgets('外层重复 show 第二次无效(返回同一 handle)', (tester) async {
+    testWidgets('重复 show 可叠加打开(返回不同 handle)', (tester) async {
       TPopupHandle? first;
+      TPopupHandle? second;
       await openPopup(
         tester,
         onPressed: () {
@@ -557,21 +558,37 @@ void main() {
             options: TPopupOptions(
                 placement: TPopupPlacement.bottom,
                 height: 80,
-                child: const SizedBox(height: 40)),
+                cancelBuilder: null,
+                confirmBuilder: null,
+                child: const SizedBox(height: 40, child: Text('first'))),
           );
-          final second = TPopup.show(
+          second = TPopup.show(
             ctx,
             options: TPopupOptions(
-                placement: TPopupPlacement.bottom,
+                placement: TPopupPlacement.center,
+                width: 120,
                 height: 80,
-                child: const SizedBox(height: 40)),
+                closeBuilder: null,
+                child: const SizedBox(width: 120, height: 80, child: Text('second'))),
           );
-          expect(second.isShowing, isTrue);
-          expect(identical(first, second), isTrue);
+          expect(second!.isShowing, isTrue);
+          expect(identical(first, second), isFalse);
         },
       );
       await tester.pumpAndSettle();
-      first?.close();
+      expect(first!.isShowing, isTrue);
+      expect(second!.isShowing, isTrue);
+      expect(find.text('first'), findsOneWidget);
+      expect(find.text('second'), findsOneWidget);
+
+      second!.close();
+      await tester.pumpAndSettle();
+      expect(second!.isShowing, isFalse);
+      expect(first!.isShowing, isTrue);
+      expect(find.text('second'), findsNothing);
+      expect(find.text('first'), findsOneWidget);
+
+      first!.close();
       await tester.pumpAndSettle();
     });
 
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 45ae8d044..93d652550 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -556,7 +556,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| show |  |   required BuildContext context,  required TPopupOptions options,  BuildContext? navigatorContext,  bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。     [context] 用于查找 [Navigator] 并展示浮层。     [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。     返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、   [TPopupHandle.isShowing] 控制与查询。     同一 [Navigator] 上若已有展示中的浮层,重复调用会返回已有 handle(防连点)。     [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。     [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
+| show |  |   required BuildContext context,  required TPopupOptions options,  BuildContext? navigatorContext,  bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。     [context] 用于查找 [Navigator] 并展示浮层。     [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。     返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、   [TPopupHandle.isShowing] 控制与查询。     [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。     [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
 
 ```
 ```

From e1f697c485d3e77daf8be54f33f5b5b8cc85808c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Mon, 25 May 2026 18:19:04 +0800
Subject: [PATCH 16/27] refactor(popup): replace titleBuilder with titleWidget
 in multiple popup examples and documentation

---
 .../assets/code/popup._buildApiMarginTop.txt      |  2 +-
 .../code/popup._buildApiShowOverlayFalse.txt      |  2 +-
 .../assets/code/popup._buildNestedPopup.txt       |  2 +-
 ...popup._buildPopFromBottomWithCloseAndTitle.txt |  2 +-
 ...p._buildPopFromBottomWithOperationAndTitle.txt |  2 +-
 .../lib/src/components/popup/t_popup.dart         |  2 ++
 tdesign-component/test/t_popup_coverage_test.dart |  2 +-
 tdesign-component/test/t_popup_test.dart          |  4 ++--
 tdesign-site/src/popup/README.md                  | 15 ++++++++-------
 9 files changed, 18 insertions(+), 15 deletions(-)

diff --git a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt
index 43846eacc..3b06a0c90 100644
--- a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt
+++ b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt
@@ -12,7 +12,7 @@
           options: TPopupOptions.bottom(
               height: 320,
               margin: const EdgeInsets.only(top: 120, left: 16, right: 16),
-              titleBuilder: (_) => TText('日历式留白'),
+              titleWidget: TText('日历式留白'),
               child: Container(
                 height: 240,
                 color: TTheme.of(context).bgColorContainer,
diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
index 4fe8fbf66..c7e2cf728 100644
--- a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
+++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
@@ -13,7 +13,7 @@
               height: 280,
               showOverlay: false,
               // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口)
-              titleBuilder: (_) => const TText('无蒙层'),
+              titleWidget: const TText('无蒙层'),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,
diff --git a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt
index 61479a115..2ffd8f295 100644
--- a/tdesign-component/example/assets/code/popup._buildNestedPopup.txt
+++ b/tdesign-component/example/assets/code/popup._buildNestedPopup.txt
@@ -35,7 +35,7 @@
                               innerContext,
                               options: TPopupOptions.bottom(
                                 height: 280,
-                                titleBuilder: (_) => const TText('内层标题'),
+                                titleWidget: const TText('内层标题'),
                                 child: Container(
                                   height: 160,
                                   color: TTheme.of(innerContext)
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
index f5c92bfb3..ad56c697c 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
@@ -16,7 +16,7 @@
                 textColor: TTheme.of(context).textColorSecondary,
                 font: TTheme.of(context).fontBodyLarge,
               ),
-              titleBuilder: (_) => Row(
+              titleWidget: Row(
                 mainAxisSize: MainAxisSize.min,
                 children: [
                   Icon(TIcons.info_circle,
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt
index d9a005c95..88e0ea6f0 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithOperationAndTitle.txt
@@ -11,7 +11,7 @@
           context,
           options: TPopupOptions.bottom(
               height: 280,
-              titleBuilder: (_) => TText('标题文字'),
+              titleWidget: TText('标题文字'),
               child: Container(height: 200)),
         );
       },
diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart
index 8ed7352ec..d263cbca4 100644
--- a/tdesign-component/lib/src/components/popup/t_popup.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup.dart
@@ -34,6 +34,7 @@ part 't_popup_types.dart';
 /// 弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。
 ///
 /// 通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。
+/// 多次调用 [show] 会继续压入新的浮层路由,可用于叠加展示。
 ///
 /// **示例**
 ///
@@ -61,6 +62,7 @@ final class TPopup {
   ///
   /// 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、
   /// [TPopupHandle.isShowing] 控制与查询。
+  /// 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。
   ///
   /// [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。
   ///
diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart
index ff3c1d67a..00cbd8e53 100644
--- a/tdesign-component/test/t_popup_coverage_test.dart
+++ b/tdesign-component/test/t_popup_coverage_test.dart
@@ -174,7 +174,7 @@ void main() {
       expect(find.byType(Positioned), findsWidgets);
     });
 
-    testWidgets('center showClose=false 无下方关闭', (tester) async {
+    testWidgets('center closeBuilder=null 无下方关闭', (tester) async {
       await openPopup(
         tester,
         onPressed: () {
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index 1a394b3b7..238ab5c4f 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -292,7 +292,7 @@ void main() {
       expect(find.text('自定义取消'), findsOneWidget);
     });
 
-    testWidgets('bottom showHeader=false 不渲染头部', (tester) async {
+    testWidgets('bottom headerBuilder=null 不渲染头部', (tester) async {
       await openPopup(
         tester,
         onPressed: () {
@@ -394,7 +394,7 @@ void main() {
       expect(find.byIcon(TIcons.close_circle), findsOneWidget);
     });
 
-    testWidgets('center showClose=false 不显示关闭区', (tester) async {
+    testWidgets('center closeBuilder=null 不显示关闭区', (tester) async {
       await openPopup(
         tester,
         onPressed: () {
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 93d652550..03f018e0c 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -189,7 +189,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           context,
           options: TPopupOptions.bottom(
               height: 280,
-              titleBuilder: (_) => TText('标题文字'),
+              titleWidget: TText('标题文字'),
               child: Container(height: 200)),
         );
       },
@@ -221,7 +221,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
                 textColor: TTheme.of(context).textColorSecondary,
                 font: TTheme.of(context).fontBodyLarge,
               ),
-              titleBuilder: (_) => Row(
+              titleWidget: Row(
                 mainAxisSize: MainAxisSize.min,
                 children: [
                   Icon(TIcons.info_circle,
@@ -366,7 +366,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
                               innerContext,
                               options: TPopupOptions.bottom(
                                 height: 280,
-                                titleBuilder: (_) => const TText('内层标题'),
+                                titleWidget: const TText('内层标题'),
                                 child: Container(
                                   height: 160,
                                   color: TTheme.of(innerContext)
@@ -448,7 +448,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
               height: 280,
               showOverlay: false,
               // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口)
-              titleBuilder: (_) => const TText('无蒙层'),
+              titleWidget: const TText('无蒙层'),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,
@@ -528,6 +528,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。
 
  通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。
+ 多次调用 [show] 会继续压入新的浮层路由,可用于叠加展示。
 
  **示例**
 
@@ -535,7 +536,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  final handle = TPopup.show(
    context,
    options: TPopupOptions.bottom(
-     titleBuilder: (_) => const Text('标题'),
+     titleWidget: const Text('标题'),
      child: MyPanel(),
    ),
  );
@@ -556,7 +557,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| show |  |   required BuildContext context,  required TPopupOptions options,  BuildContext? navigatorContext,  bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。     [context] 用于查找 [Navigator] 并展示浮层。     [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。     返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、   [TPopupHandle.isShowing] 控制与查询。     [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。     [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
+| show |  |   required BuildContext context,  required TPopupOptions options,  BuildContext? navigatorContext,  bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。     [context] 用于查找 [Navigator] 并展示浮层。     [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。     返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、   [TPopupHandle.isShowing] 控制与查询。     重复调用会继续 push 新的浮层;若需互斥请在业务层管理。     [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。     [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
 
 ```
 ```
@@ -621,7 +622,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| titleBuilder | WidgetBuilder? | - | bottom 标题;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
+| titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
 | width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
 
 

From 37f5d7f550081e9fb83ca15bf18729d326010bd0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Mon, 25 May 2026 18:41:33 +0800
Subject: [PATCH 17/27] docs(popup): enhance documentation and improve close
 behavior in TPopup components

---
 .../components/popup/_popup_center_close.dart |  4 +-
 .../src/components/popup/_popup_route.dart    |  4 ++
 .../src/components/popup/_popup_shell.dart    |  3 +-
 .../src/components/popup/_popup_tracker.dart  |  8 +++
 .../lib/src/components/popup/t_popup.dart     |  2 +-
 .../src/components/popup/t_popup_handle.dart  | 43 +++++++++---
 .../src/components/popup/t_popup_options.dart |  6 +-
 tdesign-component/test/t_popup_test.dart      | 65 +++++++++++++++++++
 tdesign-site/src/popup/README.md              |  8 +--
 9 files changed, 123 insertions(+), 20 deletions(-)

diff --git a/tdesign-component/lib/src/components/popup/_popup_center_close.dart b/tdesign-component/lib/src/components/popup/_popup_center_close.dart
index 4a2b0dcdf..778f1535e 100644
--- a/tdesign-component/lib/src/components/popup/_popup_center_close.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_center_close.dart
@@ -1,6 +1,6 @@
 part of 't_popup.dart';
 
-/// center 面板外关闭控件:默认图标与自定义关闭槽位都上报 [TPopupTrigger.close]。
+/// center 面板外下方关闭控件:默认图标与自定义关闭槽位都上报 [TPopupTrigger.close]。
 Widget buildPopupCenterCloseControl({
   required BuildContext context,
   required TPopupOptions options,
@@ -22,7 +22,7 @@ Widget buildPopupCenterCloseControl({
   return options.closeBuilder!(context, onCloseSlotTap);
 }
 
-/// center 布局:内容面板 + 外置关闭区。
+/// center 布局:内容面板 + 面板外下方关闭区。
 class PopupCenterUnderClose extends StatelessWidget {
   const PopupCenterUnderClose({
     super.key,
diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart
index 10adf4ede..5d8bd792b 100644
--- a/tdesign-component/lib/src/components/popup/_popup_route.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_route.dart
@@ -216,6 +216,10 @@ class _PopupNavigatorRoute extends PopupRoute {
 
   @override
   void dispose() {
+    if (!_closedFired) {
+      _closedFired = true;
+      options.onClosed?.call();
+    }
     if (_animationListenerAttached) {
       animation?.removeStatusListener(_onAnimationStatus);
     }
diff --git a/tdesign-component/lib/src/components/popup/_popup_shell.dart b/tdesign-component/lib/src/components/popup/_popup_shell.dart
index 97eec708f..b4817d010 100644
--- a/tdesign-component/lib/src/components/popup/_popup_shell.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_shell.dart
@@ -1,6 +1,7 @@
 part of 't_popup.dart';
 
-/// 浮层内容外壳:圆角、Header(仅 bottom)、child;center 由 [PopupCenterUnderClose] 接管下方关闭区。
+/// 浮层内容外壳:圆角、[PopupHeader](仅 bottom)与主体内容;
+/// center 由 [PopupCenterUnderClose] 接管面板外下方关闭区。
 class PopupShell extends StatelessWidget {
   const PopupShell({
     super.key,
diff --git a/tdesign-component/lib/src/components/popup/_popup_tracker.dart b/tdesign-component/lib/src/components/popup/_popup_tracker.dart
index 72bfbb9bd..2ed042d7c 100644
--- a/tdesign-component/lib/src/components/popup/_popup_tracker.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_tracker.dart
@@ -14,4 +14,12 @@ abstract class _PopupTracker {
       _stacks.remove(navigator);
     }
   }
+
+  static TPopupHandle? top(NavigatorState navigator) {
+    final stack = _stacks[navigator];
+    if (stack == null || stack.isEmpty) {
+      return null;
+    }
+    return stack.last;
+  }
 }
diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart
index d263cbca4..eeb80bc93 100644
--- a/tdesign-component/lib/src/components/popup/t_popup.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup.dart
@@ -31,7 +31,7 @@ part 't_popup_inset.dart';
 part 't_popup_options.dart';
 part 't_popup_types.dart';
 
-/// 弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。
+/// 弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作区、center 面板外下方关闭区。
 ///
 /// 通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。
 /// 多次调用 [show] 会继续压入新的浮层路由,可用于叠加展示。
diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
index 8e0906603..884d3d351 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
@@ -66,12 +66,16 @@ class TPopupHandle {
     _PopupNavigatorRoute? route;
 
     void closeWithTrigger(TPopupTrigger trigger, [Object? result]) {
-      if (!isShowing) {
+      final currentRoute = route;
+      if (!isShowing || currentRoute == null) {
         return;
       }
-      _markClosing();
-      route?.fireCloseStart(trigger);
-      navigator.pop(result);
+      _closeRoute(
+        navigator: navigator,
+        route: currentRoute,
+        trigger: trigger,
+        result: result,
+      );
     }
 
     route = _PopupNavigatorRoute(
@@ -93,14 +97,20 @@ class TPopupHandle {
   ///
   /// [result] 可选,作为 [Navigator.pop] 的返回值。
   ///
-  /// 已关闭或未展示时调用无副作用。嵌套浮层须使用对应层的 handle 关闭。
+  /// 已关闭或未展示时调用无副作用。
+  /// 嵌套浮层场景下会关闭当前 handle 对应的那一层,而不会误关栈顶其它浮层。
   void close([Object? result]) {
-    if (!isShowing) {
+    final route = _route;
+    final navigator = route?.navigator ?? _lastNavigator;
+    if (!isShowing || route == null || navigator == null) {
       return;
     }
-    _markClosing();
-    _route?.fireCloseStart(TPopupTrigger.api);
-    _route!.navigator?.pop(result);
+    _closeRoute(
+      navigator: navigator,
+      route: route,
+      trigger: TPopupTrigger.api,
+      result: result,
+    );
   }
 
   NavigatorState? _resolveNavigator(BuildContext? context) {
@@ -119,6 +129,21 @@ class TPopupHandle {
     _isClosed = true;
   }
 
+  void _closeRoute({
+    required NavigatorState navigator,
+    required _PopupNavigatorRoute route,
+    required TPopupTrigger trigger,
+    Object? result,
+  }) {
+    _markClosing();
+    route.fireCloseStart(trigger);
+    if (identical(_PopupTracker.top(navigator), this)) {
+      navigator.pop(result);
+      return;
+    }
+    navigator.removeRoute(route, result);
+  }
+
   void _detachRoute() {
     _isClosed = true;
     _route = null;
diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart
index 890a8ab3f..c787be35d 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_options.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart
@@ -16,7 +16,7 @@ const Object _unset = Object();
 ///
 /// ## 字段与 [TPopupPlacement]
 ///
-/// | [TPopupPlacement] | 头部 / 关闭 | 尺寸 |
+/// | [TPopupPlacement] | 头部 / 关闭区 | 尺寸 |
 /// |-------------------|-------------|------|
 /// | [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[inset] |
 /// | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] |
@@ -31,7 +31,7 @@ const Object _unset = Object();
 /// | 显式 `null` | 隐藏该区域 |
 /// | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
 ///
-/// [titleWidget] 默认为 `null`,表示无标题文案。
+/// [titleWidget] 默认为 `null`,表示无标题内容。
 ///
 /// 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 class TPopupOptions {
@@ -122,7 +122,7 @@ class TPopupOptions {
 
   /// 创建 [TPopupPlacement.center] 配置。
   ///
-  /// 固定 [placement] 为 [TPopupPlacement.center];默认面板外下方圆形关闭按钮。
+  /// 固定 [placement] 为 [TPopupPlacement.center];默认展示面板外下方圆形关闭按钮。
   factory TPopupOptions.center({
     required Widget child,
     double? width,
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index 238ab5c4f..3af4e78f0 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -592,6 +592,71 @@ void main() {
       await tester.pumpAndSettle();
     });
 
+    testWidgets('外层 handle.close 在内层展示时只关闭外层', (tester) async {
+      TPopupHandle? outerHandle;
+      TPopupHandle? innerHandle;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          outerHandle = TPopup.show(
+            tester.element(find.text('open')),
+            options: TPopupOptions(
+                placement: TPopupPlacement.bottom,
+                height: 160,
+                cancelBuilder: null,
+                confirmBuilder: null,
+                child: Builder(
+                  builder: (ctx) {
+                    return Column(
+                      children: [
+                        const Text('outer'),
+                        ElevatedButton(
+                          onPressed: () {
+                            innerHandle = TPopup.show(
+                              ctx,
+                              options: TPopupOptions(
+                                placement: TPopupPlacement.center,
+                                width: 120,
+                                height: 80,
+                                closeBuilder: null,
+                                child: const SizedBox(
+                                  width: 120,
+                                  height: 80,
+                                  child: Text('inner'),
+                                ),
+                              ),
+                            );
+                          },
+                          child: const Text('open inner'),
+                        ),
+                      ],
+                    );
+                  },
+                )),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(find.text('outer'), findsOneWidget);
+
+      await tester.tap(find.text('open inner'));
+      await tester.pumpAndSettle();
+      expect(find.text('outer'), findsOneWidget);
+      expect(find.text('inner'), findsOneWidget);
+
+      outerHandle!.close();
+      await tester.pumpAndSettle();
+      expect(outerHandle!.isShowing, isFalse);
+      expect(innerHandle!.isShowing, isTrue);
+      expect(find.text('outer'), findsNothing);
+      expect(find.text('inner'), findsOneWidget);
+
+      innerHandle!.close();
+      await tester.pumpAndSettle();
+      expect(find.text('inner'), findsNothing);
+    });
+
     testWidgets('系统返回键关闭', (tester) async {
       var closedCount = 0;
       late BuildContext hostContext;
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 03f018e0c..cd71478c4 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -525,7 +525,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 ## API
 ### TPopup
 #### 简介
-弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。
+弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作区、center 面板外下方关闭区。
 
  通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。
  多次调用 [show] 会继续压入新的浮层路由,可用于叠加展示。
@@ -577,7 +577,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
  ## 字段与 [TPopupPlacement]
 
- | [TPopupPlacement] | 头部 / 关闭 | 尺寸 |
+ | [TPopupPlacement] | 头部 / 关闭区 | 尺寸 |
  |-------------------|-------------|------|
 | [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[inset] |
  | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] |
@@ -592,7 +592,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  | 显式 `null` | 隐藏该区域 |
  | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
 
-[titleWidget] 默认为 `null`,表示无标题文案。
+[titleWidget] 默认为 `null`,表示无标题内容。
 
  生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 #### 默认构造方法
@@ -636,7 +636,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  蒙层、动画、生命周期等字段语义见同名成员文档。 |
 | TPopupOptions.center  | 创建 [TPopupPlacement.center] 配置。
 
- 固定 [placement] 为 [TPopupPlacement.center];默认面板外下方圆形关闭按钮。 |
+ 固定 [placement] 为 [TPopupPlacement.center];默认展示面板外下方圆形关闭按钮。 |
 | TPopupOptions.left  | 创建 [TPopupPlacement.left] 配置。
 
  固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。 |

From 91a4a39b0a663e5b1193ae07c46056fa3ea2fb44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Tue, 26 May 2026 09:43:11 +0800
Subject: [PATCH 18/27] refactor(popup): rename `duration` to
 `animationDuration` in TPopup options and related components for clarity

---
 .../src/components/popup/_popup_route.dart    |  4 +--
 .../src/components/popup/t_popup_options.dart | 30 +++++++++----------
 .../test/t_popup_coverage_test.dart           |  6 ++--
 tdesign-site/src/popup/README.md              |  6 ++--
 4 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart
index 5d8bd792b..0f6d6dead 100644
--- a/tdesign-component/lib/src/components/popup/_popup_route.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_route.dart
@@ -36,10 +36,10 @@ class _PopupNavigatorRoute extends PopupRoute {
   }
 
   @override
-  Duration get transitionDuration => options.duration;
+  Duration get transitionDuration => options.animationDuration;
 
   @override
-  Duration get reverseTransitionDuration => options.duration;
+  Duration get reverseTransitionDuration => options.animationDuration;
 
   @override
   bool get barrierDismissible => false;
diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart
index c787be35d..986142979 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_options.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart
@@ -52,7 +52,7 @@ class TPopupOptions {
     this.overlayOpacity,
     this.preventScrollThrough = true,
     this.destroyOnClose = false,
-    this.duration = const Duration(milliseconds: 240),
+    this.animationDuration = const Duration(milliseconds: 240),
     this.headerBuilder = _kPopupDefaultHeader,
     this.titleWidget,
     this.cancelBuilder = _kPopupDefaultCancel,
@@ -86,7 +86,7 @@ class TPopupOptions {
     double? overlayOpacity,
     bool preventScrollThrough = true,
     bool destroyOnClose = false,
-    Duration duration = const Duration(milliseconds: 240),
+    Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
     VoidCallback? onOpened,
     VoidCallback? onClose,
@@ -111,7 +111,7 @@ class TPopupOptions {
         overlayOpacity: overlayOpacity,
         preventScrollThrough: preventScrollThrough,
         destroyOnClose: destroyOnClose,
-        duration: duration,
+        animationDuration: animationDuration,
         onOpen: onOpen,
         onOpened: onOpened,
         onClose: onClose,
@@ -136,7 +136,7 @@ class TPopupOptions {
     double? overlayOpacity,
     bool preventScrollThrough = true,
     bool destroyOnClose = false,
-    Duration duration = const Duration(milliseconds: 240),
+    Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
     VoidCallback? onOpened,
     VoidCallback? onClose,
@@ -158,7 +158,7 @@ class TPopupOptions {
         overlayOpacity: overlayOpacity,
         preventScrollThrough: preventScrollThrough,
         destroyOnClose: destroyOnClose,
-        duration: duration,
+        animationDuration: animationDuration,
         onOpen: onOpen,
         onOpened: onOpened,
         onClose: onClose,
@@ -182,7 +182,7 @@ class TPopupOptions {
     double? overlayOpacity,
     bool preventScrollThrough = true,
     bool destroyOnClose = false,
-    Duration duration = const Duration(milliseconds: 240),
+    Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
     VoidCallback? onOpened,
     VoidCallback? onClose,
@@ -203,7 +203,7 @@ class TPopupOptions {
         overlayOpacity: overlayOpacity,
         preventScrollThrough: preventScrollThrough,
         destroyOnClose: destroyOnClose,
-        duration: duration,
+        animationDuration: animationDuration,
         onOpen: onOpen,
         onOpened: onOpened,
         onClose: onClose,
@@ -227,7 +227,7 @@ class TPopupOptions {
     double? overlayOpacity,
     bool preventScrollThrough = true,
     bool destroyOnClose = false,
-    Duration duration = const Duration(milliseconds: 240),
+    Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
     VoidCallback? onOpened,
     VoidCallback? onClose,
@@ -248,7 +248,7 @@ class TPopupOptions {
         overlayOpacity: overlayOpacity,
         preventScrollThrough: preventScrollThrough,
         destroyOnClose: destroyOnClose,
-        duration: duration,
+        animationDuration: animationDuration,
         onOpen: onOpen,
         onOpened: onOpened,
         onClose: onClose,
@@ -272,7 +272,7 @@ class TPopupOptions {
     double? overlayOpacity,
     bool preventScrollThrough = true,
     bool destroyOnClose = false,
-    Duration duration = const Duration(milliseconds: 240),
+    Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
     VoidCallback? onOpened,
     VoidCallback? onClose,
@@ -293,7 +293,7 @@ class TPopupOptions {
         overlayOpacity: overlayOpacity,
         preventScrollThrough: preventScrollThrough,
         destroyOnClose: destroyOnClose,
-        duration: duration,
+        animationDuration: animationDuration,
         onOpen: onOpen,
         onOpened: onOpened,
         onClose: onClose,
@@ -348,7 +348,7 @@ class TPopupOptions {
   final bool destroyOnClose;
 
   /// 打开/关闭动画时长。
-  final Duration duration;
+  final Duration animationDuration;
 
   /// bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。
   ///
@@ -408,7 +408,7 @@ class TPopupOptions {
     Object? overlayOpacity = _unset,
     bool? preventScrollThrough,
     bool? destroyOnClose,
-    Duration? duration,
+    Duration? animationDuration,
     Object? headerBuilder = _unset,
     Object? titleWidget = _unset,
     Object? cancelBuilder = _unset,
@@ -446,7 +446,7 @@ class TPopupOptions {
           : (overlayOpacity as num?)?.toDouble(),
       preventScrollThrough: preventScrollThrough ?? this.preventScrollThrough,
       destroyOnClose: destroyOnClose ?? this.destroyOnClose,
-      duration: duration ?? this.duration,
+      animationDuration: animationDuration ?? this.animationDuration,
       headerBuilder: identical(headerBuilder, _unset)
           ? this.headerBuilder
           : headerBuilder as TPopupHeaderBuilder?,
@@ -499,7 +499,7 @@ class TPopupOptions {
       overlayOpacity: overlayOpacity,
       preventScrollThrough: preventScrollThrough,
       destroyOnClose: destroyOnClose,
-      duration: duration,
+      animationDuration: animationDuration,
       headerBuilder: isBottom ? headerBuilder : null,
       titleWidget: isBottom ? titleWidget : null,
       cancelBuilder: isBottom ? cancelBuilder : null,
diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart
index 00cbd8e53..33b260668 100644
--- a/tdesign-component/test/t_popup_coverage_test.dart
+++ b/tdesign-component/test/t_popup_coverage_test.dart
@@ -776,17 +776,17 @@ void main() {
       }
     });
 
-    test('factory 透传通用字段:duration / overlay / callbacks', () {
+    test('factory 透传通用字段:animationDuration / overlay / callbacks', () {
       var visibleChanges = 0;
       final opts = TPopupOptions.bottom(
         child: const SizedBox(),
-        duration: const Duration(milliseconds: 500),
+        animationDuration: const Duration(milliseconds: 500),
         showOverlay: false,
         overlayOpacity: 0.5,
         destroyOnClose: true,
         onVisibleChange: (_, __) => visibleChanges++,
       );
-      expect(opts.duration, const Duration(milliseconds: 500));
+      expect(opts.animationDuration, const Duration(milliseconds: 500));
       expect(opts.showOverlay, isFalse);
       expect(opts.overlayOpacity, 0.5);
       expect(opts.destroyOnClose, isTrue);
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index cd71478c4..fb755c87a 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -498,7 +498,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
   
   Widget _buildApiDuration(BuildContext context) {
     return TButton(
-      text: 'duration: 600ms',
+      text: 'animationDuration: 600ms',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
@@ -508,7 +508,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           context,
           options: TPopupOptions.bottom(
               height: 240,
-              duration: const Duration(milliseconds: 600),
+              animationDuration: const Duration(milliseconds: 600),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,
@@ -606,7 +606,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
-| duration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 |
 | height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
 | inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 |

From 0fd36c2fa607e146b312bc07b3bda40dbcd8eb9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Tue, 26 May 2026 10:01:10 +0800
Subject: [PATCH 19/27] fix(popup): demo error

---
 .../code/indexes._buildCustomIndexes.txt      |  2 +-
 .../assets/code/indexes._buildOther.txt       |  2 +-
 .../assets/code/indexes._buildSimple.txt      |  2 +-
 .../assets/code/popup._buildApiDuration.txt   |  4 ++--
 .../assets/code/popup._buildApiInset.txt      | 23 +++++++++++++++++++
 ...p._buildPopFromBottomWithCloseAndTitle.txt | 18 +++++++--------
 .../example/lib/page/t_indexes_page.dart      |  6 ++---
 .../example/lib/page/t_popup_page.dart        |  4 ++--
 8 files changed, 42 insertions(+), 19 deletions(-)
 create mode 100644 tdesign-component/example/assets/code/popup._buildApiInset.txt

diff --git a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt
index 332d418f2..ffda89e81 100644
--- a/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt
+++ b/tdesign-component/example/assets/code/indexes._buildCustomIndexes.txt
@@ -13,7 +13,7 @@ Widget _buildCustomIndexes(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               builderIndex: (context, index, isActive) {
diff --git a/tdesign-component/example/assets/code/indexes._buildOther.txt b/tdesign-component/example/assets/code/indexes._buildOther.txt
index d89966bfb..0af8b5ceb 100644
--- a/tdesign-component/example/assets/code/indexes._buildOther.txt
+++ b/tdesign-component/example/assets/code/indexes._buildOther.txt
@@ -13,7 +13,7 @@ Widget _buildOther(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               capsuleTheme: true,
diff --git a/tdesign-component/example/assets/code/indexes._buildSimple.txt b/tdesign-component/example/assets/code/indexes._buildSimple.txt
index 40aa90866..93f3f3816 100644
--- a/tdesign-component/example/assets/code/indexes._buildSimple.txt
+++ b/tdesign-component/example/assets/code/indexes._buildSimple.txt
@@ -13,7 +13,7 @@ Widget _buildSimple(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               builderContent: (context, index) {
diff --git a/tdesign-component/example/assets/code/popup._buildApiDuration.txt b/tdesign-component/example/assets/code/popup._buildApiDuration.txt
index 3df04cd44..eeb7fc98c 100644
--- a/tdesign-component/example/assets/code/popup._buildApiDuration.txt
+++ b/tdesign-component/example/assets/code/popup._buildApiDuration.txt
@@ -1,7 +1,7 @@
 
   Widget _buildApiDuration(BuildContext context) {
     return TButton(
-      text: 'duration: 600ms',
+      text: 'animationDuration: 600ms',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
@@ -11,7 +11,7 @@
           context,
           options: TPopupOptions.bottom(
               height: 240,
-              duration: const Duration(milliseconds: 600),
+              animationDuration: const Duration(milliseconds: 600),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,
diff --git a/tdesign-component/example/assets/code/popup._buildApiInset.txt b/tdesign-component/example/assets/code/popup._buildApiInset.txt
new file mode 100644
index 000000000..03f9ad0a0
--- /dev/null
+++ b/tdesign-component/example/assets/code/popup._buildApiInset.txt
@@ -0,0 +1,23 @@
+
+  Widget _buildApiInset(BuildContext context) {
+    return TButton(
+      text: 'bottom inset.left/right',
+      isBlock: true,
+      theme: TButtonTheme.primary,
+      type: TButtonType.outline,
+      size: TButtonSize.large,
+      onTap: () {
+        TPopup.show(
+          context,
+          options: TPopupOptions.bottom(
+              height: 320,
+              inset: const TPopupBottomInset(left: 16, right: 16),
+              titleWidget: TText('左右留白'),
+              child: Container(
+                height: 240,
+                color: TTheme.of(context).bgColorContainer,
+              )),
+        );
+      },
+    );
+  }
\ No newline at end of file
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
index ad56c697c..e5df1335a 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
@@ -12,10 +12,10 @@
           options: TPopupOptions.bottom(
               height: 280,
               cancelBuilder: (_, __) => TText(
-                '关闭',
-                textColor: TTheme.of(context).textColorSecondary,
-                font: TTheme.of(context).fontBodyLarge,
-              ),
+                    '关闭',
+                    textColor: TTheme.of(context).textColorSecondary,
+                    font: TTheme.of(context).fontBodyLarge,
+                  ),
               titleWidget: Row(
                 mainAxisSize: MainAxisSize.min,
                 children: [
@@ -30,11 +30,11 @@
                 ],
               ),
               confirmBuilder: (_, __) => TText(
-                '完成',
-                textColor: TTheme.of(context).brandNormalColor,
-                font: TTheme.of(context).fontTitleMedium,
-                fontWeight: FontWeight.w600,
-              ),
+                    '完成',
+                    textColor: TTheme.of(context).brandNormalColor,
+                    font: TTheme.of(context).fontTitleMedium,
+                    fontWeight: FontWeight.w600,
+                  ),
               child: Container(height: 200)),
         );
       },
diff --git a/tdesign-component/example/lib/page/t_indexes_page.dart b/tdesign-component/example/lib/page/t_indexes_page.dart
index bb9b1e98b..d0b1f5cf2 100644
--- a/tdesign-component/example/lib/page/t_indexes_page.dart
+++ b/tdesign-component/example/lib/page/t_indexes_page.dart
@@ -161,7 +161,7 @@ Widget _buildSimple(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               builderContent: (context, index) {
@@ -193,7 +193,7 @@ Widget _buildOther(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               capsuleTheme: true,
@@ -226,7 +226,7 @@ Widget _buildCustomIndexes(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               builderIndex: (context, index, isActive) {
diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart
index 086ef2419..c7efcfe8e 100644
--- a/tdesign-component/example/lib/page/t_popup_page.dart
+++ b/tdesign-component/example/lib/page/t_popup_page.dart
@@ -661,7 +661,7 @@ class TPopupPage extends StatelessWidget {
   @Demo(group: 'popup')
   Widget _buildApiDuration(BuildContext context) {
     return TButton(
-      text: 'duration: 600ms',
+      text: 'animationDuration: 600ms',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
@@ -671,7 +671,7 @@ class TPopupPage extends StatelessWidget {
           context,
           options: TPopupOptions.bottom(
               height: 240,
-              duration: const Duration(milliseconds: 600),
+              animationDuration: const Duration(milliseconds: 600),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,

From 145f9c023550b5b71ed6c3f3aca36b783e8c380a Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Tue, 26 May 2026 02:10:50 +0000
Subject: [PATCH 20/27] [autofix.ci] apply automated fixes

---
 .../example/assets/api/action-sheet_api.md    |   56 +-
 .../example/assets/api/avatar_api.md          |   38 +-
 .../example/assets/api/back-top_api.md        |   22 +-
 .../example/assets/api/badge_api.md           |   37 +-
 .../example/assets/api/button_api.md          |  130 +-
 .../example/assets/api/calendar_api.md        |  126 +-
 .../example/assets/api/cascader_api.md        |   10 +-
 .../example/assets/api/cell_api.md            |   55 +-
 .../example/assets/api/checkbox_api.md        |   93 +-
 .../example/assets/api/collapse_api.md        |   38 +-
 .../example/assets/api/dialog_api.md          |  112 +-
 .../example/assets/api/divider_api.md         |   13 +-
 .../example/assets/api/drawer_api.md          |   28 +-
 .../example/assets/api/dropdown-menu_api.md   |   64 +-
 .../example/assets/api/empty_api.md           |   20 +-
 .../example/assets/api/fab_api.md             |   36 +-
 .../example/assets/api/footer_api.md          |   15 +-
 .../example/assets/api/form_api.md            |   46 +-
 .../example/assets/api/icon_api.md            | 2126 ++++++++++++++++-
 .../example/assets/api/image-viewer_api.md    |   62 +-
 .../example/assets/api/image_api.md           |   49 +-
 .../example/assets/api/indexes_api.md         |   10 +-
 .../example/assets/api/input_api.md           |   39 +-
 .../example/assets/api/link_api.md            |   59 +-
 .../example/assets/api/loading_api.md         |   28 +-
 .../example/assets/api/message_api.md         |   22 +-
 .../example/assets/api/navbar_api.md          |   14 +-
 .../example/assets/api/notice-bar_api.md      |   44 +-
 .../example/assets/api/picker_api.md          |   87 +-
 .../example/assets/api/popover_api.md         |   60 +-
 .../example/assets/api/popup_api.md           |  251 +-
 .../example/assets/api/progress_api.md        |   39 +-
 .../assets/api/pull-down-refresh_api.md       |   50 +-
 .../example/assets/api/radio_api.md           |   98 +-
 .../example/assets/api/rate_api.md            |   17 +-
 .../example/assets/api/result_api.md          |   14 +-
 .../example/assets/api/search_api.md          |   50 +-
 .../example/assets/api/side-bar_api.md        |   16 +-
 .../example/assets/api/skeleton_api.md        |  147 +-
 .../example/assets/api/slider_api.md          |   32 +-
 .../example/assets/api/stepper_api.md         |   69 +-
 .../example/assets/api/steps_api.md           |   28 +-
 .../example/assets/api/swipe-cell_api.md      |   20 +-
 .../example/assets/api/swiper_api.md          |   36 +-
 .../example/assets/api/switch_api.md          |   35 +-
 .../example/assets/api/tab-bar_api.md         |   59 +-
 .../example/assets/api/table_api.md           |   76 +-
 .../example/assets/api/tabs_api.md            |   46 +-
 .../example/assets/api/tag_api.md             |  100 +-
 .../example/assets/api/text_api.md            |  106 +-
 .../example/assets/api/textarea_api.md        |   12 +-
 .../example/assets/api/theme_api.md           |   35 +-
 .../example/assets/api/time-counter_api.md    |   74 +-
 .../example/assets/api/toast_api.md           |   30 +-
 .../example/assets/api/tree-select_api.md     |   22 +-
 .../example/assets/api/upload_api.md          |   87 +-
 .../assets/code/popup._buildApiMarginTop.txt  |   23 -
 tdesign-site/src/action-sheet/README.md       |   56 +-
 tdesign-site/src/avatar/README.md             |   38 +-
 tdesign-site/src/back-top/README.md           |   22 +-
 tdesign-site/src/badge/README.md              |   37 +-
 tdesign-site/src/button/README.md             |  130 +-
 tdesign-site/src/calendar/README.md           |  126 +-
 tdesign-site/src/cascader/README.md           |   10 +-
 tdesign-site/src/cell/README.md               |   55 +-
 tdesign-site/src/checkbox/README.md           |   93 +-
 tdesign-site/src/collapse/README.md           |   38 +-
 tdesign-site/src/dialog/README.md             |  112 +-
 tdesign-site/src/divider/README.md            |   13 +-
 tdesign-site/src/drawer/README.md             |   28 +-
 tdesign-site/src/dropdown-menu/README.md      |   65 +-
 tdesign-site/src/empty/README.md              |   20 +-
 tdesign-site/src/fab/README.md                |   36 +-
 tdesign-site/src/footer/README.md             |   15 +-
 tdesign-site/src/form/README.md               |   46 +-
 tdesign-site/src/image-viewer/README.md       |   62 +-
 tdesign-site/src/image/README.md              |   49 +-
 tdesign-site/src/indexes/README.md            |   18 +-
 tdesign-site/src/input/README.md              |   39 +-
 tdesign-site/src/link/README.md               |   59 +-
 tdesign-site/src/loading/README.md            |   28 +-
 tdesign-site/src/message/README.md            |   22 +-
 tdesign-site/src/navbar/README.md             |   14 +-
 tdesign-site/src/notice-bar/README.md         |   44 +-
 tdesign-site/src/picker/README.md             |   85 +-
 tdesign-site/src/popover/README.md            |   60 +-
 tdesign-site/src/popup/README.md              |  260 +-
 tdesign-site/src/progress/README.md           |   39 +-
 tdesign-site/src/pull-down-refresh/README.md  |   50 +-
 tdesign-site/src/radio/README.md              |   98 +-
 tdesign-site/src/rate/README.md               |   17 +-
 tdesign-site/src/result/README.md             |   14 +-
 tdesign-site/src/search/README.md             |   50 +-
 tdesign-site/src/side-bar/README.md           |   16 +-
 tdesign-site/src/slider/README.md             |   32 +-
 tdesign-site/src/stepper/README.md            |   69 +-
 tdesign-site/src/steps/README.md              |   28 +-
 tdesign-site/src/swipe-cell/README.md         |   20 +-
 tdesign-site/src/swiper/README.md             |   36 +-
 tdesign-site/src/switch/README.md             |   35 +-
 tdesign-site/src/tab-bar/README.md            |   59 +-
 tdesign-site/src/table/README.md              |   76 +-
 tdesign-site/src/tabs/README.md               |   46 +-
 tdesign-site/src/tag/README.md                |  100 +-
 tdesign-site/src/text/README.md               |  106 +-
 tdesign-site/src/textarea/README.md           |   12 +-
 tdesign-site/src/time-counter/README.md       |   74 +-
 tdesign-site/src/toast/README.md              |   30 +-
 tdesign-site/src/tree-select/README.md        |   22 +-
 tdesign-site/src/upload/README.md             |   87 +-
 110 files changed, 6863 insertions(+), 1044 deletions(-)
 delete mode 100644 tdesign-component/example/assets/code/popup._buildApiMarginTop.txt

diff --git a/tdesign-component/example/assets/api/action-sheet_api.md b/tdesign-component/example/assets/api/action-sheet_api.md
index a08aa51e4..664f3bfc3 100644
--- a/tdesign-component/example/assets/api/action-sheet_api.md
+++ b/tdesign-component/example/assets/api/action-sheet_api.md
@@ -9,14 +9,12 @@
 | badge | TBadge? | - | 角标 |
 | description | String? | - | 描述信息 |
 | disabled | bool | false | 是否禁用 |
-| group | String? | - | 分组,用于带描述多行滚动宫格 |
+| group | String? | - | 分组,用于带描述多行滚动宫格 当[TActionSheet.theme]等于[TActionSheetTheme.group]时有效 有效时,如果该值未配置整个[TActionSheetItem]会被忽略,即不会展示 |
 | icon | Widget? | - | 图标 |
 | iconSize | double? | - | 图标大小 |
 | label | String | - | 标题 |
 | textStyle | TextStyle? | - | 标题样式 |
 
-```
-```
 
 ### TActionSheet
 #### 简介
@@ -25,23 +23,23 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
 | align | TActionSheetAlign | TActionSheetAlign.center | 对齐方式 |
 | cancelText | String? | - | 取消按钮的文本 |
 | closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
-| context | BuildContext | context | 上下文 |
-| count | int | 8 | 每页显示的项目数 |
-| description | String? | - | 描述文本 |
-| itemHeight | double | 96.0 | 项目的行高 |
-| itemMinWidth | double | 80.0 | 项目的最小宽度 |
+| count | int | 8 | 每页显示的项目数 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为true时有效 |
+| description | String? | - | 描述文本 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.list]时有效 |
+| itemHeight | double | 96.0 | 项目的行高 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.group]时有效 |
+| itemMinWidth | double | 80.0 | 项目的最小宽度 当[theme]等于[TActionSheetTheme.grid]且[scrollable]为true时有效 或当[theme]等于[TActionSheetTheme.group]时有效 |
 | items | List | - | ActionSheet的项目列表 |
 | onCancel | VoidCallback? | - | 取消按钮的回调函数 |
 | onClose | VoidCallback? | - | 关闭时的回调函数 |
 | onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
-| rows | int | 2 | 显示的行数 |
-| scrollable | bool | false | 是否可以横向滚动 |
+| rows | int | 2 | 显示的行数 当[theme]等于[TActionSheetTheme.grid]时有效 |
+| scrollable | bool | false | 是否可以横向滚动 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为false时有效 |
 | showCancel | bool | true | 是否显示取消按钮 |
 | showOverlay | bool | true | 是否显示遮罩层 |
-| showPagination | bool | false | 是否显示分页 |
+| showPagination | bool | false | 是否显示分页 当[theme]等于[TActionSheetTheme.grid]时有效 |
 | theme | TActionSheetTheme | TActionSheetTheme.list | 主题样式 |
 | useSafeArea | bool | true | 使用安全区域 |
 | visible | bool | false | 是否立即显示 |
@@ -51,6 +49,36 @@
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showGridActionSheet |  |   required BuildContext context,  required List items,  TActionSheetAlign align,  String? cancelText,  bool showCancel,  TActionSheetItemCallback? onSelected,  bool showOverlay,  bool closeOnOverlayClick,  int count,  int rows,  double itemHeight,  double itemMinWidth,  bool scrollable,  bool showPagination,  VoidCallback? onCancel,  String? description,  VoidCallback? onClose,  bool useSafeArea, | 显示宫格类型面板 |
-| showGroupActionSheet |  |   required BuildContext context,  required List items,  TActionSheetAlign align,  String? cancelText,  bool showCancel,  TActionSheetItemCallback? onSelected,  bool showOverlay,  bool closeOnOverlayClick,  double itemHeight,  double itemMinWidth,  VoidCallback? onCancel,  VoidCallback? onClose,  bool useSafeArea, | 显示分组类型面板 |
-| showListActionSheet |  |   required BuildContext context,  required List items,  TActionSheetAlign align,  String? cancelText,  bool showCancel,  VoidCallback? onCancel,  TActionSheetItemCallback? onSelected,  bool showOverlay,  bool closeOnOverlayClick,  VoidCallback? onClose,  bool useSafeArea, | 显示列表类型面板 |
+| showGridActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, int count, int rows, double itemHeight, double itemMinWidth, bool scrollable, bool showPagination, VoidCallback? onCancel, String? description, VoidCallback? onClose, bool useSafeArea | 显示宫格类型面板 |
+| showGroupActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, double itemHeight, double itemMinWidth, VoidCallback? onCancel, VoidCallback? onClose, bool useSafeArea | 显示分组类型面板 |
+| showListActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, VoidCallback? onCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, VoidCallback? onClose, bool useSafeArea | 显示列表类型面板 |
+
+
+### TActionSheetTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| list | - |
+| grid | - |
+| group | - |
+
+
+### TActionSheetAlign
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| center | - |
+| left | - |
+| right | - |
+
+
+### TActionSheetItemCallback
+#### 类型定义
+
+```dart
+typedef TActionSheetItemCallback = void Function(TActionSheetItem item, int index);
+```
diff --git a/tdesign-component/example/assets/api/avatar_api.md b/tdesign-component/example/assets/api/avatar_api.md
index 9f1dd77b2..ddc9cf598 100644
--- a/tdesign-component/example/assets/api/avatar_api.md
+++ b/tdesign-component/example/assets/api/avatar_api.md
@@ -15,10 +15,44 @@
 | displayText | String? | - | 纯展示类型末尾文字 |
 | fit | BoxFit? | - | 自定义图片对齐方式 |
 | icon | IconData? | - | 自定义图标 |
-| key |  | - |  |
-| onTap |  Function()? | - | 操作点击事件 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onTap | Function()? | - | 操作点击事件 |
 | radius | double? | - | 自定义圆角 |
 | shape | TAvatarShape | TAvatarShape.circle | 头像形状 |
 | size | TAvatarSize | TAvatarSize.medium | 头像尺寸 |
 | text | String? | - | 自定义文字 |
 | type | TAvatarType | TAvatarType.normal | 头像类型 |
+
+
+### TAvatarSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+
+
+### TAvatarType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| icon | - |
+| normal | - |
+| customText | - |
+| display | - |
+| operation | - |
+
+
+### TAvatarShape
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
diff --git a/tdesign-component/example/assets/api/back-top_api.md b/tdesign-component/example/assets/api/back-top_api.md
index 0eaf4fdd6..8d2feb808 100644
--- a/tdesign-component/example/assets/api/back-top_api.md
+++ b/tdesign-component/example/assets/api/back-top_api.md
@@ -5,8 +5,28 @@
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | controller | ScrollController? | - | 页面滚动的控制器 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onClick | VoidCallback? | - | 按钮点击事件 |
 | showText | bool | false | 是否展示文字 |
 | style | TBackTopStyle | TBackTopStyle.circle | 样式,圆形和半圆 |
 | theme | TBackTopTheme | TBackTopTheme.light | 主题 |
+
+
+### TBackTopTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| light | - |
+| dark | - |
+
+
+### TBackTopStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| halfCircle | - |
diff --git a/tdesign-component/example/assets/api/badge_api.md b/tdesign-component/example/assets/api/badge_api.md
index 620ac2afa..e91b2fb3a 100644
--- a/tdesign-component/example/assets/api/badge_api.md
+++ b/tdesign-component/example/assets/api/badge_api.md
@@ -4,16 +4,49 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| type | TBadgeType | - | 红点样式 |
 | border | TBadgeBorder | TBadgeBorder.large | 红点圆角大小 |
 | color | Color? | - | 红点颜色 |
 | count | String? | - | 红点数量 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | maxCount | String? | '99' | 最大红点数量 |
 | message | String? | - | 消息内容 |
 | padding | EdgeInsetsGeometry? | - | 角标自定义padding |
 | showZero | bool | true | 值为0是否显示 |
 | size | TBadgeSize | TBadgeSize.small | 红点尺寸 |
 | textColor | Color? | - | 文字颜色 |
-| type | TBadgeType | type | 红点样式 |
 | widthLarge | double | 32 | 角标大三角形宽 |
 | widthSmall | double | 12 | 角标小三角形宽 |
+
+
+### TBadgeType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| redPoint | 红点样式 |
+| message | 消息样式 |
+| bubble | 气泡样式 |
+| square | 方形样式 |
+| subscript | 角标样式 |
+
+
+### TBadgeBorder
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | 大圆角 8px |
+| small | 小圆角 2px |
+
+
+### TBadgeSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | 宽 20px |
+| small | 宽 16px |
diff --git a/tdesign-component/example/assets/api/button_api.md b/tdesign-component/example/assets/api/button_api.md
index be9eb3640..c036c83cb 100644
--- a/tdesign-component/example/assets/api/button_api.md
+++ b/tdesign-component/example/assets/api/button_api.md
@@ -16,7 +16,7 @@
 | iconTextSpacing | double? | - | 自定义图标与文本之间距离 |
 | iconWidget | Widget? | - | 自定义图标 icon 控件 |
 | isBlock | bool | false | 是否为通栏按钮 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | margin | EdgeInsetsGeometry? | - | 自定义 margin |
 | onLongPress | TButtonEvent? | - | 长按事件 |
 | onTap | TButtonEvent? | - | 点击事件 |
@@ -30,8 +30,6 @@
 | type | TButtonType | TButtonType.fill | 类型:填充,描边,文字 |
 | width | double? | - | 自定义宽度 |
 
-```
-```
 
 ### TButtonStyle
 #### 默认构造方法
@@ -48,9 +46,123 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TButtonStyle.generateFillStyleByTheme  | 生成不同主题的填充按钮样式 |
-| TButtonStyle.generateGhostStyleByTheme  | 生成不同主题的幽灵按钮样式 |
-| TButtonStyle.generateOutlineStyleByTheme  | 生成不同主题的描边按钮样式 |
-| TButtonStyle.generateTextStyleByTheme  | 生成不同主题的文本按钮样式 |
+##### TButtonStyle.generateFillStyleByTheme
+
+生成不同主题的填充按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+##### TButtonStyle.generateGhostStyleByTheme
+
+生成不同主题的幽灵按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+##### TButtonStyle.generateOutlineStyleByTheme
+
+生成不同主题的描边按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+##### TButtonStyle.generateTextStyleByTheme
+
+生成不同主题的文本按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+### TButtonSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+| extraSmall | - |
+
+
+### TButtonType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| fill | - |
+| outline | - |
+| text | - |
+| ghost | - |
+
+
+### TButtonShape
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| rectangle | - |
+| round | - |
+| square | - |
+| circle | - |
+| filled | - |
+
+
+### TButtonTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | - |
+| primary | - |
+| danger | - |
+| light | - |
+
+
+### TButtonStatus
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultState | - |
+| active | - |
+| disable | - |
+
+
+### TButtonIconPosition
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+
+
+### TButtonEvent
+#### 类型定义
+
+```dart
+typedef TButtonEvent = void Function();
+```
diff --git a/tdesign-component/example/assets/api/calendar_api.md b/tdesign-component/example/assets/api/calendar_api.md
index b5bd37505..76638af64 100644
--- a/tdesign-component/example/assets/api/calendar_api.md
+++ b/tdesign-component/example/assets/api/calendar_api.md
@@ -15,7 +15,7 @@
 | format | CalendarFormat? | - | 用于格式化日期的函数,可定义日期前后的显示内容和日期样式 |
 | height | double? | - | 高度 |
 | isTimeUnit | bool? | true | 是否显示时间单位 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | maxDate | int? | - | 最大可选的日期(fromMillisecondsSinceEpoch),不传则默认半年后 |
 | minDate | int? | - | 最小可选的日期(fromMillisecondsSinceEpoch),不传则默认今天 |
 | monthTitleBuilder | Widget Function(BuildContext context, DateTime monthDate)? | - | 月标题构建器 |
@@ -38,26 +38,22 @@
 | value | List? | - | 当前选择的日期(fromMillisecondsSinceEpoch),不传则默认今天,当 type = single 时数组长度为1 |
 | width | double? | - | 宽度 |
 
-```
-```
 
 ### TCalendarPopup
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
 | autoClose | bool? | true | 自动关闭;在点击关闭按钮、确认按钮、遮罩层时自动关闭 |
 | builder | CalendarBuilder? | - | 控件构建器,优先级高于[child] |
 | child | TCalendar? | - | 日历控件 |
 | confirmBtn | Widget? | - | 自定义确认按钮 |
-| context | BuildContext | context | 上下文 |
 | onClose | VoidCallback? | - | 关闭时触发 |
 | onConfirm | void Function(List value)? | - | 点击确认按钮时触发 |
 | top | double? | - | 距离顶部的距离 |
 | visible | bool? | - | 默认是否显示日历 |
 
-```
-```
 
 ### TCalendarStyle
 #### 默认构造方法
@@ -69,27 +65,63 @@
 | cellStyle | TextStyle? | - | 日期样式 |
 | cellSuffixStyle | TextStyle? | - | 日期后面的字符串的样式 |
 | centreColor | Color? | - | 日期范围内背景样式 |
-| decoration |  | - |  |
+| decoration | BoxDecoration? | - | - |
 | monthTitleStyle | TextStyle? | - | body区域 年月文字样式 |
 | titleCloseColor | Color? | - | header区域 关闭图标的颜色 |
 | titleMaxLine | int? | - | header区域 [TCalendar.title]的行数 |
 | titleStyle | TextStyle? | - | header区域 [TCalendar.title]的样式 |
 | weekdayStyle | TextStyle? | - | header区域 周 文字样式 |
 
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| bodyPadding | double? | - | 月与月之间的垂直间距 |
+| todayStyle | TextStyle? | - | 当天日期样式 |
+| verticalGap | double? | - | 日期垂直间距,水平间距为[verticalGap] / 2 |
+
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TCalendarStyle.cellStyle  | 日期样式 |
-| TCalendarStyle.generateStyle  | 生成默认样式 |
+##### TCalendarStyle.cellStyle
+
+日期样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| type | DateSelectType? | - | - |
+
+
+##### TCalendarStyle.generateStyle
+
+生成默认样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
 
-```
-```
 
 ### TCalendarDataSource
-```
-```
+#### 简介
+日历数据源接口
+ 
+ 开发者需要实现此接口来提供农历转换能力。
+ 组件内部不包含农历算法和数据,完全依赖外部实现。
+
+#### 方法
+
+| 名称 | 返回类型 | 参数 | 说明 |
+| --- | --- | --- | --- |
+| getLunarInfo | TLunarInfo? | required DateTime solarDate | 获取指定阳历日期的农历信息 [solarDate] 阳历日期 返回 null 表示不显示农历信息 |
+| formatDate | String | required DateTime date, required TCalendarDateType type, TLunarInfo? lunarInfo | 格式化日期文本 [date] 阳历日期 [type] 日历类型 [lunarInfo] 农历信息(可选) 返回格式化后的日期字符串 |
+| getSolarTerm | String? | required DateTime date | 获取节气信息(可选实现) [date] 阳历日期 返回节气名称,如"春分"、"秋分"等,无节气则返回 null |
+| getFestival | String? | required DateTime date, TLunarInfo? lunarInfo | 获取节日信息(可选实现) [date] 阳历日期 [lunarInfo] 农历信息(可选) 返回节日名称,如"春节"、"中秋节"等,无节日则返回 null |
+| getHolidayInfo | Map? | required DateTime date | 获取假期信息(可选实现) [date] 阳历日期 返回假期类型和名称: - 'holiday': 法定节假日/公共假期(如"国庆节") - 'workday': 调休工作日(如"补班") - null: 正常日期 示例返回值: - {'type': 'holiday', 'name': '国庆节'} - {'type': 'workday', 'name': '补班'} - null |
+| formatYear | String | required int year, required TCalendarDateType type | 格式化年份文本 [year] 年份 [type] 日历类型 返回格式化后的年份字符串 阳历示例:2025 -> "2025年" 阴历示例:2025 -> "二〇二五年" |
+| formatMonth | String | required int month, required TCalendarDateType type, bool isLeapMonth | 格式化月份文本 [month] 月份(1-12) [type] 日历类型 [isLeapMonth] 是否是闰月(仅农历有效) 返回格式化后的月份字符串 阳历示例:3 -> "3月" 阴历示例:3 -> "三月",闰3月 -> "闰三月" |
+| formatDay | String | required int day, required TCalendarDateType type | 格式化日期文本 [day] 日期(1-31) [type] 日历类型 返回格式化后的日期字符串 阳历示例:7 -> "7日" 阴历示例:7 -> "初七" |
+
 
 ### TLunarInfo
 #### 默认构造方法
@@ -103,3 +135,67 @@
 | monthText | String | - | 月份文本(如:三月、闰三月) |
 | year | int | - | 农历年份(数字) |
 | yearText | String | - | 年份文本(如:二〇二五) |
+
+
+### TCalendarDateType
+#### 简介
+日历类型枚举
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| solar | 阳历(公历) |
+| lunar | 阴历(农历) |
+
+
+### CalendarType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| single | - |
+| multiple | - |
+| range | - |
+
+
+### CalendarTrigger
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| closeBtn | - |
+| confirmBtn | - |
+| overlay | - |
+
+
+### DateSelectType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| selected | - |
+| disabled | - |
+| start | - |
+| centre | - |
+| end | - |
+| empty | - |
+
+
+### CalendarFormat
+#### 类型定义
+
+```dart
+typedef CalendarFormat = TDate? Function(TDate? day);
+```
+
+
+### CalendarBuilder
+#### 类型定义
+
+```dart
+typedef CalendarBuilder = Widget Function(BuildContext context);
+```
diff --git a/tdesign-component/example/assets/api/cascader_api.md b/tdesign-component/example/assets/api/cascader_api.md
index 7906e3559..bfd99ac6a 100644
--- a/tdesign-component/example/assets/api/cascader_api.md
+++ b/tdesign-component/example/assets/api/cascader_api.md
@@ -12,7 +12,7 @@
 | initialData | String? | - | 初始化数据 |
 | initialIndexes | List? | - | 若为null表示全部从零开始 |
 | isLetterSort | bool | false | 是否开启字母排序 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onChange | MultiCascaderCallback | - | 值发生变更时触发 |
 | onClose | Function? | - | 选择器关闭按钮回调 |
 | subTitles | List? | - | 每级展示的次标题 |
@@ -20,3 +20,11 @@
 | title | String? | - | 选择器标题 |
 | titleStyle | TextStyle? | - | 标题样式 |
 | topRadius | double? | - | 顶部圆角 |
+
+
+### MultiCascaderCallback
+#### 类型定义
+
+```dart
+typedef MultiCascaderCallback = void Function(List selected);
+```
diff --git a/tdesign-component/example/assets/api/cell_api.md b/tdesign-component/example/assets/api/cell_api.md
index b66815628..c7563c928 100644
--- a/tdesign-component/example/assets/api/cell_api.md
+++ b/tdesign-component/example/assets/api/cell_api.md
@@ -18,7 +18,7 @@
 | imageCircle | double? | 50 | 主图圆角,默认50(圆形) |
 | imageSize | double? | - | 主图尺寸 |
 | imageWidget | Widget? | - | 主图组件 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftIcon | IconData? | - | 左侧图标,出现在单元格标题的左侧 |
 | leftIconWidget | Widget? | - | 左侧图标组件 |
 | note | String? | - | 和标题同行的说明文字 |
@@ -35,8 +35,6 @@
 | title | String? | - | 标题 |
 | titleWidget | Widget? | - | 标题组件 |
 
-```
-```
 
 ### TCellGroup
 #### 简介
@@ -49,15 +47,13 @@
 | builder | CellBuilder? | - | cell构建器,可自定义cell父组件,如Dismissible |
 | cells | List | - | 单元格列表 |
 | isShowLastBordered | bool? | false | 是否显示最后一个cell的下边框 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | scrollable | bool? | false | 可滚动 |
 | style | TCellStyle? | - | 自定义样式 |
 | theme | TCellGroupTheme? | TCellGroupTheme.defaultTheme | 单元格组风格。可选项:default/card |
 | title | String? | - | 单元格组标题 |
 | titleWidget | Widget? | - | 单元格组标题组件 |
 
-```
-```
 
 ### TCellStyle
 #### 简介
@@ -88,6 +84,47 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TCellStyle.cellStyle  | 生成单元格默认样式 |
+##### TCellStyle.cellStyle
+
+生成单元格默认样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 传递context,会生成默认样式 |
+
+
+### TCellAlign
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| top | - |
+| middle | - |
+| bottom | - |
+
+
+### TCellGroupTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | - |
+| cardTheme | - |
+
+
+### TCellClick
+#### 类型定义
+
+```dart
+typedef TCellClick = void Function(TCell cell);
+```
+
+
+### CellBuilder
+#### 类型定义
+
+```dart
+typedef CellBuilder = Widget Function(BuildContext context, TCell cell, int index);
+```
diff --git a/tdesign-component/example/assets/api/checkbox_api.md b/tdesign-component/example/assets/api/checkbox_api.md
index 0d77c411c..40e8991af 100644
--- a/tdesign-component/example/assets/api/checkbox_api.md
+++ b/tdesign-component/example/assets/api/checkbox_api.md
@@ -7,16 +7,16 @@
 | backgroundColor | Color? | - | 背景颜色 |
 | cardMode | bool | false | 展示为卡片模式 |
 | checkBoxLeftSpace | double? | - | 选项框左侧间距 |
-| checked | bool | false | 选中状态。默认为`false` |
+| checked | bool | false | 选中状态。默认为`false` 当FuiCheckBox嵌入到FuiCheckBoxGroup的时候,这个值表示初始状态,后续的状态会由Group管理 |
 | contentDirection | TContentDirection | TContentDirection.right | 文字相对icon的方位 |
 | customContentBuilder | ContentBuilder? | - | 完全自定义内容 |
 | customIconBuilder | IconBuilder? | - | 自定义Checkbox显示样式 |
 | customSpace | EdgeInsetsGeometry? | - | 自定义组件间距 |
 | disableColor | Color? | - | 禁用选择颜色 |
 | enable | bool | true | 不可用 |
-| id | String? | - | id |
+| id | String? | - | id 当FuiCheckBox嵌入到FuiCheckBoxGroup内时,这个值需要赋值,否则不会被纳入Group管理 |
 | insetSpacing | double? | 16 | 文字和非图标侧的距离 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onCheckBoxChanged | OnCheckValueChanged? | - | 切换监听 |
 | selectColor | Color? | - | 选择颜色 |
 | showDivider | bool | true | 是否展示分割线 |
@@ -32,8 +32,6 @@
 | titleFont | Font? | - | 标题字体大小 |
 | titleMaxLine | int? | - | 标题的行数 |
 
-```
-```
 
 ### TCheckboxGroup
 #### 默认构造方法
@@ -41,15 +39,96 @@
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | checkedIds | List? | - | 勾选的CheckBox id列表 |
-| child |  | - |  |
+| child | Widget | - | 可以是任意包含TCheckBox的容器,比如: ``` Row( children: [ TCheckBox(), TCheckBox(), ... ] ) ``` |
 | contentDirection | TContentDirection? | - | 文字相对icon的方位 |
 | controller | TCheckboxGroupController? | - | 可以通过控制器操作勾选状态 |
 | customContentBuilder | ContentBuilder? | - | CheckBox完全自定义内容 |
 | customIconBuilder | IconBuilder? | - | 自定义选择icon的样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | maxChecked | int? | - | 最多可以勾选多少 |
 | onChangeGroup | OnGroupChange? | - | 状态变化监听器 |
 | onOverloadChecked | VoidCallback? | - | 超过最大可勾选的个数 |
 | spacing | double? | - | CheckBoxicon和文字的距离 |
 | style | TCheckboxStyle? | - | CheckBox复选框样式:圆形或方形 |
 | titleMaxLine | int? | - | CheckBox标题的行数 |
+
+
+### TCheckboxStyle
+#### 简介
+选择框的样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
+| check | - |
+
+
+### TContentDirection
+#### 简介
+内容相对icon的位置,上、下、左、右,默认内容在icon的右边
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+
+
+### TCheckBoxSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| small | - |
+
+
+### IconBuilder
+#### 简介
+自定义Icon
+#### 类型定义
+
+```dart
+typedef IconBuilder = Widget? Function(BuildContext context, bool checked);
+```
+
+
+### ContentBuilder
+#### 简介
+自定义Content
+#### 类型定义
+
+```dart
+typedef ContentBuilder = Widget Function(BuildContext context, bool checked, String? content);
+```
+
+
+### OnCheckValueChanged
+#### 类型定义
+
+```dart
+typedef OnCheckValueChanged = void Function(bool selected);
+```
+
+
+### OnGroupChange
+#### 简介
+CheckBoxGroup变化监听器
+#### 类型定义
+
+```dart
+typedef OnGroupChange = void Function(List checkedIds);
+```
+
+
+### OnCheckBoxGroupChange
+#### 类型定义
+
+```dart
+typedef OnCheckBoxGroupChange = void Function(List ids);
+```
diff --git a/tdesign-component/example/assets/api/collapse_api.md b/tdesign-component/example/assets/api/collapse_api.md
index 28e0ebbd2..bc0ef877e 100644
--- a/tdesign-component/example/assets/api/collapse_api.md
+++ b/tdesign-component/example/assets/api/collapse_api.md
@@ -9,13 +9,39 @@
 | animationDuration | Duration | kThemeAnimationDuration | 折叠面板列表的动画时长 |
 | children | List | - | 折叠面板列表的子组件 |
 | elevation | double | 0 | 折叠面板列表的阴影 |
-| expansionCallback | ExpansionPanelCallback? | - | 折叠面板列表的回调函数; |
-| key |  | - |  |
-| style | TCollapseStyle | TCollapseStyle.block | 折叠面板列表的样式 |
+| expansionCallback | ExpansionPanelCallback? | - | 折叠面板列表的回调函数; 回调时,入参为当前点击的折叠面板的索引 index 和是否展开的状态 isExpanded |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| style | TCollapseStyle | TCollapseStyle.block | 折叠面板列表的样式 - [TCollapseStyle.block] 通栏风格 - [TCollapseStyle.card] 卡片风格 |
+
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| initialOpenPanelValue | Object? | - | 折叠面板列表的默认展开面板的值; 当使用 [TCollapse.accordion] 时,此值生效 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TCollapse.accordion  |  |
+##### TCollapse.accordion
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| children | List | - | 折叠面板列表的子组件 |
+| style | TCollapseStyle | TCollapseStyle.block | 折叠面板列表的样式 - [TCollapseStyle.block] 通栏风格 - [TCollapseStyle.card] 卡片风格 |
+| expansionCallback | ExpansionPanelCallback? | - | 折叠面板列表的回调函数; 回调时,入参为当前点击的折叠面板的索引 index 和是否展开的状态 isExpanded |
+| animationDuration | Duration | kThemeAnimationDuration | 折叠面板列表的动画时长 |
+| elevation | double | 0 | 折叠面板列表的阴影 |
+| initialOpenPanelValue | Object? | - | 折叠面板列表的默认展开面板的值; 当使用 [TCollapse.accordion] 时,此值生效 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+
+
+### TCollapseStyle
+#### 简介
+折叠面板的组件样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| block | Block 通栏风格 |
+| card | Card 卡片风格 |
diff --git a/tdesign-component/example/assets/api/dialog_api.md b/tdesign-component/example/assets/api/dialog_api.md
index 9dd84999d..0679ea786 100644
--- a/tdesign-component/example/assets/api/dialog_api.md
+++ b/tdesign-component/example/assets/api/dialog_api.md
@@ -5,19 +5,19 @@
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | backgroundColor | Color? | - | 背景颜色 |
-| buttonStyle |  | TDialogButtonStyle.normal |  |
+| buttonStyle | TDialogButtonStyle | TDialogButtonStyle.normal | - |
 | buttonWidget | Widget? | - | 自定义按钮 |
 | content | String? | - | 内容 |
 | contentColor | Color? | - | 内容颜色 |
 | contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
 | contentWidget | Widget? | - | 内容Widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions? | - | 左侧按钮配置 |
-| leftBtnAction |  Function()? | - | 左侧按钮默认点击 |
+| leftBtnAction | Function()? | - | 左侧按钮默认点击 |
 | padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
 | rightBtn | TDialogButtonOptions? | - | 右侧按钮配置 |
-| rightBtnAction |  Function()? | - | 右侧按钮默认点击 |
+| rightBtnAction | Function()? | - | 右侧按钮默认点击 |
 | showCloseButton | bool? | - | 显示右上角关闭按钮 |
 | title | String? | - | 标题 |
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
@@ -26,21 +26,36 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TAlertDialog.vertical  | 纵向按钮排列的对话框
+##### TAlertDialog.vertical
 
- [buttons]参数是必须的,纵向按钮默认样式都是[TButtonTheme.primary] |
+纵向按钮排列的对话框
+
+ [buttons]参数是必须的,纵向按钮默认样式都是[TButtonTheme.primary]
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| buttons | List | - | - |
+| backgroundColor | Color? | - | 背景颜色 |
+| radius | double | 12.0 | 圆角 |
+| title | String? | - | 标题 |
+| titleColor | Color? | - | 标题颜色 |
+| titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
+| contentWidget | Widget? | - | 内容Widget |
+| content | String? | - | 内容 |
+| contentColor | Color? | - | 内容颜色 |
+| contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
+| showCloseButton | bool? | - | 显示右上角关闭按钮 |
+| padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
+| buttonWidget | Widget? | - | 自定义按钮 |
 
-```
-```
 
 ### TConfirmDialog
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| action |  Function()? | - | 点击 |
+| action | Function()? | - | 点击 |
 | backgroundColor | Color? | - | 背景颜色 |
 | buttonStyle | TDialogButtonStyle | TDialogButtonStyle.normal | 按钮样式 |
 | buttonStyleCustom | TButtonStyle? | - | 按钮自定义样式属性,背景色、边框... |
@@ -51,35 +66,31 @@
 | contentColor | Color? | - | 内容颜色 |
 | contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
 | contentWidget | Widget? | - | 内容Widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
 | showCloseButton | bool? | - | 右上角关闭按钮 |
 | title | String? | - | 标题 |
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
 | titleColor | Color? | - | 标题颜色 |
-| width |  | - |  |
+| width | double? | - | - |
 
-```
-```
 
 ### TDialogButtonOptions
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| action |  Function()? | - | 点击操作 |
+| action | Function()? | - | 点击操作 |
 | fontWeight | FontWeight? | - | 字体粗细 |
-| height | double? | - | 按钮高度 |
-| style | TButtonStyle? | - | 按钮样式 |
+| height | double? | - | 按钮高度 建议使用默认高度 |
+| style | TButtonStyle? | - | 按钮样式 设置单个按钮的样式会覆盖Dialog的默认样式 |
 | theme | TButtonTheme? | - | 按钮类型 |
 | title | String | - | 标题内容 |
 | titleColor | Color? | - | 标题颜色 |
 | titleSize | double? | - | 字体大小 |
 | type | TButtonType? | - | 按钮类型 |
 
-```
-```
 
 ### TDialogScaffold
 #### 默认构造方法
@@ -88,25 +99,21 @@
 | --- | --- | --- | --- |
 | backgroundColor | Color? | - | 背景色 |
 | body | Widget | - | Dialog主体 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | radius | double | 12.0 | 圆角 |
 | showCloseButton | bool? | - | 显示右上角关闭按钮 |
 | width | double? | - | 弹窗宽度 |
 
-```
-```
 
 ### TDialogTitle
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | title | String? | - | 标题文字 |
 | titleColor | Color? | - | 标题颜色 |
 
-```
-```
 
 ### TDialogContent
 #### 默认构造方法
@@ -115,10 +122,8 @@
 | --- | --- | --- | --- |
 | content | String? | - | 标题文字 |
 | contentColor | Color? | - | 标题颜色 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 
-```
-```
 
 ### TDialogInfoWidget
 #### 默认构造方法
@@ -129,38 +134,32 @@
 | contentColor | Color? | - | 内容颜色 |
 | contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
 | contentWidget | Widget? | - | 内容Widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | padding | EdgeInsetsGeometry? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容的内边距 |
 | title | String? | - | 标题 |
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
 | titleColor | Color? | - | 标题颜色 |
 
-```
-```
 
 ### HorizontalNormalButtons
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions | - | 左按钮 |
 | rightBtn | TDialogButtonOptions | - | 右按钮 |
 
-```
-```
 
 ### HorizontalTextButtons
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions | - | 左按钮 |
 | rightBtn | TDialogButtonOptions | - | 右按钮 |
 
-```
-```
 
 ### TDialogButton
 #### 默认构造方法
@@ -176,12 +175,10 @@
 | buttonType | TButtonType? | - | 按钮类型 |
 | height | double? | 40.0 | 按钮高度 |
 | isBlock | bool | true | 按钮高度 |
-| key |  | - |  |
-| onPressed |  Function() | - | 点击 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onPressed | Function() | - | 点击 |
 | width | double? | - | 按钮宽度 |
 
-```
-```
 
 ### TImageDialog
 #### 默认构造方法
@@ -195,7 +192,7 @@
 | contentWidget | Widget? | - | 内容Widget |
 | image | Image | - | 图片 |
 | imagePosition | TDialogImagePosition? | TDialogImagePosition.top | 图片位置 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions? | - | 左侧按钮配置 |
 | padding | EdgeInsets? | - | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
@@ -205,8 +202,6 @@
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
 | titleColor | Color? | - | 标题颜色 |
 
-```
-```
 
 ### TInputDialog
 #### 默认构造方法
@@ -220,7 +215,7 @@
 | contentWidget | Widget? | - | 内容Widget |
 | customInputWidget | Widget? | - | 自定义输入框 |
 | hintText | String? | '' | 输入提示 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions? | - | 左侧按钮配置 |
 | padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
@@ -230,3 +225,28 @@
 | title | String? | - | 标题 |
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
 | titleColor | Color? | - | 标题颜色 |
+
+
+### TDialogButtonStyle
+#### 简介
+Dialog按钮样式
+
+ 用于在Dialog层面配置按钮样式
+ Dialog内支持配置每个按钮的样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| text | - |
+
+
+### TDialogImagePosition
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| top | - |
+| middle | - |
diff --git a/tdesign-component/example/assets/api/divider_api.md b/tdesign-component/example/assets/api/divider_api.md
index 19030afc5..72e5ed264 100644
--- a/tdesign-component/example/assets/api/divider_api.md
+++ b/tdesign-component/example/assets/api/divider_api.md
@@ -11,9 +11,20 @@
 | height | double | 0.5 | 高度,横向线条使用 |
 | hideLine | bool | false | 隐藏线条,使用纯文本分割 |
 | isDashed | bool | false | 是否为虚线 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | margin | EdgeInsetsGeometry? | - | 外部填充 |
 | text | String? | - | 文本字符串,使用默认样式 |
 | textStyle | TextStyle? | - | 自定义文本样式 |
 | widget | Widget? | - | 中间控件,可自定义样式 |
 | width | double? | - | 宽度,需要竖向线条时使用 |
+
+
+### TextAlignment
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| center | - |
+| right | - |
diff --git a/tdesign-component/example/assets/api/drawer_api.md b/tdesign-component/example/assets/api/drawer_api.md
index 6dcef5beb..9ca2934fa 100644
--- a/tdesign-component/example/assets/api/drawer_api.md
+++ b/tdesign-component/example/assets/api/drawer_api.md
@@ -6,11 +6,11 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
 | backgroundColor | Color? | - | 组件背景颜色 |
 | bordered | bool? | true | 是否显示边框 |
 | closeOnOverlayClick | bool? | true | 点击蒙层时是否关闭抽屉 |
 | contentWidget | Widget? | - | 自定义内容,优先级高于[items]/[footer]/[title] |
-| context | BuildContext | context | 上下文 |
 | drawerTop | double? | - | 距离顶部的距离 |
 | footer | Widget? | - | 抽屉的底部 |
 | hover | bool? | true | 是否开启点击反馈 |
@@ -26,8 +26,6 @@
 | visible | bool? | - | 组件是否可见 |
 | width | double? | 280 | 宽度 |
 
-```
-```
 
 ### TDrawerWidget
 #### 简介
@@ -44,15 +42,13 @@
 | hover | bool? | true | 是否开启点击反馈 |
 | isShowLastBordered | bool? | true | 是否显示最后一行分割线 |
 | items | List? | - | 抽屉里的列表项 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onItemClick | TDrawerItemClickCallback? | - | 点击抽屉里的列表项触发 |
 | style | TCellStyle? | - | 列表自定义样式 |
 | title | String? | - | 抽屉的标题 |
 | titleWidget | Widget? | - | 抽屉的标题组件 |
 | width | double? | 280 | 宽度 |
 
-```
-```
 
 ### TDrawerItem
 #### 简介
@@ -64,3 +60,23 @@
 | content | Widget? | - | 完全自定义 |
 | icon | Widget? | - | 每列图标 |
 | title | String? | - | 每列标题 |
+
+
+### TDrawerPlacement
+#### 简介
+抽屉方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+
+
+### TDrawerItemClickCallback
+#### 类型定义
+
+```dart
+typedef TDrawerItemClickCallback = void Function(int index, TDrawerItem item);
+```
diff --git a/tdesign-component/example/assets/api/dropdown-menu_api.md b/tdesign-component/example/assets/api/dropdown-menu_api.md
index e19bc36b7..940cadce5 100644
--- a/tdesign-component/example/assets/api/dropdown-menu_api.md
+++ b/tdesign-component/example/assets/api/dropdown-menu_api.md
@@ -16,7 +16,7 @@
 | height | double? | 48 | menu的高度 |
 | isScrollable | bool? | false | 是否开启滚动列表 |
 | items | List? | - | 下拉菜单 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelBuilder | LabelBuilder? | - | 自定义标签内容 |
 | onMenuClosed | ValueChanged? | - | 关闭菜单事件 |
 | onMenuOpened | ValueChanged? | - | 展开菜单事件 |
@@ -24,8 +24,6 @@
 | tabBarAlign | MainAxisAlignment? | MainAxisAlignment.center | [TDropdownItem.label]和[arrowIcon]/[TDropdownItem.arrowIcon]的对齐方式 |
 | width | double? | - | menu的宽度 |
 
-```
-```
 
 ### TDropdownItem
 #### 简介
@@ -39,7 +37,7 @@
 | builder | TDropdownItemContentBuilder? | - | 完全自定义展示内容 |
 | controller | TDropdownItemController? | - | 下拉菜单控制器 |
 | disabled | bool? | false | 是否禁用 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String? | - | 标题 |
 | maxHeight | double? | - | 内容最大高度 |
 | minHeight | double? | - | 内容最小高度 |
@@ -53,8 +51,12 @@
 | tabBarFlex | int? | 1 | 该item在menu上的宽度占比,仅在[TDropdownMenu.isScrollable]为false时有效 |
 | tabBarWidth | double? | - | 该item在menu上的宽度,仅在[TDropdownMenu.isScrollable]为true时有效 |
 
-```
-```
+#### 静态成员
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| operateHeight | double | - | - |
+
 
 ### TDropdownItemOption
 #### 简介
@@ -71,9 +73,55 @@
 | selectedColor | Color? | - | 选中颜色 |
 | value | String | - | 选项值 |
 
+
+### TDropdownItemController
+#### 简介
+下拉菜单控制器
+
+### TDropdownMenuDirection
+#### 简介
+菜单展开方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| down | 向下 |
+| up | 向上 |
+| auto | 根据内容高度动态展示方向 |
+
+
+### TDropdownItemContentBuilder
+#### 类型定义
+
+```dart
+typedef TDropdownItemContentBuilder = Widget Function(BuildContext context, _TDropdownItemState itemState, TDropdownPopup? popupState);
 ```
+
+
+### TDropdownItemOptionsCallback
+#### 类型定义
+
+```dart
+typedef TDropdownItemOptionsCallback = void Function(List? options);
 ```
 
-### TDropdownItemController
+
+### TDropdownItemBuilder
 #### 简介
-下拉菜单控制器
\ No newline at end of file
+下拉菜单构建器
+#### 类型定义
+
+```dart
+typedef TDropdownItemBuilder = List Function(BuildContext context);
+```
+
+
+### LabelBuilder
+#### 简介
+自定义标签内容
+#### 类型定义
+
+```dart
+typedef LabelBuilder = Widget Function(BuildContext context, String label, bool isOpened, int index);
+```
diff --git a/tdesign-component/example/assets/api/empty_api.md b/tdesign-component/example/assets/api/empty_api.md
index e39301b33..ba743b257 100644
--- a/tdesign-component/example/assets/api/empty_api.md
+++ b/tdesign-component/example/assets/api/empty_api.md
@@ -10,8 +10,26 @@
 | emptyTextFont | Font? | - | 描述文字大小 |
 | icon | IconData? | TIcons.info_circle_filled | 图标 |
 | image | Widget? | - | 展示图片 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onTapEvent | TTapEvent? | - | 点击事件 |
 | operationText | String? | - | 操作按钮文案 |
 | operationTheme | TButtonTheme? | - | 操作按钮文案主题色 |
 | type | TEmptyType | TEmptyType.plain | 类型,为operation有操作按钮,plain无按钮 |
+
+
+### TEmptyType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| plain | - |
+| operation | - |
+
+
+### TTapEvent
+#### 类型定义
+
+```dart
+typedef TTapEvent = void Function();
+```
diff --git a/tdesign-component/example/assets/api/fab_api.md b/tdesign-component/example/assets/api/fab_api.md
index 641de8127..405f3c9fd 100644
--- a/tdesign-component/example/assets/api/fab_api.md
+++ b/tdesign-component/example/assets/api/fab_api.md
@@ -5,9 +5,43 @@
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | icon | Icon? | - | 图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onClick | VoidCallback? | - | 点击事件 |
 | shape | TFabShape | TFabShape.circle | 形状 |
 | size | TFabSize | TFabSize.large | 大小 |
 | text | String? | - | 文本 |
 | theme | TFabTheme | TFabTheme.defaultTheme | 主题 |
+
+
+### TFabTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| primary | - |
+| defaultTheme | - |
+| light | - |
+| danger | - |
+
+
+### TFabShape
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
+
+
+### TFabSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+| extraSmall | - |
diff --git a/tdesign-component/example/assets/api/footer_api.md b/tdesign-component/example/assets/api/footer_api.md
index 17deb0158..f5df55732 100644
--- a/tdesign-component/example/assets/api/footer_api.md
+++ b/tdesign-component/example/assets/api/footer_api.md
@@ -4,10 +4,21 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| type | TFooterType | - | 样式 |
 | height | double? | - | 自定义图片高 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | links | List | const [] | 链接 |
 | logo | String? | - | 品牌图片 |
 | text | String | '' | 文字 |
-| type | TFooterType | type | 样式 |
 | width | double? | - | 自定义图片宽 |
+
+
+### TFooterType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| text | 文字样式 |
+| link | 链接样式 |
+| brand | 品牌样式 |
diff --git a/tdesign-component/example/assets/api/form_api.md b/tdesign-component/example/assets/api/form_api.md
index 61922773d..948830f5e 100644
--- a/tdesign-component/example/assets/api/form_api.md
+++ b/tdesign-component/example/assets/api/form_api.md
@@ -9,24 +9,22 @@
 | data | Map | - | 表单数据 |
 | disabled | bool | false | 是否禁用整个表单 |
 | errorMessage | Object? | - | 表单信息错误信息配置 |
-| formContentAlign | TextAlign | TextAlign.left | 表单内容对齐方式: 左对齐、右对齐、居中对齐 |
+| formContentAlign | TextAlign | TextAlign.left | 表单内容对齐方式: 左对齐、右对齐、居中对齐 可选项: left/right/center 默认为左对齐 优先级低于 TFormItem 的对齐 API TODO: TStepper TRate 等组件没用实现通用性 |
 | formController | FormController? | - | 表单控制器 |
-| formLabelAlign | TextAlign? | TextAlign.left | 表单字段标签的对齐方式: |
-| formShowErrorMessage | bool? | true | 校验不通过时,是否显示错误提示信息,统一控制全部表单项 |
+| formLabelAlign | TextAlign? | TextAlign.left | 表单字段标签的对齐方式: 左对齐、右对齐、顶部对齐 可选项: left/right/top TODO: 表单总体标签对齐方式 |
+| formShowErrorMessage | bool? | true | 校验不通过时,是否显示错误提示信息,统一控制全部表单项 如果希望控制单个表单项,请给 FormItem 设置该属性 |
 | isHorizontal | bool | true | 表单排列方式是否为 水平方向 |
 | items | List | - | 表单内容 items |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelWidth | double? | 20.0 | 可以整体设置 label 标签宽度 |
 | onReset | Function? | - | 表单重置时触发 |
 | onSubmit | Function | - | 表单提交时触发 |
-| preventSubmitDefault | bool? | true | 是否阻止表单提交默认事件(表单提交默认事件会刷新页面) |
+| preventSubmitDefault | bool? | true | 是否阻止表单提交默认事件(表单提交默认事件会刷新页面) 设置为 true 可以避免刷新 |
 | requiredMark | bool? | true | 是否显示必填符号(*),默认显示 |
 | rules | Map | - | 整个表单字段校验规则 |
-| scrollToFirstError | String? | - | 表单校验不通过时,是否自动滚动到第一个校验不通过的字段,平滑滚动或是瞬间直达。 |
+| scrollToFirstError | String? | - | 表单校验不通过时,是否自动滚动到第一个校验不通过的字段,平滑滚动或是瞬间直达。 值为空则表示不滚动。可选项:''/smooth/auto |
 | submitWithWarningMessage | bool? | false | 【讨论中】当校验结果只有告警信息时,是否触发 submit 提交事件 |
 
-```
-```
 
 ### TFormItem
 #### 默认构造方法
@@ -36,20 +34,20 @@
 | additionInfo | String? | - | TInput的辅助信息 |
 | backgroundColor | Color? | - | 背景色 |
 | child | Widget? | - | 表单子组件 |
-| contentAlign | TextAlign? | - | 表单显示内容对齐方式: |
-| formItemNotifier |  | - |  |
+| contentAlign | TextAlign? | - | 表单显示内容对齐方式: left、right、top TODO: TStepper TRate 等组件没用实现通用性 |
+| formItemNotifier | FormItemNotifier? | - | - |
 | formRules | List? | - | 整个表单的校验规则 |
 | help | String? | - | TInput 默认显示文字 |
-| hintText | null | '' | 提示内容 |
+| hintText | - | '' | 提示内容 |
 | indicator | bool? | - | TTextarea 的属性,指示器 |
 | itemRule | List? | - | 表单项验证规则 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String? | - | 表单项标签左侧展示的内容 |
-| labelAlign | TextAlign? | - | TODO: item 标签对齐方式 |
+| labelAlign | TextAlign? | - | TODO: item 标签对齐方式 可选: left、right、top |
 | labelWidget | Widget? | - | 自定义标签 |
 | labelWidth | double? | - | 标签宽度,如果提供则覆盖Form的labelWidth |
 | name | String? | - | 表单字段名称 |
-| radios |  | - |  |
+| radios | Map? | - | - |
 | requiredMark | bool? | true | 是否显示必填标记(*) |
 | select | String | '' | 选择器 适用于日期选择器等 |
 | selectFn | Function? | - | 选择器方法 适用于日期选择器等 |
@@ -57,8 +55,6 @@
 | tipAlign | TextAlign? | - | 组件提示内容对齐方式 |
 | type | TFormItemType | - | 表格单元需要使用的组件类型 |
 
-```
-```
 
 ### TFormValidation
 #### 默认构造方法
@@ -68,3 +64,21 @@
 | errorMessage | String | - | 错误提示信息 |
 | type | TFormItemType | - | 校验对象的类型 |
 | validate | String? Function(dynamic) | - | 校验方法 |
+
+
+### TFormItemType
+#### 简介
+表格单元选用组件类型的枚举
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| input | - |
+| radios | - |
+| dateTimePicker | - |
+| cascader | - |
+| stepper | - |
+| rate | - |
+| textarea | - |
+| upLoadImg | - |
diff --git a/tdesign-component/example/assets/api/icon_api.md b/tdesign-component/example/assets/api/icon_api.md
index 74dbb5f5b..73c5f852d 100644
--- a/tdesign-component/example/assets/api/icon_api.md
+++ b/tdesign-component/example/assets/api/icon_api.md
@@ -1,8 +1,2128 @@
 ## API
 ### TIcons
+#### 静态成员
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| accessibility | - | - | - |
+| accessibility_filled | - | - | - |
+| activity | - | - | - |
+| activity_filled | - | - | - |
+| add | - | - | - |
+| add_and_subtract | - | - | - |
+| add_circle | - | - | - |
+| add_circle_filled | - | - | - |
+| add_rectangle | - | - | - |
+| add_rectangle_filled | - | - | - |
+| address_book | - | - | - |
+| address_book_filled | - | - | - |
+| adjustment | - | - | - |
+| adjustment_filled | - | - | - |
+| airplay_wave | - | - | - |
+| airplay_wave_filled | - | - | - |
+| alarm | - | - | - |
+| alarm_add | - | - | - |
+| alarm_add_filled | - | - | - |
+| alarm_filled | - | - | - |
+| alarm_off | - | - | - |
+| alarm_off_filled | - | - | - |
+| align_top | - | - | - |
+| align_vertical | - | - | - |
+| all | - | - | - |
+| alpha | - | - | - |
+| analytics | - | - | - |
+| analytics_filled | - | - | - |
+| anchor | - | - | - |
+| angry | - | - | - |
+| angry_filled | - | - | - |
+| animation | - | - | - |
+| animation_1 | - | - | - |
+| animation_1_filled | - | - | - |
+| animation_filled | - | - | - |
+| anticlockwise | - | - | - |
+| anticlockwise_filled | - | - | - |
+| api | - | - | - |
+| app | - | - | - |
+| app_filled | - | - | - |
+| apple | - | - | - |
+| apple_filled | - | - | - |
+| application | - | - | - |
+| application_filled | - | - | - |
+| architecture_hui_style | - | - | - |
+| architecture_hui_style_filled | - | - | - |
+| archway | - | - | - |
+| archway_1 | - | - | - |
+| archway_1_filled | - | - | - |
+| archway_filled | - | - | - |
+| arrow_down | - | - | - |
+| arrow_down_circle | - | - | - |
+| arrow_down_circle_filled | - | - | - |
+| arrow_down_rectangle | - | - | - |
+| arrow_down_rectangle_filled | - | - | - |
+| arrow_left | - | - | - |
+| arrow_left_circle | - | - | - |
+| arrow_left_circle_filled | - | - | - |
+| arrow_left_down | - | - | - |
+| arrow_left_down_circle | - | - | - |
+| arrow_left_down_circle_filled | - | - | - |
+| arrow_left_right_1 | - | - | - |
+| arrow_left_right_2 | - | - | - |
+| arrow_left_right_3 | - | - | - |
+| arrow_left_right_circle | - | - | - |
+| arrow_left_right_circle_filled | - | - | - |
+| arrow_left_up | - | - | - |
+| arrow_left_up_circle | - | - | - |
+| arrow_left_up_circle_filled | - | - | - |
+| arrow_right | - | - | - |
+| arrow_right_circle | - | - | - |
+| arrow_right_circle_filled | - | - | - |
+| arrow_right_down | - | - | - |
+| arrow_right_down_circle | - | - | - |
+| arrow_right_down_circle_filled | - | - | - |
+| arrow_right_up | - | - | - |
+| arrow_right_up_circle | - | - | - |
+| arrow_right_up_circle_filled | - | - | - |
+| arrow_triangle_down | - | - | - |
+| arrow_triangle_down_filled | - | - | - |
+| arrow_triangle_up | - | - | - |
+| arrow_triangle_up_filled | - | - | - |
+| arrow_up | - | - | - |
+| arrow_up_circle | - | - | - |
+| arrow_up_circle_filled | - | - | - |
+| arrow_up_down_1 | - | - | - |
+| arrow_up_down_2 | - | - | - |
+| arrow_up_down_3 | - | - | - |
+| arrow_up_down_circle | - | - | - |
+| arrow_up_down_circle_filled | - | - | - |
+| artboard | - | - | - |
+| article | - | - | - |
+| article_filled | - | - | - |
+| assignment | - | - | - |
+| assignment_checked | - | - | - |
+| assignment_checked_filled | - | - | - |
+| assignment_code | - | - | - |
+| assignment_code_filled | - | - | - |
+| assignment_error | - | - | - |
+| assignment_error_filled | - | - | - |
+| assignment_filled | - | - | - |
+| assignment_user | - | - | - |
+| assignment_user_filled | - | - | - |
+| attach | - | - | - |
+| attic | - | - | - |
+| attic_1 | - | - | - |
+| attic_1_filled | - | - | - |
+| attic_filled | - | - | - |
+| audio | - | - | - |
+| audio_filled | - | - | - |
+| awkward | - | - | - |
+| awkward_filled | - | - | - |
+| backtop | - | - | - |
+| backtop_rectangle | - | - | - |
+| backtop_rectangle_filled | - | - | - |
+| backup | - | - | - |
+| backup_filled | - | - | - |
+| backward | - | - | - |
+| backward_filled | - | - | - |
+| bad_laugh | - | - | - |
+| bad_laugh_filled | - | - | - |
+| bamboo_shoot | - | - | - |
+| bamboo_shoot_filled | - | - | - |
+| banana | - | - | - |
+| banana_filled | - | - | - |
+| barbecue | - | - | - |
+| barbecue_filled | - | - | - |
+| barcode | - | - | - |
+| barcode_1 | - | - | - |
+| base_station | - | - | - |
+| battery | - | - | - |
+| battery_add | - | - | - |
+| battery_add_filled | - | - | - |
+| battery_charging | - | - | - |
+| battery_charging_filled | - | - | - |
+| battery_filled | - | - | - |
+| battery_low | - | - | - |
+| battery_low_filled | - | - | - |
+| bean | - | - | - |
+| bean_filled | - | - | - |
+| beer | - | - | - |
+| beer_filled | - | - | - |
+| beta | - | - | - |
+| bifurcate | - | - | - |
+| bifurcate_filled | - | - | - |
+| bill | - | - | - |
+| bill_filled | - | - | - |
+| bluetooth | - | - | - |
+| bone | - | - | - |
+| bone_filled | - | - | - |
+| book | - | - | - |
+| book_filled | - | - | - |
+| book_open | - | - | - |
+| book_open_filled | - | - | - |
+| book_unknown | - | - | - |
+| book_unknown_filled | - | - | - |
+| bookmark | - | - | - |
+| bookmark_add | - | - | - |
+| bookmark_add_filled | - | - | - |
+| bookmark_checked | - | - | - |
+| bookmark_checked_filled | - | - | - |
+| bookmark_double | - | - | - |
+| bookmark_double_filled | - | - | - |
+| bookmark_filled | - | - | - |
+| bookmark_minus | - | - | - |
+| bookmark_minus_filled | - | - | - |
+| braces | - | - | - |
+| brackets | - | - | - |
+| bread | - | - | - |
+| bread_filled | - | - | - |
+| bridge | - | - | - |
+| bridge_1 | - | - | - |
+| bridge_1_filled | - | - | - |
+| bridge_2 | - | - | - |
+| bridge_2_filled | - | - | - |
+| bridge_3 | - | - | - |
+| bridge_4 | - | - | - |
+| bridge_5 | - | - | - |
+| bridge_5_filled | - | - | - |
+| bridge_6 | - | - | - |
+| bridge_6_filled | - | - | - |
+| brightness | - | - | - |
+| brightness_1 | - | - | - |
+| brightness_1_filled | - | - | - |
+| brightness_filled | - | - | - |
+| broccoli | - | - | - |
+| broccoli_filled | - | - | - |
+| browse | - | - | - |
+| browse_filled | - | - | - |
+| browse_gallery | - | - | - |
+| browse_gallery_filled | - | - | - |
+| browse_off | - | - | - |
+| browse_off_filled | - | - | - |
+| brush | - | - | - |
+| brush_filled | - | - | - |
+| bug | - | - | - |
+| bug_filled | - | - | - |
+| bug_report | - | - | - |
+| bug_report_filled | - | - | - |
+| building | - | - | - |
+| building_1 | - | - | - |
+| building_1_filled | - | - | - |
+| building_2 | - | - | - |
+| building_2_filled | - | - | - |
+| building_3 | - | - | - |
+| building_3_filled | - | - | - |
+| building_4 | - | - | - |
+| building_4_filled | - | - | - |
+| building_5 | - | - | - |
+| building_5_filled | - | - | - |
+| building_filled | - | - | - |
+| bulletpoint | - | - | - |
+| button | - | - | - |
+| button_filled | - | - | - |
+| cabbage | - | - | - |
+| cabbage_filled | - | - | - |
+| cake | - | - | - |
+| cake_filled | - | - | - |
+| calculation | - | - | - |
+| calculation_1 | - | - | - |
+| calculation_1_filled | - | - | - |
+| calculator | - | - | - |
+| calculator_1 | - | - | - |
+| calculator_filled | - | - | - |
+| calendar | - | - | - |
+| calendar_1 | - | - | - |
+| calendar_1_filled | - | - | - |
+| calendar_2 | - | - | - |
+| calendar_2_filled | - | - | - |
+| calendar_edit | - | - | - |
+| calendar_edit_filled | - | - | - |
+| calendar_event | - | - | - |
+| calendar_event_filled | - | - | - |
+| calendar_filled | - | - | - |
+| call | - | - | - |
+| call_1 | - | - | - |
+| call_1_filled | - | - | - |
+| call_cancel | - | - | - |
+| call_cancel_filled | - | - | - |
+| call_filled | - | - | - |
+| call_forwarded | - | - | - |
+| call_forwarded_filled | - | - | - |
+| call_incoming | - | - | - |
+| call_incoming_filled | - | - | - |
+| call_off | - | - | - |
+| call_off_filled | - | - | - |
+| calm | - | - | - |
+| calm_1 | - | - | - |
+| calm_1_filled | - | - | - |
+| calm_filled | - | - | - |
+| camera | - | - | - |
+| camera_1 | - | - | - |
+| camera_1_filled | - | - | - |
+| camera_2 | - | - | - |
+| camera_2_filled | - | - | - |
+| camera_filled | - | - | - |
+| camera_off | - | - | - |
+| camera_off_filled | - | - | - |
+| candy | - | - | - |
+| candy_filled | - | - | - |
+| card | - | - | - |
+| card_filled | - | - | - |
+| cardmembership | - | - | - |
+| cardmembership_filled | - | - | - |
+| caret_down | - | - | - |
+| caret_down_small | - | - | - |
+| caret_left | - | - | - |
+| caret_left_small | - | - | - |
+| caret_right | - | - | - |
+| caret_right_small | - | - | - |
+| caret_up | - | - | - |
+| caret_up_small | - | - | - |
+| cart | - | - | - |
+| cart_add | - | - | - |
+| cart_add_filled | - | - | - |
+| cart_filled | - | - | - |
+| cast | - | - | - |
+| cast_filled | - | - | - |
+| castle | - | - | - |
+| castle_1 | - | - | - |
+| castle_1_filled | - | - | - |
+| castle_2 | - | - | - |
+| castle_2_filled | - | - | - |
+| castle_3 | - | - | - |
+| castle_3_filled | - | - | - |
+| castle_4 | - | - | - |
+| castle_4_filled | - | - | - |
+| castle_5 | - | - | - |
+| castle_5_filled | - | - | - |
+| castle_6 | - | - | - |
+| castle_6_filled | - | - | - |
+| castle_7 | - | - | - |
+| castle_7_filled | - | - | - |
+| castle_filled | - | - | - |
+| cat | - | - | - |
+| cat_filled | - | - | - |
+| catalog | - | - | - |
+| catalog_filled | - | - | - |
+| cd | - | - | - |
+| cd_filled | - | - | - |
+| celsius | - | - | - |
+| center_focus_strong | - | - | - |
+| center_focus_strong_filled | - | - | - |
+| centimeter | - | - | - |
+| certificate | - | - | - |
+| certificate_1 | - | - | - |
+| certificate_1_filled | - | - | - |
+| certificate_filled | - | - | - |
+| chart | - | - | - |
+| chart_3d | - | - | - |
+| chart_3d_filled | - | - | - |
+| chart_add | - | - | - |
+| chart_add_filled | - | - | - |
+| chart_analytics | - | - | - |
+| chart_area | - | - | - |
+| chart_area_filled | - | - | - |
+| chart_area_multi | - | - | - |
+| chart_area_multi_filled | - | - | - |
+| chart_bar | - | - | - |
+| chart_bar_filled | - | - | - |
+| chart_bubble | - | - | - |
+| chart_bubble_filled | - | - | - |
+| chart_column | - | - | - |
+| chart_column_filled | - | - | - |
+| chart_combo | - | - | - |
+| chart_combo_filled | - | - | - |
+| chart_filled | - | - | - |
+| chart_line | - | - | - |
+| chart_line_data | - | - | - |
+| chart_line_data_1 | - | - | - |
+| chart_line_multi | - | - | - |
+| chart_maximum | - | - | - |
+| chart_median | - | - | - |
+| chart_minimum | - | - | - |
+| chart_pie | - | - | - |
+| chart_pie_filled | - | - | - |
+| chart_radar | - | - | - |
+| chart_radar_filled | - | - | - |
+| chart_radial | - | - | - |
+| chart_ring | - | - | - |
+| chart_ring_1 | - | - | - |
+| chart_ring_1_filled | - | - | - |
+| chart_ring_filled | - | - | - |
+| chart_scatter | - | - | - |
+| chart_stacked | - | - | - |
+| chart_stacked_filled | - | - | - |
+| chat | - | - | - |
+| chat_add | - | - | - |
+| chat_add_filled | - | - | - |
+| chat_bubble | - | - | - |
+| chat_bubble_1 | - | - | - |
+| chat_bubble_1_filled | - | - | - |
+| chat_bubble_add | - | - | - |
+| chat_bubble_add_filled | - | - | - |
+| chat_bubble_error | - | - | - |
+| chat_bubble_error_filled | - | - | - |
+| chat_bubble_filled | - | - | - |
+| chat_bubble_help | - | - | - |
+| chat_bubble_help_filled | - | - | - |
+| chat_bubble_history | - | - | - |
+| chat_bubble_history_filled | - | - | - |
+| chat_bubble_locked | - | - | - |
+| chat_bubble_locked_filled | - | - | - |
+| chat_bubble_smile | - | - | - |
+| chat_bubble_smile_filled | - | - | - |
+| chat_checked | - | - | - |
+| chat_checked_filled | - | - | - |
+| chat_clear | - | - | - |
+| chat_clear_filled | - | - | - |
+| chat_double | - | - | - |
+| chat_double_filled | - | - | - |
+| chat_error | - | - | - |
+| chat_error_filled | - | - | - |
+| chat_filled | - | - | - |
+| chat_heart | - | - | - |
+| chat_heart_filled | - | - | - |
+| chat_message | - | - | - |
+| chat_message_filled | - | - | - |
+| chat_off | - | - | - |
+| chat_off_filled | - | - | - |
+| chat_poll | - | - | - |
+| chat_poll_filled | - | - | - |
+| chat_setting | - | - | - |
+| chat_setting_filled | - | - | - |
+| check | - | - | - |
+| check_circle | - | - | - |
+| check_circle_filled | - | - | - |
+| check_double | - | - | - |
+| check_rectangle | - | - | - |
+| check_rectangle_filled | - | - | - |
+| cheese | - | - | - |
+| cheese_filled | - | - | - |
+| cherry | - | - | - |
+| cherry_filled | - | - | - |
+| chevron_down | - | - | - |
+| chevron_down_circle | - | - | - |
+| chevron_down_circle_filled | - | - | - |
+| chevron_down_double | - | - | - |
+| chevron_down_double_s | - | - | - |
+| chevron_down_rectangle | - | - | - |
+| chevron_down_rectangle_filled | - | - | - |
+| chevron_down_s | - | - | - |
+| chevron_left | - | - | - |
+| chevron_left_circle | - | - | - |
+| chevron_left_circle_filled | - | - | - |
+| chevron_left_double | - | - | - |
+| chevron_left_double_s | - | - | - |
+| chevron_left_rectangle | - | - | - |
+| chevron_left_rectangle_filled | - | - | - |
+| chevron_left_s | - | - | - |
+| chevron_right | - | - | - |
+| chevron_right_circle | - | - | - |
+| chevron_right_circle_filled | - | - | - |
+| chevron_right_double | - | - | - |
+| chevron_right_double_s | - | - | - |
+| chevron_right_rectangle | - | - | - |
+| chevron_right_rectangle_filled | - | - | - |
+| chevron_right_s | - | - | - |
+| chevron_up | - | - | - |
+| chevron_up_circle | - | - | - |
+| chevron_up_circle_filled | - | - | - |
+| chevron_up_double | - | - | - |
+| chevron_up_double_s | - | - | - |
+| chevron_up_rectangle | - | - | - |
+| chevron_up_rectangle_filled | - | - | - |
+| chevron_up_s | - | - | - |
+| chicken | - | - | - |
+| chili | - | - | - |
+| chili_filled | - | - | - |
+| chimney | - | - | - |
+| chimney_1 | - | - | - |
+| chimney_1_filled | - | - | - |
+| chimney_2 | - | - | - |
+| chimney_2_filled | - | - | - |
+| chimney_filled | - | - | - |
+| chinese_cabbage | - | - | - |
+| chinese_cabbage_filled | - | - | - |
+| church | - | - | - |
+| church_filled | - | - | - |
+| circle | - | - | - |
+| circle_filled | - | - | - |
+| city | - | - | - |
+| city_1 | - | - | - |
+| city_10 | - | - | - |
+| city_10_filled | - | - | - |
+| city_11 | - | - | - |
+| city_11_filled | - | - | - |
+| city_12 | - | - | - |
+| city_12_filled | - | - | - |
+| city_13 | - | - | - |
+| city_13_filled | - | - | - |
+| city_14 | - | - | - |
+| city_14_filled | - | - | - |
+| city_15 | - | - | - |
+| city_15_filled | - | - | - |
+| city_1_filled | - | - | - |
+| city_2 | - | - | - |
+| city_2_filled | - | - | - |
+| city_3 | - | - | - |
+| city_3_filled | - | - | - |
+| city_4 | - | - | - |
+| city_4_filled | - | - | - |
+| city_5 | - | - | - |
+| city_5_filled | - | - | - |
+| city_6 | - | - | - |
+| city_6_filled | - | - | - |
+| city_7 | - | - | - |
+| city_7_filled | - | - | - |
+| city_8 | - | - | - |
+| city_8_filled | - | - | - |
+| city_9 | - | - | - |
+| city_9_filled | - | - | - |
+| city_ancient | - | - | - |
+| city_ancient_1 | - | - | - |
+| city_ancient_1_filled | - | - | - |
+| city_ancient_2 | - | - | - |
+| city_ancient_2_filled | - | - | - |
+| city_ancient_filled | - | - | - |
+| city_filled | - | - | - |
+| clear | - | - | - |
+| clear_filled | - | - | - |
+| clear_formatting | - | - | - |
+| clear_formatting_1 | - | - | - |
+| clear_formatting_1_filled | - | - | - |
+| clear_formatting_filled | - | - | - |
+| close | - | - | - |
+| close_circle | - | - | - |
+| close_circle_filled | - | - | - |
+| close_octagon | - | - | - |
+| close_octagon_filled | - | - | - |
+| close_rectangle | - | - | - |
+| close_rectangle_filled | - | - | - |
+| cloud | - | - | - |
+| cloud_download | - | - | - |
+| cloud_filled | - | - | - |
+| cloud_upload | - | - | - |
+| cloudy_day | - | - | - |
+| cloudy_day_filled | - | - | - |
+| cloudy_night | - | - | - |
+| cloudy_night_filled | - | - | - |
+| cloudy_night_rain | - | - | - |
+| cloudy_night_rain_filled | - | - | - |
+| cloudy_rain | - | - | - |
+| cloudy_rain_filled | - | - | - |
+| cloudy_sunny | - | - | - |
+| cloudy_sunny_filled | - | - | - |
+| code | - | - | - |
+| code_1 | - | - | - |
+| code_off | - | - | - |
+| cola | - | - | - |
+| cola_filled | - | - | - |
+| collage | - | - | - |
+| collage_filled | - | - | - |
+| collection | - | - | - |
+| collection_filled | - | - | - |
+| color_invert | - | - | - |
+| color_invert_filled | - | - | - |
+| combination | - | - | - |
+| combination_filled | - | - | - |
+| command | - | - | - |
+| compass | - | - | - |
+| compass_1 | - | - | - |
+| compass_1_filled | - | - | - |
+| compass_filled | - | - | - |
+| component_breadcrumb | - | - | - |
+| component_breadcrumb_filled | - | - | - |
+| component_checkbox | - | - | - |
+| component_checkbox_filled | - | - | - |
+| component_divider_horizontal | - | - | - |
+| component_divider_horizontal_filled | - | - | - |
+| component_divider_vertical | - | - | - |
+| component_divider_vertical_filled | - | - | - |
+| component_dropdown | - | - | - |
+| component_dropdown_filled | - | - | - |
+| component_grid | - | - | - |
+| component_grid_filled | - | - | - |
+| component_input | - | - | - |
+| component_input_filled | - | - | - |
+| component_layout | - | - | - |
+| component_layout_filled | - | - | - |
+| component_radio | - | - | - |
+| component_space | - | - | - |
+| component_space_filled | - | - | - |
+| component_steps | - | - | - |
+| component_steps_filled | - | - | - |
+| component_switch | - | - | - |
+| component_switch_filled | - | - | - |
+| constraint | - | - | - |
+| contrast | - | - | - |
+| contrast_1 | - | - | - |
+| contrast_1_filled | - | - | - |
+| contrast_filled | - | - | - |
+| control_platform | - | - | - |
+| control_platform_filled | - | - | - |
+| cooperate | - | - | - |
+| cooperate_filled | - | - | - |
+| coordinate_system | - | - | - |
+| coordinate_system_filled | - | - | - |
+| copy | - | - | - |
+| copy_filled | - | - | - |
+| copyright | - | - | - |
+| copyright_filled | - | - | - |
+| corn | - | - | - |
+| corn_filled | - | - | - |
+| coupon | - | - | - |
+| coupon_filled | - | - | - |
+| course | - | - | - |
+| course_filled | - | - | - |
+| cpu | - | - | - |
+| cpu_filled | - | - | - |
+| crack | - | - | - |
+| crack_filled | - | - | - |
+| creditcard | - | - | - |
+| creditcard_add | - | - | - |
+| creditcard_add_filled | - | - | - |
+| creditcard_filled | - | - | - |
+| creditcard_off | - | - | - |
+| creditcard_off_filled | - | - | - |
+| crooked_smile | - | - | - |
+| crooked_smile_filled | - | - | - |
+| cry_and_laugh | - | - | - |
+| cry_and_laugh_filled | - | - | - |
+| cry_loudly | - | - | - |
+| cry_loudly_filled | - | - | - |
+| css3 | - | - | - |
+| css3_filled | - | - | - |
+| cucumber | - | - | - |
+| currency_exchange | - | - | - |
+| cursor | - | - | - |
+| cursor_filled | - | - | - |
+| curtain | - | - | - |
+| curtain_filled | - | - | - |
+| curve | - | - | - |
+| cut | - | - | - |
+| cut_1 | - | - | - |
+| dam | - | - | - |
+| dam_1 | - | - | - |
+| dam_1_filled | - | - | - |
+| dam_2 | - | - | - |
+| dam_2_filled | - | - | - |
+| dam_3 | - | - | - |
+| dam_3_filled | - | - | - |
+| dam_4 | - | - | - |
+| dam_4_filled | - | - | - |
+| dam_5 | - | - | - |
+| dam_5_filled | - | - | - |
+| dam_6 | - | - | - |
+| dam_6_filled | - | - | - |
+| dam_7 | - | - | - |
+| dam_7_filled | - | - | - |
+| dam_filled | - | - | - |
+| dart_board | - | - | - |
+| dart_board_filled | - | - | - |
+| dashboard | - | - | - |
+| dashboard_1 | - | - | - |
+| dashboard_1_filled | - | - | - |
+| dashboard_filled | - | - | - |
+| data | - | - | - |
+| data_base | - | - | - |
+| data_base_filled | - | - | - |
+| data_checked | - | - | - |
+| data_checked_filled | - | - | - |
+| data_display | - | - | - |
+| data_error | - | - | - |
+| data_error_filled | - | - | - |
+| data_filled | - | - | - |
+| data_search | - | - | - |
+| data_search_filled | - | - | - |
+| delete | - | - | - |
+| delete_1 | - | - | - |
+| delete_1_filled | - | - | - |
+| delete_filled | - | - | - |
+| delete_time | - | - | - |
+| delete_time_filled | - | - | - |
+| delta | - | - | - |
+| delta_filled | - | - | - |
+| depressed | - | - | - |
+| depressed_filled | - | - | - |
+| desktop | - | - | - |
+| desktop_1 | - | - | - |
+| desktop_1_filled | - | - | - |
+| desktop_filled | - | - | - |
+| despise | - | - | - |
+| despise_filled | - | - | - |
+| device | - | - | - |
+| device_filled | - | - | - |
+| discount | - | - | - |
+| discount_filled | - | - | - |
+| dissatisfaction | - | - | - |
+| dissatisfaction_filled | - | - | - |
+| divide | - | - | - |
+| dividers | - | - | - |
+| dividers_1 | - | - | - |
+| doge | - | - | - |
+| doge_filled | - | - | - |
+| double_storey | - | - | - |
+| double_storey_filled | - | - | - |
+| download | - | - | - |
+| download_1 | - | - | - |
+| download_2 | - | - | - |
+| download_2_filled | - | - | - |
+| downscale | - | - | - |
+| drag_drop | - | - | - |
+| drag_move | - | - | - |
+| drink | - | - | - |
+| drink_filled | - | - | - |
+| drumstick | - | - | - |
+| drumstick_filled | - | - | - |
+| dv | - | - | - |
+| dv_filled | - | - | - |
+| dvd | - | - | - |
+| dvd_filled | - | - | - |
+| earphone | - | - | - |
+| earphone_filled | - | - | - |
+| earth | - | - | - |
+| earth_filled | - | - | - |
+| edit | - | - | - |
+| edit_1 | - | - | - |
+| edit_1_filled | - | - | - |
+| edit_2 | - | - | - |
+| edit_2_filled | - | - | - |
+| edit_filled | - | - | - |
+| edit_off | - | - | - |
+| edit_off_filled | - | - | - |
+| education | - | - | - |
+| education_filled | - | - | - |
+| eggplant | - | - | - |
+| eggplant_filled | - | - | - |
+| ellipsis | - | - | - |
+| emo_emotional | - | - | - |
+| emo_emotional_filled | - | - | - |
+| enter | - | - | - |
+| equal | - | - | - |
+| error | - | - | - |
+| error_circle | - | - | - |
+| error_circle_filled | - | - | - |
+| error_triangle | - | - | - |
+| error_triangle_filled | - | - | - |
+| excited | - | - | - |
+| excited_1 | - | - | - |
+| excited_1_filled | - | - | - |
+| excited_filled | - | - | - |
+| expand_down | - | - | - |
+| expand_down_filled | - | - | - |
+| expand_horizontal | - | - | - |
+| expand_up | - | - | - |
+| expand_up_filled | - | - | - |
+| expand_vertical | - | - | - |
+| explore | - | - | - |
+| explore_filled | - | - | - |
+| explore_off | - | - | - |
+| explore_off_filled | - | - | - |
+| exposure | - | - | - |
+| exposure_filled | - | - | - |
+| extension | - | - | - |
+| extension_filled | - | - | - |
+| extension_off | - | - | - |
+| extension_off_filled | - | - | - |
+| face_retouching | - | - | - |
+| face_retouching_filled | - | - | - |
+| fact_check | - | - | - |
+| fact_check_filled | - | - | - |
+| fahrenheit_scale | - | - | - |
+| feel_at_ease | - | - | - |
+| feel_at_ease_filled | - | - | - |
+| ferocious | - | - | - |
+| ferocious_filled | - | - | - |
+| ferris_wheel | - | - | - |
+| ferris_wheel_filled | - | - | - |
+| file | - | - | - |
+| file_1 | - | - | - |
+| file_1_filled | - | - | - |
+| file_add | - | - | - |
+| file_add_1 | - | - | - |
+| file_add_1_filled | - | - | - |
+| file_add_filled | - | - | - |
+| file_attachment | - | - | - |
+| file_attachment_filled | - | - | - |
+| file_blocked | - | - | - |
+| file_blocked_filled | - | - | - |
+| file_code | - | - | - |
+| file_code_1 | - | - | - |
+| file_code_1_filled | - | - | - |
+| file_code_filled | - | - | - |
+| file_copy | - | - | - |
+| file_copy_filled | - | - | - |
+| file_download | - | - | - |
+| file_download_filled | - | - | - |
+| file_excel | - | - | - |
+| file_excel_filled | - | - | - |
+| file_export | - | - | - |
+| file_export_filled | - | - | - |
+| file_filled | - | - | - |
+| file_icon | - | - | - |
+| file_icon_filled | - | - | - |
+| file_image | - | - | - |
+| file_image_filled | - | - | - |
+| file_import | - | - | - |
+| file_import_filled | - | - | - |
+| file_locked | - | - | - |
+| file_locked_filled | - | - | - |
+| file_minus | - | - | - |
+| file_minus_filled | - | - | - |
+| file_music | - | - | - |
+| file_music_filled | - | - | - |
+| file_onenote | - | - | - |
+| file_onenote_filled | - | - | - |
+| file_outlook | - | - | - |
+| file_outlook_filled | - | - | - |
+| file_paste | - | - | - |
+| file_paste_filled | - | - | - |
+| file_pdf | - | - | - |
+| file_pdf_filled | - | - | - |
+| file_powerpoint | - | - | - |
+| file_powerpoint_filled | - | - | - |
+| file_restore | - | - | - |
+| file_restore_filled | - | - | - |
+| file_safety | - | - | - |
+| file_safety_filled | - | - | - |
+| file_search | - | - | - |
+| file_search_filled | - | - | - |
+| file_setting | - | - | - |
+| file_setting_filled | - | - | - |
+| file_teams | - | - | - |
+| file_teams_filled | - | - | - |
+| file_transmit | - | - | - |
+| file_transmit_double | - | - | - |
+| file_transmit_double_filled | - | - | - |
+| file_transmit_filled | - | - | - |
+| file_unknown | - | - | - |
+| file_unknown_filled | - | - | - |
+| file_unlocked | - | - | - |
+| file_unlocked_filled | - | - | - |
+| file_word | - | - | - |
+| file_word_filled | - | - | - |
+| file_zip | - | - | - |
+| file_zip_filled | - | - | - |
+| fill_color | - | - | - |
+| fill_color_1 | - | - | - |
+| fill_color_1_filled | - | - | - |
+| fill_color_filled | - | - | - |
+| film | - | - | - |
+| film_1 | - | - | - |
+| film_1_filled | - | - | - |
+| film_filled | - | - | - |
+| filter | - | - | - |
+| filter_1 | - | - | - |
+| filter_1_filled | - | - | - |
+| filter_2 | - | - | - |
+| filter_2_filled | - | - | - |
+| filter_3 | - | - | - |
+| filter_3_filled | - | - | - |
+| filter_clear | - | - | - |
+| filter_clear_filled | - | - | - |
+| filter_filled | - | - | - |
+| filter_off | - | - | - |
+| filter_off_filled | - | - | - |
+| filter_sort | - | - | - |
+| filter_sort_filled | - | - | - |
+| fingerprint | - | - | - |
+| fingerprint_1 | - | - | - |
+| fingerprint_2 | - | - | - |
+| fingerprint_3 | - | - | - |
+| fish | - | - | - |
+| fish_filled | - | - | - |
+| flag | - | - | - |
+| flag_1 | - | - | - |
+| flag_1_filled | - | - | - |
+| flag_2 | - | - | - |
+| flag_2_filled | - | - | - |
+| flag_3 | - | - | - |
+| flag_3_filled | - | - | - |
+| flag_4 | - | - | - |
+| flag_4_filled | - | - | - |
+| flag_filled | - | - | - |
+| flashlight | - | - | - |
+| flashlight_filled | - | - | - |
+| flight_landing | - | - | - |
+| flight_landing_filled | - | - | - |
+| flight_takeoff | - | - | - |
+| flight_takeoff_filled | - | - | - |
+| flip_smiling_face | - | - | - |
+| flip_smiling_face_filled | - | - | - |
+| flip_to_back | - | - | - |
+| flip_to_back_filled | - | - | - |
+| flip_to_front | - | - | - |
+| flip_to_front_filled | - | - | - |
+| focus | - | - | - |
+| focus_filled | - | - | - |
+| fog | - | - | - |
+| fog_filled | - | - | - |
+| fog_night | - | - | - |
+| fog_night_filled | - | - | - |
+| fog_sunny | - | - | - |
+| fog_sunny_filled | - | - | - |
+| folder | - | - | - |
+| folder_1 | - | - | - |
+| folder_1_filled | - | - | - |
+| folder_add | - | - | - |
+| folder_add_1 | - | - | - |
+| folder_add_1_filled | - | - | - |
+| folder_add_filled | - | - | - |
+| folder_blocked | - | - | - |
+| folder_blocked_filled | - | - | - |
+| folder_details | - | - | - |
+| folder_details_filled | - | - | - |
+| folder_export | - | - | - |
+| folder_export_filled | - | - | - |
+| folder_filled | - | - | - |
+| folder_import | - | - | - |
+| folder_import_filled | - | - | - |
+| folder_locked | - | - | - |
+| folder_locked_filled | - | - | - |
+| folder_minus | - | - | - |
+| folder_minus_filled | - | - | - |
+| folder_move | - | - | - |
+| folder_move_filled | - | - | - |
+| folder_off | - | - | - |
+| folder_off_filled | - | - | - |
+| folder_open | - | - | - |
+| folder_open_1 | - | - | - |
+| folder_open_1_filled | - | - | - |
+| folder_open_filled | - | - | - |
+| folder_search | - | - | - |
+| folder_search_filled | - | - | - |
+| folder_setting | - | - | - |
+| folder_setting_filled | - | - | - |
+| folder_shared | - | - | - |
+| folder_shared_filled | - | - | - |
+| folder_unlocked | - | - | - |
+| folder_unlocked_filled | - | - | - |
+| folder_zip | - | - | - |
+| folder_zip_filled | - | - | - |
+| forest | - | - | - |
+| forest_filled | - | - | - |
+| fork | - | - | - |
+| fork_filled | - | - | - |
+| form | - | - | - |
+| form_filled | - | - | - |
+| format_horizontal_align_bottom | - | - | - |
+| format_horizontal_align_center | - | - | - |
+| format_horizontal_align_top | - | - | - |
+| format_vertical_align_center | - | - | - |
+| format_vertical_align_left | - | - | - |
+| format_vertical_align_right | - | - | - |
+| forward | - | - | - |
+| forward_filled | - | - | - |
+| frame | - | - | - |
+| frame_1 | - | - | - |
+| frame_1_filled | - | - | - |
+| frame_filled | - | - | - |
+| fries | - | - | - |
+| fries_filled | - | - | - |
+| fullscreen | - | - | - |
+| fullscreen_1 | - | - | - |
+| fullscreen_2 | - | - | - |
+| fullscreen_exit | - | - | - |
+| fullscreen_exit_1 | - | - | - |
+| function_curve | - | - | - |
+| functions | - | - | - |
+| functions_1 | - | - | - |
+| gamepad | - | - | - |
+| gamepad_1 | - | - | - |
+| gamepad_1_filled | - | - | - |
+| gamepad_filled | - | - | - |
+| gamma | - | - | - |
+| garlic | - | - | - |
+| garlic_filled | - | - | - |
+| gender_female | - | - | - |
+| gender_male | - | - | - |
+| gesture_applause | - | - | - |
+| gesture_applause_filled | - | - | - |
+| gesture_click | - | - | - |
+| gesture_click_filled | - | - | - |
+| gesture_down | - | - | - |
+| gesture_down_filled | - | - | - |
+| gesture_expansion | - | - | - |
+| gesture_expansion_filled | - | - | - |
+| gesture_left | - | - | - |
+| gesture_left_filled | - | - | - |
+| gesture_left_slip | - | - | - |
+| gesture_left_slip_filled | - | - | - |
+| gesture_open | - | - | - |
+| gesture_open_filled | - | - | - |
+| gesture_pray | - | - | - |
+| gesture_pray_filled | - | - | - |
+| gesture_press | - | - | - |
+| gesture_press_filled | - | - | - |
+| gesture_ranslation | - | - | - |
+| gesture_ranslation_filled | - | - | - |
+| gesture_right | - | - | - |
+| gesture_right_filled | - | - | - |
+| gesture_right_slip | - | - | - |
+| gesture_right_slip_filled | - | - | - |
+| gesture_slide_left_and_right | - | - | - |
+| gesture_slide_left_and_right_filled | - | - | - |
+| gesture_slide_up | - | - | - |
+| gesture_slide_up_filled | - | - | - |
+| gesture_typing | - | - | - |
+| gesture_typing_filled | - | - | - |
+| gesture_up | - | - | - |
+| gesture_up_and_down | - | - | - |
+| gesture_up_and_down_filled | - | - | - |
+| gesture_up_filled | - | - | - |
+| gesture_wipe_down | - | - | - |
+| gesture_wipe_down_filled | - | - | - |
+| gift | - | - | - |
+| gift_filled | - | - | - |
+| giggle | - | - | - |
+| giggle_filled | - | - | - |
+| git_branch | - | - | - |
+| git_branch_filled | - | - | - |
+| git_commit | - | - | - |
+| git_commit_filled | - | - | - |
+| git_merge | - | - | - |
+| git_merge_filled | - | - | - |
+| git_pull_request | - | - | - |
+| git_pull_request_filled | - | - | - |
+| git_repository | - | - | - |
+| git_repository_commits | - | - | - |
+| git_repository_commits_filled | - | - | - |
+| git_repository_filled | - | - | - |
+| git_repository_private | - | - | - |
+| git_repository_private_filled | - | - | - |
+| gps | - | - | - |
+| gps_filled | - | - | - |
+| grape | - | - | - |
+| grape_filled | - | - | - |
+| greater_than | - | - | - |
+| greater_than_or_equal | - | - | - |
+| green_onion | - | - | - |
+| grid_add | - | - | - |
+| grid_add_filled | - | - | - |
+| grid_view | - | - | - |
+| grid_view_filled | - | - | - |
+| guitar | - | - | - |
+| guitar_filled | - | - | - |
+| hamburger | - | - | - |
+| hamburger_filled | - | - | - |
+| happy | - | - | - |
+| happy_filled | - | - | - |
+| hard_disk_storage | - | - | - |
+| hard_disk_storage_filled | - | - | - |
+| hard_drive | - | - | - |
+| hard_drive_filled | - | - | - |
+| hashtag | - | - | - |
+| hd | - | - | - |
+| hd_filled | - | - | - |
+| heart | - | - | - |
+| heart_filled | - | - | - |
+| help | - | - | - |
+| help_circle | - | - | - |
+| help_circle_filled | - | - | - |
+| help_rectangle | - | - | - |
+| help_rectangle_filled | - | - | - |
+| highlight | - | - | - |
+| highlight_1 | - | - | - |
+| highlight_1_filled | - | - | - |
+| history | - | - | - |
+| history_setting | - | - | - |
+| home | - | - | - |
+| home_filled | - | - | - |
+| horizontal | - | - | - |
+| horizontal_filled | - | - | - |
+| hospital | - | - | - |
+| hospital_1 | - | - | - |
+| hospital_1_filled | - | - | - |
+| hospital_filled | - | - | - |
+| hotspot_wave | - | - | - |
+| hotspot_wave_filled | - | - | - |
+| hourglass | - | - | - |
+| hourglass_filled | - | - | - |
+| houses | - | - | - |
+| houses_1 | - | - | - |
+| houses_1_filled | - | - | - |
+| houses_2 | - | - | - |
+| houses_2_filled | - | - | - |
+| houses_filled | - | - | - |
+| html5 | - | - | - |
+| html5_filled | - | - | - |
+| https | - | - | - |
+| https_filled | - | - | - |
+| ice_cream | - | - | - |
+| ice_cream_filled | - | - | - |
+| icon | - | - | - |
+| icon_filled | - | - | - |
+| image | - | - | - |
+| image_1 | - | - | - |
+| image_1_filled | - | - | - |
+| image_add | - | - | - |
+| image_add_filled | - | - | - |
+| image_edit | - | - | - |
+| image_edit_filled | - | - | - |
+| image_error | - | - | - |
+| image_error_filled | - | - | - |
+| image_filled | - | - | - |
+| image_off | - | - | - |
+| image_off_filled | - | - | - |
+| image_search | - | - | - |
+| image_search_filled | - | - | - |
+| indent_left | - | - | - |
+| indent_right | - | - | - |
+| indicator | - | - | - |
+| indicator_filled | - | - | - |
+| info_circle | - | - | - |
+| info_circle_filled | - | - | - |
+| ink | - | - | - |
+| ink_filled | - | - | - |
+| install | - | - | - |
+| install_desktop | - | - | - |
+| install_desktop_filled | - | - | - |
+| install_filled | - | - | - |
+| install_mobile | - | - | - |
+| install_mobile_filled | - | - | - |
+| institution | - | - | - |
+| institution_checked | - | - | - |
+| institution_checked_filled | - | - | - |
+| institution_filled | - | - | - |
+| internet | - | - | - |
+| internet_filled | - | - | - |
+| ipod | - | - | - |
+| ipod_filled | - | - | - |
+| joyful | - | - | - |
+| joyful_filled | - | - | - |
+| jump | - | - | - |
+| jump_double | - | - | - |
+| jump_off | - | - | - |
+| key | - | - | - |
+| key_filled | - | - | - |
+| keyboard | - | - | - |
+| keyboard_filled | - | - | - |
+| laptop | - | - | - |
+| laptop_filled | - | - | - |
+| layers | - | - | - |
+| layers_filled | - | - | - |
+| layout | - | - | - |
+| layout_filled | - | - | - |
+| leaderboard | - | - | - |
+| leaderboard_filled | - | - | - |
+| lemon | - | - | - |
+| lemon_filled | - | - | - |
+| lemon_slice | - | - | - |
+| lemon_slice_filled | - | - | - |
+| less_than | - | - | - |
+| less_than_or_equal | - | - | - |
+| letters_a | - | - | - |
+| letters_b | - | - | - |
+| letters_c | - | - | - |
+| letters_d | - | - | - |
+| letters_e | - | - | - |
+| letters_f | - | - | - |
+| letters_g | - | - | - |
+| letters_h | - | - | - |
+| letters_i | - | - | - |
+| letters_j | - | - | - |
+| letters_k | - | - | - |
+| letters_l | - | - | - |
+| letters_m | - | - | - |
+| letters_n | - | - | - |
+| letters_o | - | - | - |
+| letters_p | - | - | - |
+| letters_q | - | - | - |
+| letters_r | - | - | - |
+| letters_s | - | - | - |
+| letters_t | - | - | - |
+| letters_u | - | - | - |
+| letters_v | - | - | - |
+| letters_w | - | - | - |
+| letters_x | - | - | - |
+| letters_y | - | - | - |
+| letters_z | - | - | - |
+| lightbulb | - | - | - |
+| lightbulb_circle | - | - | - |
+| lightbulb_circle_filled | - | - | - |
+| lightbulb_filled | - | - | - |
+| lighthouse | - | - | - |
+| lighthouse_1 | - | - | - |
+| lighthouse_1_filled | - | - | - |
+| lighthouse_2 | - | - | - |
+| lighthouse_2_filled | - | - | - |
+| lighthouse_filled | - | - | - |
+| lighting_circle | - | - | - |
+| lighting_circle_filled | - | - | - |
+| line_height | - | - | - |
+| link | - | - | - |
+| link_1 | - | - | - |
+| link_unlink | - | - | - |
+| liquor | - | - | - |
+| liquor_filled | - | - | - |
+| list | - | - | - |
+| list_numbered | - | - | - |
+| load | - | - | - |
+| loading | - | - | - |
+| location | - | - | - |
+| location_1 | - | - | - |
+| location_1_filled | - | - | - |
+| location_enlargement | - | - | - |
+| location_enlargement_filled | - | - | - |
+| location_error | - | - | - |
+| location_error_filled | - | - | - |
+| location_filled | - | - | - |
+| location_parking_place | - | - | - |
+| location_parking_place_filled | - | - | - |
+| location_reduction | - | - | - |
+| location_reduction_filled | - | - | - |
+| location_setting | - | - | - |
+| location_setting_filled | - | - | - |
+| lock_off | - | - | - |
+| lock_off_filled | - | - | - |
+| lock_on | - | - | - |
+| lock_on_filled | - | - | - |
+| lock_time | - | - | - |
+| lock_time_filled | - | - | - |
+| login | - | - | - |
+| logo_adobe_illustrate | - | - | - |
+| logo_adobe_illustrate_filled | - | - | - |
+| logo_adobe_lightroom | - | - | - |
+| logo_adobe_lightroom_filled | - | - | - |
+| logo_adobe_photoshop | - | - | - |
+| logo_adobe_photoshop_filled | - | - | - |
+| logo_android | - | - | - |
+| logo_android_filled | - | - | - |
+| logo_apple | - | - | - |
+| logo_apple_filled | - | - | - |
+| logo_behance | - | - | - |
+| logo_chrome | - | - | - |
+| logo_chrome_filled | - | - | - |
+| logo_cinema4d | - | - | - |
+| logo_cinema4d_filled | - | - | - |
+| logo_codepen | - | - | - |
+| logo_codesandbox | - | - | - |
+| logo_dribbble | - | - | - |
+| logo_dribbble_filled | - | - | - |
+| logo_facebook | - | - | - |
+| logo_facebook_filled | - | - | - |
+| logo_figma | - | - | - |
+| logo_figma_filled | - | - | - |
+| logo_framer | - | - | - |
+| logo_framer_filled | - | - | - |
+| logo_github | - | - | - |
+| logo_github_filled | - | - | - |
+| logo_gitlab | - | - | - |
+| logo_gitlab_filled | - | - | - |
+| logo_ie | - | - | - |
+| logo_ie_filled | - | - | - |
+| logo_instagram | - | - | - |
+| logo_instagram_filled | - | - | - |
+| logo_qq | - | - | - |
+| logo_qq_filled | - | - | - |
+| logo_twitter | - | - | - |
+| logo_twitter_filled | - | - | - |
+| logo_wechat_stroke | - | - | - |
+| logo_wechat_stroke_filled | - | - | - |
+| logo_wechatpay | - | - | - |
+| logo_wechatpay_filled | - | - | - |
+| logo_wecom | - | - | - |
+| logo_wecom_filled | - | - | - |
+| logo_windows | - | - | - |
+| logo_windows_filled | - | - | - |
+| logo_youtube | - | - | - |
+| logo_youtube_filled | - | - | - |
+| logout | - | - | - |
+| look_around | - | - | - |
+| look_around_filled | - | - | - |
+| loudspeaker | - | - | - |
+| loudspeaker_filled | - | - | - |
+| mail | - | - | - |
+| mail_filled | - | - | - |
+| map | - | - | - |
+| map_3d | - | - | - |
+| map_3d_filled | - | - | - |
+| map_add | - | - | - |
+| map_add_filled | - | - | - |
+| map_aiming | - | - | - |
+| map_aiming_filled | - | - | - |
+| map_blocked | - | - | - |
+| map_blocked_filled | - | - | - |
+| map_bubble | - | - | - |
+| map_bubble_filled | - | - | - |
+| map_cancel | - | - | - |
+| map_cancel_filled | - | - | - |
+| map_chat | - | - | - |
+| map_chat_filled | - | - | - |
+| map_checked | - | - | - |
+| map_checked_filled | - | - | - |
+| map_collection | - | - | - |
+| map_collection_filled | - | - | - |
+| map_connection | - | - | - |
+| map_connection_filled | - | - | - |
+| map_distance | - | - | - |
+| map_distance_filled | - | - | - |
+| map_double | - | - | - |
+| map_double_filled | - | - | - |
+| map_edit | - | - | - |
+| map_edit_filled | - | - | - |
+| map_filled | - | - | - |
+| map_grid | - | - | - |
+| map_grid_filled | - | - | - |
+| map_information | - | - | - |
+| map_information_1 | - | - | - |
+| map_information_1_filled | - | - | - |
+| map_information_2 | - | - | - |
+| map_information_2_filled | - | - | - |
+| map_information_filled | - | - | - |
+| map_location | - | - | - |
+| map_location_filled | - | - | - |
+| map_locked | - | - | - |
+| map_locked_filled | - | - | - |
+| map_marked | - | - | - |
+| map_marked_filled | - | - | - |
+| map_navigation | - | - | - |
+| map_navigation_filled | - | - | - |
+| map_outline | - | - | - |
+| map_outline_filled | - | - | - |
+| map_route_planning | - | - | - |
+| map_route_planning_filled | - | - | - |
+| map_ruler | - | - | - |
+| map_ruler_filled | - | - | - |
+| map_safety | - | - | - |
+| map_safety_filled | - | - | - |
+| map_search | - | - | - |
+| map_search_1 | - | - | - |
+| map_search_1_filled | - | - | - |
+| map_search_filled | - | - | - |
+| map_setting | - | - | - |
+| map_setting_filled | - | - | - |
+| map_unlocked | - | - | - |
+| map_unlocked_filled | - | - | - |
+| mark_as_unread | - | - | - |
+| mark_as_unread_filled | - | - | - |
+| markup | - | - | - |
+| markup_filled | - | - | - |
+| mathematics | - | - | - |
+| mathematics_filled | - | - | - |
+| measurement | - | - | - |
+| measurement_1 | - | - | - |
+| measurement_1_filled | - | - | - |
+| measurement_2 | - | - | - |
+| measurement_2_filled | - | - | - |
+| measurement_filled | - | - | - |
+| meat_pepper | - | - | - |
+| meat_pepper_filled | - | - | - |
+| media_library | - | - | - |
+| media_library_filled | - | - | - |
+| member | - | - | - |
+| member_filled | - | - | - |
+| menu | - | - | - |
+| menu_application | - | - | - |
+| menu_filled | - | - | - |
+| menu_fold | - | - | - |
+| menu_unfold | - | - | - |
+| merge_cells | - | - | - |
+| merge_cells_filled | - | - | - |
+| microphone | - | - | - |
+| microphone_1 | - | - | - |
+| microphone_1_filled | - | - | - |
+| microphone_2 | - | - | - |
+| microphone_2_filled | - | - | - |
+| microphone_filled | - | - | - |
+| milk | - | - | - |
+| milk_filled | - | - | - |
+| minus | - | - | - |
+| minus_circle | - | - | - |
+| minus_circle_filled | - | - | - |
+| minus_rectangle | - | - | - |
+| minus_rectangle_filled | - | - | - |
+| mirror | - | - | - |
+| mirror_filled | - | - | - |
+| mobile | - | - | - |
+| mobile_blocked | - | - | - |
+| mobile_blocked_filled | - | - | - |
+| mobile_filled | - | - | - |
+| mobile_list | - | - | - |
+| mobile_list_filled | - | - | - |
+| mobile_navigation | - | - | - |
+| mobile_navigation_filled | - | - | - |
+| mobile_shortcut | - | - | - |
+| mobile_shortcut_filled | - | - | - |
+| mobile_vibrate | - | - | - |
+| mobile_vibrate_filled | - | - | - |
+| mode_dark | - | - | - |
+| mode_dark_filled | - | - | - |
+| mode_light | - | - | - |
+| mode_light_filled | - | - | - |
+| module | - | - | - |
+| module_filled | - | - | - |
+| money | - | - | - |
+| money_filled | - | - | - |
+| monument | - | - | - |
+| monument_filled | - | - | - |
+| moon | - | - | - |
+| moon_fall | - | - | - |
+| moon_fall_filled | - | - | - |
+| moon_filled | - | - | - |
+| moon_rising | - | - | - |
+| moon_rising_filled | - | - | - |
+| more | - | - | - |
+| mosque | - | - | - |
+| mosque_1 | - | - | - |
+| mosque_1_filled | - | - | - |
+| mosque_filled | - | - | - |
+| mouse | - | - | - |
+| mouse_filled | - | - | - |
+| move | - | - | - |
+| move_1 | - | - | - |
+| movie_clapper | - | - | - |
+| movie_clapper_filled | - | - | - |
+| multiply | - | - | - |
+| museum | - | - | - |
+| museum_1 | - | - | - |
+| museum_1_filled | - | - | - |
+| museum_2 | - | - | - |
+| museum_2_filled | - | - | - |
+| museum_filled | - | - | - |
+| mushroom | - | - | - |
+| mushroom_1 | - | - | - |
+| mushroom_1_filled | - | - | - |
+| mushroom_filled | - | - | - |
+| music | - | - | - |
+| music_1 | - | - | - |
+| music_1_filled | - | - | - |
+| music_2 | - | - | - |
+| music_2_filled | - | - | - |
+| music_filled | - | - | - |
+| music_rectangle_add | - | - | - |
+| music_rectangle_add_filled | - | - | - |
+| navigation_arrow | - | - | - |
+| navigation_arrow_filled | - | - | - |
+| next | - | - | - |
+| next_filled | - | - | - |
+| no_expression | - | - | - |
+| no_expression_filled | - | - | - |
+| noodle | - | - | - |
+| noodle_filled | - | - | - |
+| notification | - | - | - |
+| notification_add | - | - | - |
+| notification_add_filled | - | - | - |
+| notification_circle | - | - | - |
+| notification_circle_filled | - | - | - |
+| notification_error | - | - | - |
+| notification_error_filled | - | - | - |
+| notification_filled | - | - | - |
+| numbers_0 | - | - | - |
+| numbers_0_1 | - | - | - |
+| numbers_1 | - | - | - |
+| numbers_1_1 | - | - | - |
+| numbers_2 | - | - | - |
+| numbers_2_1 | - | - | - |
+| numbers_3 | - | - | - |
+| numbers_3_1 | - | - | - |
+| numbers_4 | - | - | - |
+| numbers_4_1 | - | - | - |
+| numbers_5 | - | - | - |
+| numbers_5_1 | - | - | - |
+| numbers_6 | - | - | - |
+| numbers_6_1 | - | - | - |
+| numbers_7 | - | - | - |
+| numbers_7_1 | - | - | - |
+| numbers_8 | - | - | - |
+| numbers_8_1 | - | - | - |
+| numbers_9 | - | - | - |
+| numbers_9_1 | - | - | - |
+| nut | - | - | - |
+| nut_filled | - | - | - |
+| object_storage | - | - | - |
+| open_mouth | - | - | - |
+| open_mouth_filled | - | - | - |
+| opera | - | - | - |
+| opera_filled | - | - | - |
+| order_adjustment_column | - | - | - |
+| order_ascending | - | - | - |
+| order_descending | - | - | - |
+| outbox | - | - | - |
+| outbox_filled | - | - | - |
+| page_first | - | - | - |
+| page_head | - | - | - |
+| page_head_filled | - | - | - |
+| page_last | - | - | - |
+| palace | - | - | - |
+| palace_1 | - | - | - |
+| palace_1_filled | - | - | - |
+| palace_2 | - | - | - |
+| palace_2_filled | - | - | - |
+| palace_3 | - | - | - |
+| palace_3_filled | - | - | - |
+| palace_4 | - | - | - |
+| palace_4_filled | - | - | - |
+| palace_filled | - | - | - |
+| palette | - | - | - |
+| palette_1 | - | - | - |
+| palette_1_filled | - | - | - |
+| palette_filled | - | - | - |
+| panorama_horizontal | - | - | - |
+| panorama_horizontal_filled | - | - | - |
+| panorama_vertical | - | - | - |
+| panorama_vertical_filled | - | - | - |
+| pantone | - | - | - |
+| pantone_filled | - | - | - |
+| parabola | - | - | - |
+| parentheses | - | - | - |
+| paste | - | - | - |
+| paste_filled | - | - | - |
+| patio | - | - | - |
+| patio_filled | - | - | - |
+| pause | - | - | - |
+| pause_circle | - | - | - |
+| pause_circle_filled | - | - | - |
+| pause_circle_stroke | - | - | - |
+| pause_circle_stroke_filled | - | - | - |
+| pea | - | - | - |
+| pea_filled | - | - | - |
+| peach | - | - | - |
+| peach_filled | - | - | - |
+| pear | - | - | - |
+| pear_filled | - | - | - |
+| pearl_of_the_orient | - | - | - |
+| pearl_of_the_orient_filled | - | - | - |
+| pen | - | - | - |
+| pen_ball | - | - | - |
+| pen_ball_filled | - | - | - |
+| pen_brush | - | - | - |
+| pen_brush_filled | - | - | - |
+| pen_filled | - | - | - |
+| pen_mark | - | - | - |
+| pen_mark_filled | - | - | - |
+| pen_quill | - | - | - |
+| pen_quill_filled | - | - | - |
+| pending | - | - | - |
+| pending_filled | - | - | - |
+| percent | - | - | - |
+| personal_information | - | - | - |
+| personal_information_filled | - | - | - |
+| phone_locked | - | - | - |
+| phone_locked_filled | - | - | - |
+| phone_search | - | - | - |
+| phone_search_filled | - | - | - |
+| pi | - | - | - |
+| piano | - | - | - |
+| piano_filled | - | - | - |
+| pin | - | - | - |
+| pin_filled | - | - | - |
+| play | - | - | - |
+| play_circle | - | - | - |
+| play_circle_filled | - | - | - |
+| play_circle_stroke | - | - | - |
+| play_circle_stroke_add | - | - | - |
+| play_circle_stroke_add_filled | - | - | - |
+| play_circle_stroke_filled | - | - | - |
+| play_demo | - | - | - |
+| play_demo_filled | - | - | - |
+| play_rectangle | - | - | - |
+| play_rectangle_filled | - | - | - |
+| plus | - | - | - |
+| popsicle | - | - | - |
+| popsicle_filled | - | - | - |
+| portrait | - | - | - |
+| portrait_filled | - | - | - |
+| pout | - | - | - |
+| pout_filled | - | - | - |
+| poweroff | - | - | - |
+| precise_monitor | - | - | - |
+| previous | - | - | - |
+| previous_filled | - | - | - |
+| print | - | - | - |
+| print_filled | - | - | - |
+| pumpkin | - | - | - |
+| pumpkin_filled | - | - | - |
+| pyramid | - | - | - |
+| pyramid_filled | - | - | - |
+| pyramid_maya | - | - | - |
+| pyramid_maya_filled | - | - | - |
+| qrcode | - | - | - |
+| quadratic | - | - | - |
+| questionnaire | - | - | - |
+| questionnaire_double | - | - | - |
+| questionnaire_double_filled | - | - | - |
+| questionnaire_filled | - | - | - |
+| queue | - | - | - |
+| queue_filled | - | - | - |
+| radar | - | - | - |
+| radio_1 | - | - | - |
+| radio_1_filled | - | - | - |
+| radio_2 | - | - | - |
+| radio_2_filled | - | - | - |
+| radish | - | - | - |
+| radish_filled | - | - | - |
+| rain_heavy | - | - | - |
+| rain_light | - | - | - |
+| rain_light_filled | - | - | - |
+| rain_medium | - | - | - |
+| rainbow | - | - | - |
+| rectangle | - | - | - |
+| rectangle_filled | - | - | - |
+| refresh | - | - | - |
+| relation | - | - | - |
+| relativity | - | - | - |
+| relativity_filled | - | - | - |
+| remote_wave | - | - | - |
+| remote_wave_filled | - | - | - |
+| remove | - | - | - |
+| replay | - | - | - |
+| replay_filled | - | - | - |
+| rice | - | - | - |
+| rice_ball | - | - | - |
+| rice_ball_filled | - | - | - |
+| rice_filled | - | - | - |
+| roast | - | - | - |
+| roast_filled | - | - | - |
+| rocket | - | - | - |
+| rocket_filled | - | - | - |
+| rollback | - | - | - |
+| rollfront | - | - | - |
+| root_list | - | - | - |
+| root_list_filled | - | - | - |
+| rotate | - | - | - |
+| rotate_locked | - | - | - |
+| rotate_locked_filled | - | - | - |
+| rotation | - | - | - |
+| round | - | - | - |
+| round_filled | - | - | - |
+| router_wave | - | - | - |
+| router_wave_filled | - | - | - |
+| rss | - | - | - |
+| ruler | - | - | - |
+| ruler_filled | - | - | - |
+| sailing_hotel | - | - | - |
+| sailing_hotel_filled | - | - | - |
+| sandwich | - | - | - |
+| sandwich_filled | - | - | - |
+| saturation | - | - | - |
+| saturation_filled | - | - | - |
+| sausage | - | - | - |
+| sausage_filled | - | - | - |
+| save | - | - | - |
+| save_filled | - | - | - |
+| saving_pot | - | - | - |
+| saving_pot_filled | - | - | - |
+| scan | - | - | - |
+| screen_4k | - | - | - |
+| screen_4k_filled | - | - | - |
+| screencast | - | - | - |
+| screencast_filled | - | - | - |
+| screenshot | - | - | - |
+| scroll_bar | - | - | - |
+| scroll_bar_filled | - | - | - |
+| sd_card | - | - | - |
+| sd_card_1 | - | - | - |
+| sd_card_1_filled | - | - | - |
+| sd_card_filled | - | - | - |
+| search | - | - | - |
+| search_error | - | - | - |
+| search_error_filled | - | - | - |
+| search_filled | - | - | - |
+| secured | - | - | - |
+| secured_filled | - | - | - |
+| send | - | - | - |
+| send_cancel | - | - | - |
+| send_cancel_filled | - | - | - |
+| send_filled | - | - | - |
+| sensors | - | - | - |
+| sensors_1 | - | - | - |
+| sensors_2 | - | - | - |
+| sensors_off | - | - | - |
+| sequence | - | - | - |
+| sequence_filled | - | - | - |
+| serenity | - | - | - |
+| serenity_filled | - | - | - |
+| server | - | - | - |
+| server_filled | - | - | - |
+| service | - | - | - |
+| service_filled | - | - | - |
+| setting | - | - | - |
+| setting_1 | - | - | - |
+| setting_1_filled | - | - | - |
+| setting_filled | - | - | - |
+| share | - | - | - |
+| share_1 | - | - | - |
+| share_1_filled | - | - | - |
+| share_filled | - | - | - |
+| sharpness | - | - | - |
+| sharpness_filled | - | - | - |
+| shield_error | - | - | - |
+| shield_error_filled | - | - | - |
+| shimen | - | - | - |
+| shimen_filled | - | - | - |
+| shop | - | - | - |
+| shop_1 | - | - | - |
+| shop_1_filled | - | - | - |
+| shop_2 | - | - | - |
+| shop_2_filled | - | - | - |
+| shop_3 | - | - | - |
+| shop_3_filled | - | - | - |
+| shop_4 | - | - | - |
+| shop_4_filled | - | - | - |
+| shop_5 | - | - | - |
+| shop_5_filled | - | - | - |
+| shop_filled | - | - | - |
+| shrimp | - | - | - |
+| shrimp_filled | - | - | - |
+| shrink_horizontal | - | - | - |
+| shrink_vertical | - | - | - |
+| shutter | - | - | - |
+| shutter_filled | - | - | - |
+| shutup | - | - | - |
+| shutup_filled | - | - | - |
+| sim_card | - | - | - |
+| sim_card_1 | - | - | - |
+| sim_card_1_filled | - | - | - |
+| sim_card_2 | - | - | - |
+| sim_card_2_filled | - | - | - |
+| sim_card_filled | - | - | - |
+| sinister_smile | - | - | - |
+| sinister_smile_filled | - | - | - |
+| sip | - | - | - |
+| sip_filled | - | - | - |
+| sitemap | - | - | - |
+| sitemap_filled | - | - | - |
+| slash | - | - | - |
+| sleep | - | - | - |
+| sleep_filled | - | - | - |
+| slice | - | - | - |
+| slice_filled | - | - | - |
+| slideshow | - | - | - |
+| slideshow_filled | - | - | - |
+| smile | - | - | - |
+| smile_filled | - | - | - |
+| sneer | - | - | - |
+| sneer_filled | - | - | - |
+| snowflake | - | - | - |
+| sonic | - | - | - |
+| sound | - | - | - |
+| sound_down | - | - | - |
+| sound_down_filled | - | - | - |
+| sound_filled | - | - | - |
+| sound_high | - | - | - |
+| sound_high_filled | - | - | - |
+| sound_low | - | - | - |
+| sound_low_filled | - | - | - |
+| sound_mute | - | - | - |
+| sound_mute_1 | - | - | - |
+| sound_mute_1_filled | - | - | - |
+| sound_mute_filled | - | - | - |
+| sound_up | - | - | - |
+| sound_up_filled | - | - | - |
+| space | - | - | - |
+| speechless | - | - | - |
+| speechless_1 | - | - | - |
+| speechless_1_filled | - | - | - |
+| speechless_filled | - | - | - |
+| star | - | - | - |
+| star_filled | - | - | - |
+| statue_of_jesus | - | - | - |
+| statue_of_jesus_filled | - | - | - |
+| sticky_note | - | - | - |
+| sticky_note_filled | - | - | - |
+| stop | - | - | - |
+| stop_circle | - | - | - |
+| stop_circle_filled | - | - | - |
+| stop_circle_stroke | - | - | - |
+| stop_circle_stroke_filled | - | - | - |
+| store | - | - | - |
+| store_filled | - | - | - |
+| street_road | - | - | - |
+| street_road_1 | - | - | - |
+| street_road_1_filled | - | - | - |
+| street_road_filled | - | - | - |
+| subtitle | - | - | - |
+| subtitle_filled | - | - | - |
+| subway_line | - | - | - |
+| subway_line_filled | - | - | - |
+| sum | - | - | - |
+| sun_fall | - | - | - |
+| sun_fall_filled | - | - | - |
+| sun_rising | - | - | - |
+| sun_rising_filled | - | - | - |
+| sunny | - | - | - |
+| sunny_filled | - | - | - |
+| support | - | - | - |
+| support_filled | - | - | - |
+| surprised | - | - | - |
+| surprised_1 | - | - | - |
+| surprised_1_filled | - | - | - |
+| surprised_filled | - | - | - |
+| swap | - | - | - |
+| swap_left | - | - | - |
+| swap_right | - | - | - |
+| swear_1 | - | - | - |
+| swear_1_filled | - | - | - |
+| swear_2 | - | - | - |
+| swear_2_filled | - | - | - |
+| system_2 | - | - | - |
+| system_3 | - | - | - |
+| system_3_filled | - | - | - |
+| system_application | - | - | - |
+| system_application_filled | - | - | - |
+| system_blocked | - | - | - |
+| system_blocked_filled | - | - | - |
+| system_code | - | - | - |
+| system_code_filled | - | - | - |
+| system_components | - | - | - |
+| system_components_filled | - | - | - |
+| system_coordinate | - | - | - |
+| system_coordinate_filled | - | - | - |
+| system_device | - | - | - |
+| system_device_filled | - | - | - |
+| system_interface | - | - | - |
+| system_interface_filled | - | - | - |
+| system_location | - | - | - |
+| system_location_filled | - | - | - |
+| system_locked | - | - | - |
+| system_locked_filled | - | - | - |
+| system_log | - | - | - |
+| system_log_filled | - | - | - |
+| system_marked | - | - | - |
+| system_marked_filled | - | - | - |
+| system_messages | - | - | - |
+| system_messages_filled | - | - | - |
+| system_regulation | - | - | - |
+| system_regulation_filled | - | - | - |
+| system_search | - | - | - |
+| system_search_filled | - | - | - |
+| system_setting | - | - | - |
+| system_setting_filled | - | - | - |
+| system_storage | - | - | - |
+| system_storage_filled | - | - | - |
+| system_sum | - | - | - |
+| system_unlocked | - | - | - |
+| system_unlocked_filled | - | - | - |
+| tab | - | - | - |
+| tab_filled | - | - | - |
+| table | - | - | - |
+| table_1 | - | - | - |
+| table_1_filled | - | - | - |
+| table_2 | - | - | - |
+| table_2_filled | - | - | - |
+| table_add | - | - | - |
+| table_add_filled | - | - | - |
+| table_filled | - | - | - |
+| table_split | - | - | - |
+| table_split_filled | - | - | - |
+| tag | - | - | - |
+| tag_filled | - | - | - |
+| tangerinr | - | - | - |
+| tangerinr_filled | - | - | - |
+| tape | - | - | - |
+| tape_filled | - | - | - |
+| task | - | - | - |
+| task_1 | - | - | - |
+| task_1_filled | - | - | - |
+| task_add | - | - | - |
+| task_add_1 | - | - | - |
+| task_add_filled | - | - | - |
+| task_checked | - | - | - |
+| task_checked_1 | - | - | - |
+| task_checked_filled | - | - | - |
+| task_double | - | - | - |
+| task_double_filled | - | - | - |
+| task_error | - | - | - |
+| task_error_filled | - | - | - |
+| task_filled | - | - | - |
+| task_location | - | - | - |
+| task_location_filled | - | - | - |
+| task_marked | - | - | - |
+| task_marked_filled | - | - | - |
+| task_setting | - | - | - |
+| task_setting_filled | - | - | - |
+| task_time | - | - | - |
+| task_time_filled | - | - | - |
+| task_visible | - | - | - |
+| task_visible_filled | - | - | - |
+| tea | - | - | - |
+| tea_filled | - | - | - |
+| teahouse | - | - | - |
+| teahouse_filled | - | - | - |
+| template | - | - | - |
+| template_filled | - | - | - |
+| temple | - | - | - |
+| temple_filled | - | - | - |
+| terminal | - | - | - |
+| terminal_rectangle | - | - | - |
+| terminal_rectangle_1 | - | - | - |
+| terminal_rectangle_1_filled | - | - | - |
+| terminal_rectangle_filled | - | - | - |
+| terminal_window | - | - | - |
+| terminal_window_filled | - | - | - |
+| textbox | - | - | - |
+| textbox_filled | - | - | - |
+| textformat_bold | - | - | - |
+| textformat_color | - | - | - |
+| textformat_italic | - | - | - |
+| textformat_strikethrough | - | - | - |
+| textformat_underline | - | - | - |
+| textformat_wrap | - | - | - |
+| theaters | - | - | - |
+| theaters_filled | - | - | - |
+| thumb_down | - | - | - |
+| thumb_down_1 | - | - | - |
+| thumb_down_1_filled | - | - | - |
+| thumb_down_2 | - | - | - |
+| thumb_down_2_filled | - | - | - |
+| thumb_down_filled | - | - | - |
+| thumb_up | - | - | - |
+| thumb_up_1 | - | - | - |
+| thumb_up_1_filled | - | - | - |
+| thumb_up_2 | - | - | - |
+| thumb_up_2_filled | - | - | - |
+| thumb_up_filled | - | - | - |
+| thunder | - | - | - |
+| thunderstorm | - | - | - |
+| thunderstorm_night | - | - | - |
+| thunderstorm_night_filled | - | - | - |
+| thunderstorm_sunny | - | - | - |
+| thunderstorm_sunny_filled | - | - | - |
+| ticket | - | - | - |
+| ticket_filled | - | - | - |
+| time | - | - | - |
+| time_filled | - | - | - |
+| tips | - | - | - |
+| tips_double | - | - | - |
+| tips_double_filled | - | - | - |
+| tips_filled | - | - | - |
+| tomato | - | - | - |
+| tomato_filled | - | - | - |
+| tools | - | - | - |
+| tools_circle | - | - | - |
+| tools_circle_filled | - | - | - |
+| tools_filled | - | - | - |
+| tornado | - | - | - |
+| tower | - | - | - |
+| tower_1 | - | - | - |
+| tower_1_filled | - | - | - |
+| tower_2 | - | - | - |
+| tower_2_filled | - | - | - |
+| tower_3 | - | - | - |
+| tower_3_filled | - | - | - |
+| tower_clock | - | - | - |
+| tower_clock_filled | - | - | - |
+| tower_filled | - | - | - |
+| town | - | - | - |
+| town_filled | - | - | - |
+| traffic | - | - | - |
+| traffic_events | - | - | - |
+| traffic_events_filled | - | - | - |
+| traffic_filled | - | - | - |
+| transform | - | - | - |
+| transform_1 | - | - | - |
+| transform_1_filled | - | - | - |
+| transform_2 | - | - | - |
+| transform_3 | - | - | - |
+| transform_filled | - | - | - |
+| translate | - | - | - |
+| translate_1 | - | - | - |
+| tree_round_dot | - | - | - |
+| tree_round_dot_filled | - | - | - |
+| tree_round_dot_vertical | - | - | - |
+| tree_round_dot_vertical_filled | - | - | - |
+| tree_square_dot | - | - | - |
+| tree_square_dot_filled | - | - | - |
+| tree_square_dot_vertical | - | - | - |
+| tree_square_dot_vertical_filled | - | - | - |
+| trending_down | - | - | - |
+| trending_up | - | - | - |
+| tv | - | - | - |
+| tv_1 | - | - | - |
+| tv_1_filled | - | - | - |
+| tv_2 | - | - | - |
+| tv_2_filled | - | - | - |
+| tv_filled | - | - | - |
+| typography | - | - | - |
+| typography_filled | - | - | - |
+| uncomfortable | - | - | - |
+| uncomfortable_1 | - | - | - |
+| uncomfortable_1_filled | - | - | - |
+| uncomfortable_2 | - | - | - |
+| uncomfortable_2_filled | - | - | - |
+| uncomfortable_filled | - | - | - |
+| undertake | - | - | - |
+| undertake_delivery | - | - | - |
+| undertake_delivery_filled | - | - | - |
+| undertake_environment_protection | - | - | - |
+| undertake_environment_protection_filled | - | - | - |
+| undertake_filled | - | - | - |
+| undertake_hold_up | - | - | - |
+| undertake_hold_up_filled | - | - | - |
+| undertake_transaction | - | - | - |
+| undertake_transaction_filled | - | - | - |
+| unfold_less | - | - | - |
+| unfold_more | - | - | - |
+| unhappy | - | - | - |
+| unhappy_1 | - | - | - |
+| unhappy_1_filled | - | - | - |
+| unhappy_filled | - | - | - |
+| uninstall | - | - | - |
+| uninstall_filled | - | - | - |
+| upload | - | - | - |
+| upload_1 | - | - | - |
+| upscale | - | - | - |
+| usb | - | - | - |
+| usb_filled | - | - | - |
+| user | - | - | - |
+| user_1 | - | - | - |
+| user_1_filled | - | - | - |
+| user_add | - | - | - |
+| user_add_filled | - | - | - |
+| user_arrow_down | - | - | - |
+| user_arrow_down_filled | - | - | - |
+| user_arrow_left | - | - | - |
+| user_arrow_left_filled | - | - | - |
+| user_arrow_right | - | - | - |
+| user_arrow_right_filled | - | - | - |
+| user_arrow_up | - | - | - |
+| user_arrow_up_filled | - | - | - |
+| user_avatar | - | - | - |
+| user_avatar_filled | - | - | - |
+| user_blocked | - | - | - |
+| user_blocked_filled | - | - | - |
+| user_business | - | - | - |
+| user_business_filled | - | - | - |
+| user_checked | - | - | - |
+| user_checked_1 | - | - | - |
+| user_checked_1_filled | - | - | - |
+| user_checked_filled | - | - | - |
+| user_circle | - | - | - |
+| user_circle_filled | - | - | - |
+| user_clear | - | - | - |
+| user_clear_filled | - | - | - |
+| user_error_1 | - | - | - |
+| user_error_1_filled | - | - | - |
+| user_filled | - | - | - |
+| user_invisible | - | - | - |
+| user_invisible_filled | - | - | - |
+| user_list | - | - | - |
+| user_list_filled | - | - | - |
+| user_locked | - | - | - |
+| user_locked_filled | - | - | - |
+| user_marked | - | - | - |
+| user_marked_filled | - | - | - |
+| user_password | - | - | - |
+| user_password_filled | - | - | - |
+| user_safety | - | - | - |
+| user_safety_filled | - | - | - |
+| user_search | - | - | - |
+| user_search_filled | - | - | - |
+| user_setting | - | - | - |
+| user_setting_filled | - | - | - |
+| user_talk | - | - | - |
+| user_talk_1 | - | - | - |
+| user_talk_1_filled | - | - | - |
+| user_talk_filled | - | - | - |
+| user_talk_off_1 | - | - | - |
+| user_talk_off_1_filled | - | - | - |
+| user_time | - | - | - |
+| user_time_filled | - | - | - |
+| user_transmit | - | - | - |
+| user_transmit_filled | - | - | - |
+| user_unknown | - | - | - |
+| user_unknown_filled | - | - | - |
+| user_unlocked | - | - | - |
+| user_unlocked_filled | - | - | - |
+| user_vip | - | - | - |
+| user_vip_filled | - | - | - |
+| user_visible | - | - | - |
+| user_visible_filled | - | - | - |
+| usercase | - | - | - |
+| usercase_filled | - | - | - |
+| usercase_link | - | - | - |
+| usercase_link_filled | - | - | - |
+| usergroup | - | - | - |
+| usergroup_add | - | - | - |
+| usergroup_add_filled | - | - | - |
+| usergroup_clear | - | - | - |
+| usergroup_clear_filled | - | - | - |
+| usergroup_filled | - | - | - |
+| vehicle | - | - | - |
+| vehicle_filled | - | - | - |
+| verified | - | - | - |
+| verified_filled | - | - | - |
+| verify | - | - | - |
+| verify_filled | - | - | - |
+| vertical | - | - | - |
+| vertical_filled | - | - | - |
+| video | - | - | - |
+| video_camera | - | - | - |
+| video_camera_1 | - | - | - |
+| video_camera_1_filled | - | - | - |
+| video_camera_2 | - | - | - |
+| video_camera_2_filled | - | - | - |
+| video_camera_3 | - | - | - |
+| video_camera_3_filled | - | - | - |
+| video_camera_dollar | - | - | - |
+| video_camera_dollar_filled | - | - | - |
+| video_camera_filled | - | - | - |
+| video_camera_minus | - | - | - |
+| video_camera_minus_filled | - | - | - |
+| video_camera_music | - | - | - |
+| video_camera_music_filled | - | - | - |
+| video_camera_off | - | - | - |
+| video_camera_off_filled | - | - | - |
+| video_filled | - | - | - |
+| video_library | - | - | - |
+| video_library_filled | - | - | - |
+| view_agenda | - | - | - |
+| view_agenda_filled | - | - | - |
+| view_column | - | - | - |
+| view_in_ar | - | - | - |
+| view_in_ar_filled | - | - | - |
+| view_list | - | - | - |
+| view_module | - | - | - |
+| view_module_filled | - | - | - |
+| visual_recognition | - | - | - |
+| visual_recognition_filled | - | - | - |
+| wallet | - | - | - |
+| wallet_filled | - | - | - |
+| watch | - | - | - |
+| watch_filled | - | - | - |
+| watermelon | - | - | - |
+| watermelon_filled | - | - | - |
+| wave_bye | - | - | - |
+| wave_bye_filled | - | - | - |
+| wave_left | - | - | - |
+| wave_left_filled | - | - | - |
+| wave_right | - | - | - |
+| wave_right_filled | - | - | - |
+| wealth | - | - | - |
+| wealth_1 | - | - | - |
+| wealth_1_filled | - | - | - |
+| wealth_filled | - | - | - |
+| widget | - | - | - |
+| widget_filled | - | - | - |
+| wifi | - | - | - |
+| wifi_1 | - | - | - |
+| wifi_1_filled | - | - | - |
+| wifi_off | - | - | - |
+| wifi_off_1 | - | - | - |
+| wifi_off_1_filled | - | - | - |
+| window | - | - | - |
+| window_1 | - | - | - |
+| window_1_filled | - | - | - |
+| window_filled | - | - | - |
+| windy | - | - | - |
+| windy_rain | - | - | - |
+| wink | - | - | - |
+| wink_filled | - | - | - |
+| work | - | - | - |
+| work_filled | - | - | - |
+| work_history | - | - | - |
+| work_history_filled | - | - | - |
+| work_off | - | - | - |
+| work_off_filled | - | - | - |
+| wry_smile | - | - | - |
+| wry_smile_filled | - | - | - |
+| zoom_in | - | - | - |
+| zoom_in_filled | - | - | - |
+| zoom_out | - | - | - |
+| zoom_out_filled | - | - | - |
+
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TIcons._  | 私有构造方法,不支持外部创建,仅提供静态常量给外部使用 |
+##### TIcons._
+
+私有构造方法,不支持外部创建,仅提供静态常量给外部使用
\ No newline at end of file
diff --git a/tdesign-component/example/assets/api/image-viewer_api.md b/tdesign-component/example/assets/api/image-viewer_api.md
index f36abefb8..89df5e4e7 100644
--- a/tdesign-component/example/assets/api/image-viewer_api.md
+++ b/tdesign-component/example/assets/api/image-viewer_api.md
@@ -5,10 +5,8 @@
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showImageViewer |  |   required BuildContext context,  required List images,  List? labels,  bool? closeBtn,  bool? deleteBtn,  bool? showIndex,  bool? loop,  bool? autoplay,  int? duration,  Color? bgColor,  Color? navBarBgColor,  Color? iconColor,  TextStyle? labelStyle,  TextStyle? indexStyle,  Color? modalBarrierColor,  bool? barrierDismissible,  int? defaultIndex,  double? width,  double? height,  OnIndexChange? onIndexChange,  OnClose? onClose,  OnDelete? onDelete,  bool? ignoreDeleteError,  OnImageTap? onTap,  OnLongPress? onLongPress,  LeftItemBuilder? leftItemBuilder,  RightItemBuilder? rightItemBuilder, | 显示图片预览 |
+| showImageViewer | void | required BuildContext context, required List images, List? labels, bool? closeBtn, bool? deleteBtn, bool? showIndex, bool? loop, bool? autoplay, int? duration, Color? bgColor, Color? navBarBgColor, Color? iconColor, TextStyle? labelStyle, TextStyle? indexStyle, Color? modalBarrierColor, bool? barrierDismissible, int? defaultIndex, double? width, double? height, OnIndexChange? onIndexChange, OnClose? onClose, OnDelete? onDelete, bool? ignoreDeleteError, OnImageTap? onTap, OnLongPress? onLongPress, LeftItemBuilder? leftItemBuilder, RightItemBuilder? rightItemBuilder | 显示图片预览 |
 
-```
-```
 
 ### TImageViewerWidget
 #### 默认构造方法
@@ -26,7 +24,7 @@
 | ignoreDeleteError | bool? | false | 是否忽略单张图片删除错误提示 |
 | images | List | - | 图片数组 |
 | indexStyle | TextStyle? | - | 页码样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labels | List? | - | 图片描述 |
 | labelStyle | TextStyle? | - | label文字样式 |
 | leftItemBuilder | LeftItemBuilder? | - | 左侧自定义操作 |
@@ -40,3 +38,59 @@
 | rightItemBuilder | RightItemBuilder? | - | 右侧自定义操作 |
 | showIndex | bool? | - | 是否显示页码 |
 | width | double? | - | 图片宽度 |
+
+
+### OnIndexChange
+#### 类型定义
+
+```dart
+typedef OnIndexChange =  Function(int index);
+```
+
+
+### OnClose
+#### 类型定义
+
+```dart
+typedef OnClose =  Function(int index);
+```
+
+
+### OnDelete
+#### 类型定义
+
+```dart
+typedef OnDelete =  Function(int index);
+```
+
+
+### OnImageTap
+#### 类型定义
+
+```dart
+typedef OnImageTap =  Function(int index);
+```
+
+
+### OnLongPress
+#### 类型定义
+
+```dart
+typedef OnLongPress =  Function(int index);
+```
+
+
+### LeftItemBuilder
+#### 类型定义
+
+```dart
+typedef LeftItemBuilder = Widget Function(BuildContext context, int index);
+```
+
+
+### RightItemBuilder
+#### 类型定义
+
+```dart
+typedef RightItemBuilder = Widget Function(BuildContext context, int index);
+```
diff --git a/tdesign-component/example/assets/api/image_api.md b/tdesign-component/example/assets/api/image_api.md
index 133ef6adf..f49591872 100644
--- a/tdesign-component/example/assets/api/image_api.md
+++ b/tdesign-component/example/assets/api/image_api.md
@@ -4,30 +4,45 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| alignment |  | Alignment.center |  |
+| alignment | AlignmentGeometry | Alignment.center | - |
 | assetUrl | String? | - | 本地素材地址 |
-| cacheHeight |  | - |  |
-| cacheWidth |  | - |  |
-| centerSlice |  | - |  |
-| color |  | - |  |
-| colorBlendMode |  | - |  |
-| errorBuilder |  | - |  |
+| cacheHeight | int? | - | - |
+| cacheWidth | int? | - | - |
+| centerSlice | Rect? | - | - |
+| color | Color? | - | - |
+| colorBlendMode | BlendMode? | - | - |
+| errorBuilder | ImageErrorWidgetBuilder? | - | - |
 | errorWidget | Widget? | - | 失败自定义提示 |
-| excludeFromSemantics |  | false |  |
-| filterQuality |  | FilterQuality.low |  |
+| excludeFromSemantics | bool | false | - |
+| filterQuality | FilterQuality | FilterQuality.low | - |
 | fit | BoxFit? | - | 适配样式 |
 | frameBuilder | ImageFrameBuilder? | - | 以下系统Image属性,释义请参考系统[Image]中注释 |
-| gaplessPlayback |  | false |  |
+| gaplessPlayback | bool | false | - |
 | height | double? | - | 自定义高 |
 | imageFile | File? | - | 图片文件路径 |
 | imgUrl | String? | - | 图片地址 |
-| isAntiAlias |  | false |  |
-| key |  | - |  |
-| loadingBuilder |  | - |  |
+| isAntiAlias | bool | false | - |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| loadingBuilder | ImageLoadingBuilder? | - | - |
 | loadingWidget | Widget? | - | 加载自定义提示 |
-| matchTextDirection |  | false |  |
-| opacity |  | - |  |
-| repeat |  | ImageRepeat.noRepeat |  |
-| semanticLabel |  | - |  |
+| matchTextDirection | bool | false | - |
+| opacity | Animation? | - | - |
+| repeat | ImageRepeat | ImageRepeat.noRepeat | - |
+| semanticLabel | String? | - | - |
 | type | TImageType | TImageType.roundedSquare | 图片类型 |
 | width | double? | - | 自定义宽 |
+
+
+### TImageType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| clip | 裁剪 |
+| fitHeight | 适应高 |
+| fitWidth | 适应宽 |
+| stretch | 拉伸 |
+| square | 方形, |
+| roundedSquare | 圆角方形 |
+| circle | 圆形 |
diff --git a/tdesign-component/example/assets/api/indexes_api.md b/tdesign-component/example/assets/api/indexes_api.md
index eeb9a7294..771f6eb82 100644
--- a/tdesign-component/example/assets/api/indexes_api.md
+++ b/tdesign-component/example/assets/api/indexes_api.md
@@ -12,7 +12,7 @@
 | capsuleTheme | bool? | false | 锚点是否为胶囊式样式 |
 | indexList | List? | - | 索引字符列表。不传默认 A-Z |
 | indexListMaxHeight | double? | 0.8 | 索引列表最大高度(父容器高度的百分比,默认 0.8) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onChange | void Function(String index)? | - | 索引发生变更时触发事件 |
 | onSelect | void Function(String index)? | - | 点击侧边栏时触发事件 |
 | reverse | bool? | false | 反方向滚动置顶 |
@@ -20,8 +20,6 @@
 | sticky | bool? | true | 锚点是否吸顶 |
 | stickyOffset | double? | 0 | 锚点吸顶时与顶部的距离 |
 
-```
-```
 
 ### TIndexesAnchor
 #### 简介
@@ -33,12 +31,10 @@
 | activeIndex | ValueNotifier | - | 选中索引 |
 | builderAnchor | Widget? Function(BuildContext context, String index, bool isPinnedToTop)? | - | 索引锚点构建 |
 | capsuleTheme | bool | - | 是否为胶囊式样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | sticky | bool | - | 索引是否吸顶 |
 | text | String | - | 锚点文本 |
 
-```
-```
 
 ### TIndexesList
 #### 简介
@@ -51,5 +47,5 @@
 | builderIndex | Widget Function(BuildContext context, String index, bool isActive)? | - | 索引文本自定义构建,包括索引激活左侧提示 |
 | indexList | List | - | 索引字符列表。不传默认 A-Z |
 | indexListMaxHeight | double | 0.8 | 索引列表最大高度(父容器高度的百分比,默认0.8) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onSelect | void Function(String newIndex, String oldIndex) | - | 点击侧边栏时触发事件 |
diff --git a/tdesign-component/example/assets/api/input_api.md b/tdesign-component/example/assets/api/input_api.md
index 7d6f2ccf8..019509b1e 100644
--- a/tdesign-component/example/assets/api/input_api.md
+++ b/tdesign-component/example/assets/api/input_api.md
@@ -27,7 +27,7 @@
 | inputDecoration | InputDecoration? | - | 自定义输入框样式,默认圆角 |
 | inputFormatters | List? | - | 显示输入内容,如限制长度(LengthLimitingTextInputFormatter(6)) |
 | inputType | TextInputType? | - | 键盘类型,数字、字母 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelWidget | Widget? | - | leftLabel右侧组件,支持自定义 |
 | leftContentSpace | double? | - | 输入框内容左侧间距 |
 | leftIcon | Widget? | - | 带图标的输入框 |
@@ -52,9 +52,44 @@
 | selectionControls | TextSelectionControls? | - | 自定义选择控制器 |
 | showBottomDivider | bool | true | 是否展示底部分割线 |
 | size | TInputSize | TInputSize.large | 输入框规格 |
-| spacer | TInputSpacer | - | 组件各模块间间距 |
+| spacer | TInputSpacer? | - | 组件各模块间间距 |
 | textAlign | TextAlign? | - | 文字对齐方向 |
 | textInputBackgroundColor | Color? | - | 文本框背景色 |
 | textStyle | TextStyle? | - | 文本颜色 |
 | type | TInputType | TInputType.normal | 输入框类型 |
 | width | double? | - | 输入框宽度(TCardStyle时必须设置该参数) |
+
+
+### TInputType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| twoLine | - |
+| longText | - |
+| special | - |
+| normalMaxTwoLine | - |
+| cardStyle | - |
+
+
+### TInputSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | - |
+| large | - |
+
+
+### TCardStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| topText | - |
+| topTextWithBlueBorder | - |
+| errorStyle | - |
diff --git a/tdesign-component/example/assets/api/link_api.md b/tdesign-component/example/assets/api/link_api.md
index 7324abe45..2b2ac08fe 100644
--- a/tdesign-component/example/assets/api/link_api.md
+++ b/tdesign-component/example/assets/api/link_api.md
@@ -7,7 +7,7 @@
 | color | Color? | - | link 文本的颜色,如果不设置则根据状态和风格进行计算 |
 | fontSize | double? | - | link 文本的字体大小,如果不设置则根据状态和风格进行计算 |
 | iconSize | double? | - | link icon 大小,如果不设置则根据状态和风格进行计算 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String | - | link 展示的文本 |
 | leftGapWithIcon | double? | - | 前置icon和文本之间的间隔,如果不设置则根据状态和风格进行计算 |
 | linkClick | LinkClick? | - | link 被点击之后所采取的动作,会将uri当做参数传入到该方法当中 |
@@ -19,3 +19,60 @@
 | suffixIcon | Icon? | - | 后置 icon |
 | type | TLinkType | TLinkType.basic | link 类型 |
 | uri | Uri? | - | link 跳转的uri |
+
+
+### TLinkType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| basic | - |
+| withUnderline | - |
+| withPrefixIcon | - |
+| withSuffixIcon | - |
+
+
+### TLinkStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| primary | - |
+| defaultStyle | - |
+| danger | - |
+| warning | - |
+| success | - |
+
+
+### TLinkState
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| active | - |
+| disabled | - |
+
+
+### TLinkSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | - |
+| medium | - |
+| large | - |
+
+
+### LinkClick
+#### 简介
+限制Function类型,防止传递错误的Function,导致参数对不上
+#### 类型定义
+
+```dart
+typedef LinkClick =  Function(Uri? uri);
+```
diff --git a/tdesign-component/example/assets/api/loading_api.md b/tdesign-component/example/assets/api/loading_api.md
index 6bf7be714..f5fd3bf6f 100644
--- a/tdesign-component/example/assets/api/loading_api.md
+++ b/tdesign-component/example/assets/api/loading_api.md
@@ -9,8 +9,34 @@
 | duration | int | 2000 | 一次刷新的时间,控制动画速度 |
 | icon | TLoadingIcon? | TLoadingIcon.circle | 图标,支持圆形、点状、菊花状 |
 | iconColor | Color? | - | 图标颜色 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | refreshWidget | Widget? | - | 失败刷新组件 |
 | size | TLoadingSize | - | 尺寸 |
 | text | String? | - | 文案 |
 | textColor | Color? | - | 文案颜色 |
+
+
+### TLoadingSize
+#### 简介
+Loading 尺寸
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | 小尺寸 |
+| medium | 中尺寸 |
+| large | 大尺寸 |
+
+
+### TLoadingIcon
+#### 简介
+Loading图标
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | 圆形 |
+| point | 点状 |
+| activity | 菊花状 |
diff --git a/tdesign-component/example/assets/api/message_api.md b/tdesign-component/example/assets/api/message_api.md
index a327735d1..9c3abd1ab 100644
--- a/tdesign-component/example/assets/api/message_api.md
+++ b/tdesign-component/example/assets/api/message_api.md
@@ -8,7 +8,7 @@
 | content | String? | - | 通知内容 |
 | duration | int? | 3000 | 消息内置计时器 |
 | icon | dynamic | true | 自定义消息前面的图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | link | dynamic | - | 链接名称 |
 | marquee | MessageMarquee? | - | 跑马灯效果 |
 | offset | List? | - | 相对于 placement 的偏移量 |
@@ -23,10 +23,8 @@
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showMessage |  |   required BuildContext context,  String? content,  bool? visible,  int? duration,  dynamic closeBtn,  dynamic icon,  dynamic link,  MessageMarquee? marquee,  List? offset,  MessageTheme? theme,  VoidCallback? onCloseBtnClick,  VoidCallback? onDurationEnd,  VoidCallback? onLinkClick, |  |
+| showMessage | void | required BuildContext context, String? content, bool? visible, int? duration, dynamic closeBtn, dynamic icon, dynamic link, MessageMarquee? marquee, List? offset, MessageTheme? theme, VoidCallback? onCloseBtnClick, VoidCallback? onDurationEnd, VoidCallback? onLinkClick | - |
 
-```
-```
 
 ### MessageMarquee
 #### 默认构造方法
@@ -37,8 +35,6 @@
 | loop | int? | - | 循环次数 |
 | speed | int? | - | 速度 |
 
-```
-```
 
 ### MessageLink
 #### 默认构造方法
@@ -48,3 +44,17 @@
 | color | Color? | - | 颜色 |
 | name | String | - | 名称 |
 | uri | Uri? | - | 资源链接 |
+
+
+### MessageTheme
+#### 简介
+定义消息主题枚举
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| info | 普通通知 |
+| success | 成功通知 |
+| warning | 警示通知 |
+| error | 错误通知 |
diff --git a/tdesign-component/example/assets/api/navbar_api.md b/tdesign-component/example/assets/api/navbar_api.md
index 49fab9f65..5bc4678a1 100644
--- a/tdesign-component/example/assets/api/navbar_api.md
+++ b/tdesign-component/example/assets/api/navbar_api.md
@@ -12,7 +12,7 @@
 | centerTitle | bool | true | 标题是否居中 |
 | flexibleSpace | Widget? | - | 固定背景 |
 | height | double | 48 | 高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBarItems | List? | - | 左边操作项 |
 | onBack | VoidCallback? | - | 返回事件 |
 | opacity | double | 1.0 | 透明度 |
@@ -29,8 +29,6 @@
 | useBorderStyle | bool | false | 是否使用边框模式 |
 | useDefaultBack | bool | true | 是否使用默认的返回 |
 
-```
-```
 
 ### TNavBarItem
 #### 默认构造方法
@@ -43,4 +41,12 @@
 | iconColor | Color? | - | 图标颜色 |
 | iconSize | double? | 24.0 | 图标尺寸 |
 | iconWidget | Widget? | - | 图标组件,优先级高于 icon |
-| padding | EdgeInsetsGeometry? | - |  |
+| padding | EdgeInsetsGeometry? | - | 内部填充 |
+
+
+### TBarItemAction
+#### 类型定义
+
+```dart
+typedef TBarItemAction = void Function();
+```
diff --git a/tdesign-component/example/assets/api/notice-bar_api.md b/tdesign-component/example/assets/api/notice-bar_api.md
index f43768900..5f72f1e01 100644
--- a/tdesign-component/example/assets/api/notice-bar_api.md
+++ b/tdesign-component/example/assets/api/notice-bar_api.md
@@ -1,7 +1,5 @@
 ## API
 ### TNoticeBar
-#### 简介
-
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -11,7 +9,7 @@
 | direction | Axis? | Axis.horizontal | 滚动方向 |
 | height | double | 22 | 文字高度 (当使用prefixIcon或suffixIcon时,icon大小值等于该属性) |
 | interval | int? | 3000 | 步进滚动间隔时间(毫秒) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | left | Widget? | - | 左侧内容(自定义左侧内容,优先级高于prefixIcon) |
 | marquee | bool? | false | 跑马灯效果 |
 | maxLines | int? | 1 | 文本行数(仅静态有效) |
@@ -23,8 +21,6 @@
 | suffixIcon | IconData? | - | 右侧图标 |
 | theme | TNoticeBarTheme? | TNoticeBarTheme.info | 主题 |
 
-```
-```
 
 ### TNoticeBarStyle
 #### 简介
@@ -43,6 +39,38 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TNoticeBarStyle.generateTheme  | 根据主题生成样式 |
+##### TNoticeBarStyle.generateTheme
+
+根据主题生成样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
+| theme | TNoticeBarTheme? | TNoticeBarTheme.info | - |
+
+
+### TNoticeBarType
+#### 简介
+公告栏类型
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| none | 静止(默认) |
+| scroll | 滚动 |
+| step | 步进 |
+
+
+### TNoticeBarTheme
+#### 简介
+公告栏主题
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| info | 信息(默认) |
+| success | 成功 |
+| warning | 警告 |
+| error | 错误 |
diff --git a/tdesign-component/example/assets/api/picker_api.md b/tdesign-component/example/assets/api/picker_api.md
index d09bcdf8f..57fbf9802 100644
--- a/tdesign-component/example/assets/api/picker_api.md
+++ b/tdesign-component/example/assets/api/picker_api.md
@@ -4,25 +4,23 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| cancel | Widget? | - | 工具栏左侧自定义插槽,默认使用 [TResourceDelegate.cancel] |
-| confirm | Widget? | - | 工具栏右侧自定义插槽,默认使用 [TResourceDelegate.confirm] |
+| cancel | Widget? | - | 工具栏左侧自定义插槽,默认使用 [TResourceDelegate.cancel] 可用于渲染图标、图标+文字组合等。点击事件依然由外层 [GestureDetector] 处理,触发 [onCancel] 回调——所以插槽内的 Widget 不需要自己处理点击。 ```dart // 简单改文字 TPicker( cancel: const Text('关闭'), onCancel: () => Navigator.of(context).pop(), ) // 带图标 TPicker( cancel: const Icon(Icons.close, size: 22), onCancel: () => Navigator.of(context).pop(), ) ``` |
+| confirm | Widget? | - | 工具栏右侧自定义插槽,默认使用 [TResourceDelegate.confirm] 可用于渲染图标、图标+文字组合等。点击事件依然由外层 [GestureDetector] 处理,触发 [onConfirm] 回调——所以插槽内的 Widget 不需要自己处理点击。 ```dart // 简单改文字 TPicker( confirm: const Text('确定'), onConfirm: (v) => Navigator.of(context).pop(v), ) // 带图标 TPicker( confirm: const Icon(Icons.check, size: 22), onConfirm: (v) => Navigator.of(context).pop(v), ) ``` |
 | disabled | bool | false | 是否禁用整个选择器(禁止滚动和操作),默认 false |
 | height | double | 200 | 视窗高度,默认 200 |
 | initialValue | List? | - | 初始选中值列表(按 value 匹配) |
 | itemBuilder | ItemBuilderType? | - | 自定义子项构建器(disabled 项仍由内部统一渲染,不会走此 builder) |
 | itemCount | int | 5 | 每屏显示 item 数,默认 5 |
 | itemDistanceCalculator | ItemDistanceCalculator? | - | 自定义距离计算器(控制颜色/字重/字号随"离中心距离"的变化) |
-| items | TPickerItems | - | 数据源(必填) |
-| key |  | - |  |
-| onCancel | VoidCallback? | - | 点击「取消」按钮回调 |
-| onChange | void Function(TPickerValue)? | - | 值改变回调(滚动时实时触发) |
-| onConfirm | void Function(TPickerValue)? | - | 点击「确认」按钮回调 |
-| onLoad | void Function(TPickerLoadEvent)? | - | 列选中项变化的事件回调 |
-| title | String? | - | 工具栏中部标题(可选,不传时中部留白) |
-| titleWidget | Widget? | - | 工具栏中部自定义标题插槽 |
-
-```
-```
+| items | TPickerItems | - | 数据源(必填) 使用密封类 [TPickerItems] 编译期强制二选一: - [TPickerColumns] → 多列独立选择 - [TPickerLinked] → 联动选择 自由结构数据通过 `.fromRaw()` 工厂构造归一化。 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onCancel | VoidCallback? | - | 点击「取消」按钮回调 仅作为点击事件通知,不携带任何参数。组件本身不会做任何 popup 操作,业务层可在此自行决定是否关闭弹窗、重置状态等。 |
+| onChange | void Function(TPickerValue)? | - | 值改变回调(滚动时实时触发) 触发时机: - 用户滚动经过某个 enabled 项并稳定时 - disabled 修正动画完成后,回调最终落点 **注意**:此回调代表"滚动时实时变化",不代表"用户已确认选择"。 如需"已确认"语义,请使用 [onConfirm]。 如需做网络请求/埋点等去抖处理,请在业务层自行 debounce。 |
+| onConfirm | void Function(TPickerValue)? | - | 点击「确认」按钮回调 携带当前选中的完整 [TPickerValue],包含: - `selectedOptions`: 当前选中的所有 [TPickerOption] - `values`: 各列选中项的 value 列表 - `labels`: 各列选中项的 label 列表 - `indexes`: 各列选中项的索引 与 [onChange] 不同——只有用户点击「确认」时才触发,代表"已确认选择"。 组件本身不会做任何 popup 操作,业务层可在此自行决定是否关闭弹窗、 提交表单等。 |
+| onLoad | void Function(TPickerLoadEvent)? | - | 列选中项变化的事件回调 **触发时机**:每次用户滚动到一个 enabled 项后都会触发(联动模式下还会 在新展开的列就位后触发)。组件本身不做"距底部多少项"的阈值判断——把 决策权交给业务层。 **事件参数**包含: - [TPickerLoadEvent.column]:触发列索引 - [TPickerLoadEvent.remaining]:当前列距底部剩余项数 - [TPickerLoadEvent.displayedCount]:当前列总项数 - [TPickerLoadEvent.parentValue]:联动模式下父级选中值(首列为 null) **典型用法**:业务层根据 [TPickerLoadEvent.remaining] 自行判断是否加载更多。 ```dart onLoad: (e) async { if (e.remaining > 5 \|\| _isLoading) return; // 距底部还远 / 已在加载,跳过 _isLoading = true; final more = await fetchMore(parent: e.parentValue); setState(() { _data.addAll(more); _isLoading = false; }); } ``` |
+| title | String? | - | 工具栏中部标题(可选,不传时中部留白) 顶部工具栏永远显示,包含「取消」「标题」「确认」三块。 用户点击「取消」触发 [onCancel],点击「确认」触发 [onConfirm]。 选择器与弹窗(popup)完全解耦——关闭/打开弹窗的逻辑由业务层在 这两个回调中自行控制。 典型用法(与 popup 弹窗组合): ```dart TPicker( items: items, title: '请选择地区', onCancel: () => setState(() => visible = false), onConfirm: (value) { setState(() { selected = value; visible = false; }); }, ) ``` |
+| titleWidget | Widget? | - | 工具栏中部自定义标题插槽 传入后会**完全替换**默认的 [title] 文字,可用于渲染更复杂的标题(副标题、图标+文字等)。 标题区域不响应点击。 |
+
 
 ### TPickerOption
 #### 默认构造方法
@@ -33,8 +31,6 @@
 | label | String | - | 展示文字(可包含 emoji、单位、国际化等) |
 | value | dynamic | - | 业务值(onChange 回调返回此字段) |
 
-```
-```
 
 ### TPickerValue
 #### 默认构造方法
@@ -44,8 +40,13 @@
 | indexes | List | - | 每列选中项的索引 |
 | selectedOptions | List | - | 每列选中的完整 option |
 
-```
-```
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| labels | List | - | 所有选中项的 label(展示用) 顺序与列顺序对应,可直接用于 UI 展示。 懒计算并缓存,生命周期内只计算一次。 |
+| values | List | - | 所有选中项的 value(提交表单用) 顺序与列顺序对应,可直接用于表单提交。 懒计算并缓存,生命周期内只计算一次。 |
+
 
 ### TPickerLoadEvent
 #### 默认构造方法
@@ -54,63 +55,71 @@
 | --- | --- | --- | --- |
 | column | int | - | 触发事件的列索引(0 表示第一列) |
 | displayedCount | int | - | 当前列已展示的选项总数 |
-| parentValue | dynamic | - | 当前列的父级选中值(联动模式下使用) |
+| parentValue | dynamic | - | 当前列的父级选中值(联动模式下使用) 第一列时为 null;业务层可用此值从原始数据中筛选子级选项。 |
 | remaining | int | - | 距底部剩余的选项数(业务可用此值做"接近底部时加载"判断) |
 
-```
-```
 
 ### TPickerColumns
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| columns | List> | columns | 每列的选项列表 |
+| columns | List> | - | 每列的选项列表 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPickerColumns.fromRaw  | 从自由结构的 raw 数据创建,自动归一化
+##### TPickerColumns.fromRaw
+
+从自由结构的 raw 数据创建,自动归一化
 
  ```dart
  TPickerColumns.fromRaw(
    [['北京', '上海', '广州']],
    keys: const TPickerKeys(label: 'name', value: 'code'),
  )
- ``` |
+ ```
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| rawColumns | List | - | - |
+| keys | TPickerKeys | TPickerKeys.defaults | - |
 
-```
-```
 
 ### TPickerLinked
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| tree | Map | tree | 联动树结构:`Map` |
+| tree | Map | - | 联动树结构:`Map` value 可以是: - `Map` → 下一级联动 - `List` → 叶子级选项 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPickerLinked.fromRaw  | 从自由结构的 raw Map 数据创建,自动归一化
+##### TPickerLinked.fromRaw
+
+从自由结构的 raw Map 数据创建,自动归一化
 
  ```dart
  TPickerLinked.fromRaw({
    '广东': {'深圳': ['南山', '福田'], '广州': ['天河']},
    '浙江': {'杭州': ['西湖']},
  })
- ``` |
+ ```
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| rawTree | Map | - | - |
+| keys | TPickerKeys | TPickerKeys.defaults | - |
 
-```
-```
 
 ### TPickerItems
-```
-```
+#### 简介
+选择器数据源密封类
+
+ 编译期强制二选一,消除运行时类型错误:
+ - [TPickerColumns] → 多列独立选择
+ - [TPickerLinked] → 联动选择
 
 ### TPickerKeys
 #### 默认构造方法
@@ -121,3 +130,9 @@
 | disabled | String | 'disabled' | 禁用标记对应的字段名,默认 `disabled` |
 | label | String | 'label' | 展示文案对应的字段名,默认 `label` |
 | value | String | 'value' | 业务值对应的字段名,默认 `value` |
+
+#### 静态成员
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| defaults | TPickerKeys | - | 默认配置(`label / value / disabled / children`) |
diff --git a/tdesign-component/example/assets/api/popover_api.md b/tdesign-component/example/assets/api/popover_api.md
index 0a44f139d..480124a64 100644
--- a/tdesign-component/example/assets/api/popover_api.md
+++ b/tdesign-component/example/assets/api/popover_api.md
@@ -1,20 +1,14 @@
 ## API
 ### TPopover
-#### 简介
-
 
 #### 静态方法
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showPopover |  |   required BuildContext context,  String? content,  Widget? contentWidget,  double offset,  TPopoverTheme? theme,  bool closeOnClickOutside,  TPopoverPlacement? placement,  bool? showArrow,  double arrowSize,  EdgeInsetsGeometry? padding,  double? width,  double? height,  Color? overlayColor,  OnTap? onTap,  OnLongTap? onLongTap,  BorderRadius? radius, |  |
+| showPopover | Future | required BuildContext context, String? content, Widget? contentWidget, double offset, TPopoverTheme? theme, bool closeOnClickOutside, TPopoverPlacement? placement, bool? showArrow, double arrowSize, EdgeInsetsGeometry? padding, double? width, double? height, Color? overlayColor, OnTap? onTap, OnLongTap? onLongTap, BorderRadius? radius | - |
 
-```
-```
 
 ### TPopoverWidget
-#### 简介
-
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -24,7 +18,7 @@
 | contentWidget | Widget? | - | 自定义内容 |
 | context | BuildContext | - | 上下文 |
 | height | double? | - | 内容高度(包含padding,实际高度:height - paddingTop - paddingBottom) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | offset | double | 4 | 偏移 |
 | onLongTap | OnLongTap? | - | 长按事件 |
 | onTap | OnTap? | - | 点击事件 |
@@ -34,3 +28,53 @@
 | showArrow | bool? | true | 是否显示浮层箭头 |
 | theme | TPopoverTheme? | - | 弹出气泡主题 |
 | width | double? | - | 内容宽度(包含padding,实际高度:height - paddingLeft - paddingRight) |
+
+
+### TPopoverTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| dark | 暗色 |
+| light | 亮色 |
+| info | 品牌色 |
+| success | 成功 |
+| warning | 警告 |
+| error | 错误 |
+
+
+### TPopoverPlacement
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| topLeft | 上左 |
+| top | 上 |
+| topRight | 上右 |
+| rightTop | 右上 |
+| right | 右 |
+| rightBottom | 右下 |
+| bottomRight | 下右 |
+| bottom | 下 |
+| bottomLeft | 下左 |
+| leftBottom | 左下 |
+| left | 左 |
+| leftTop | 左上 |
+
+
+### OnTap
+#### 类型定义
+
+```dart
+typedef OnTap =  Function(String? content);
+```
+
+
+### OnLongTap
+#### 类型定义
+
+```dart
+typedef OnLongTap =  Function(String? content);
+```
diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md
index 51d85dd6f..ef3784508 100644
--- a/tdesign-component/example/assets/api/popup_api.md
+++ b/tdesign-component/example/assets/api/popup_api.md
@@ -1,9 +1,10 @@
 ## API
 ### TPopup
 #### 简介
-弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作栏、center 下方关闭。
+弹出层入口:五向滑入 / 居中弹出,支持蒙层、bottom 操作区、center 面板外下方关闭区。
 
  通过 [show] 命令式打开;返回 [TPopupHandle] 用于关闭与再次打开。
+ 多次调用 [show] 会继续压入新的浮层路由,可用于叠加展示。
 
  **示例**
 
@@ -11,7 +12,7 @@
  final handle = TPopup.show(
    context,
    options: TPopupOptions.bottom(
-     titleBuilder: (_) => const Text('标题'),
+     titleWidget: const Text('标题'),
      child: MyPanel(),
    ),
  );
@@ -23,19 +24,14 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPopup._  |  |
-
+##### TPopup._
 
 #### 静态方法
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| show |  |   required BuildContext context,  required TPopupOptions options,  BuildContext? navigatorContext,  bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。     [context] 用于查找 [Navigator] 并展示浮层。     [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。     返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、   [TPopupHandle.isShowing] 控制与查询。     同一 [Navigator] 上若已有展示中的浮层,重复调用会返回已有 handle(防连点)。     [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。     [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
+| show | TPopupHandle | required BuildContext context, required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator | 打开浮层并压入独立 [PopupRoute]。 [context] 用于查找 [Navigator] 并展示浮层。 [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 [TPopupHandle.isShowing] 控制与查询。 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。 [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
 
-```
-```
 
 ### TPopupOptions
 #### 简介
@@ -52,12 +48,12 @@
 
  ## 字段与 [TPopupPlacement]
 
- | [TPopupPlacement] | 头部 / 关闭 | 尺寸 |
+ | [TPopupPlacement] | 头部 / 关闭区 | 尺寸 |
  |-------------------|-------------|------|
- | [TPopupPlacement.bottom] | [headerBuilder]、[titleBuilder]、[cancelBuilder]、[confirmBuilder] | [height]、[margin] |
+ | [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[inset] |
  | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] |
- | [TPopupPlacement.top] | — | [height]、[margin] |
- | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[margin] |
+ | [TPopupPlacement.top] | — | [height]、[inset] |
+ | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[inset] |
 
  ## Builder 三态([headerBuilder]、[cancelBuilder]、[confirmBuilder]、[closeBuilder])
 
@@ -67,24 +63,24 @@
  | 显式 `null` | 隐藏该区域 |
  | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
 
- [titleBuilder] 默认为 `null`,表示无标题文案。
+ [titleWidget] 默认为 `null`,表示无标题内容。
 
  生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 |
+| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
 | child | Widget | - | 浮层主体内容(必填)。 |
-| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 |
+| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
-| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 |
+| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
-| duration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
-| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 |
+| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
 | height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
-| margin | EdgeInsets | EdgeInsets.zero | 外边距;生效边取决于 [placement]。 |
+| inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
 | onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -97,33 +93,157 @@
 | preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| titleBuilder | WidgetBuilder? | - | bottom 标题;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
+| titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
 | width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPopupOptions.bottom  | 创建 [TPopupPlacement.bottom] 配置。
+##### TPopupOptions.bottom
+
+创建 [TPopupPlacement.bottom] 配置。
 
  固定 [placement] 为 [TPopupPlacement.bottom];默认带内置头部。
- 蒙层、动画、生命周期等字段语义见同名成员文档。 |
-| TPopupOptions.center  | 创建 [TPopupPlacement.center] 配置。
+ 蒙层、动画、生命周期等字段语义见同名成员文档。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
+| inset | TPopupBottomInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
+| titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
+| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
+| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
- 固定 [placement] 为 [TPopupPlacement.center];默认面板外下方圆形关闭按钮。 |
-| TPopupOptions.left  | 创建 [TPopupPlacement.left] 配置。
 
- 固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。 |
-| TPopupOptions.right  | 创建 [TPopupPlacement.right] 配置。
+##### TPopupOptions.center
 
- 固定 [placement] 为 [TPopupPlacement.right];未传 [width] 时布局默认宽度 280。 |
-| TPopupOptions.top  | 创建 [TPopupPlacement.top] 配置。
+创建 [TPopupPlacement.center] 配置。
 
- 固定 [placement] 为 [TPopupPlacement.top];无内置头部。 |
+ 固定 [placement] 为 [TPopupPlacement.center];默认展示面板外下方圆形关闭按钮。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
+| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
+| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
+
+
+##### TPopupOptions.left
+
+创建 [TPopupPlacement.left] 配置。
+
+ 固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
+| inset | TPopupLeftInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
+
+
+##### TPopupOptions.right
+
+创建 [TPopupPlacement.right] 配置。
+
+ 固定 [placement] 为 [TPopupPlacement.right];未传 [width] 时布局默认宽度 280。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
+| inset | TPopupRightInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
+
+
+##### TPopupOptions.top
+
+创建 [TPopupPlacement.top] 配置。
+
+ 固定 [placement] 为 [TPopupPlacement.top];无内置头部。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
+| inset | TPopupTopInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
-```
-```
 
 ### TPopupHandle
 #### 简介
@@ -139,9 +259,64 @@
  handle.close();
  handle.open(); // 可省略 context,复用已缓存的 Navigator
  ```
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
+| options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
+| useRootNavigator | bool | - | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
+
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPopupHandle._  |  |
+##### TPopupHandle._
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
+| navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
+| useRootNavigator | bool | false | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
+
+
+### TPopupPlacement
+#### 简介
+浮层出现方向;决定 [TPopupOptions] 中哪些字段生效。
+
+ 与 [TPopupOptions] 类文档中的「字段与 placement」表对应。
+ 方向固定时请用 [TPopupOptions.bottom]、[TPopupOptions.center] 等命名工厂。
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| top | 自顶部滑入;使用 [TPopupOptions.height]、[TPopupOptions.inset]([TPopupTopInset])。 |
+| left | 自左侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.inset]([TPopupLeftInset])。 |
+| right | 自右侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.inset]([TPopupRightInset])。 |
+| bottom | 自底部滑入;默认内置头部;使用 [TPopupOptions.height]、[TPopupOptions.inset]([TPopupBottomInset])。 |
+| center | 屏幕居中;使用 [TPopupOptions.closeBuilder] 控制面板外下方关闭区。 |
+
+
+### TPopupTrigger
+#### 简介
+浮层关闭或显隐变化时的触发来源。
+
+ 作为 [TPopupVisibleChangeCallback] 的第二个参数,以及关闭流程中的语义标记。
+
+ 内置控件会映射为 [TPopupTrigger.overlay]、[TPopupTrigger.cancel]、
+ [TPopupTrigger.confirm]、[TPopupTrigger.close];
+ [TPopupHandle.close] 为 [TPopupTrigger.api];系统返回为
+ [TPopupTrigger.systemBack];[headerBuilder] 内调用 `close` 等为
+ [TPopupTrigger.custom]。
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| overlay | 点击蒙层,且 [TPopupOptions.closeOnOverlayClick] 为 true。 |
+| cancel | 点击 bottom 取消语义槽位(含默认与自定义 [TPopupOptions.cancelBuilder])。 |
+| confirm | 点击 bottom 确认语义槽位(含默认与自定义 [TPopupOptions.confirmBuilder])。 |
+| close | 点击 center 关闭语义槽位(含默认与自定义 [TPopupOptions.closeBuilder])。 |
+| api | 外部 API 主动触发的显隐变化,如 [TPopupHandle.close] 或打开事件。 |
+| systemBack | 系统返回键或系统路由返回触发的关闭。 |
+| custom | 无框架预设动作语义的自定义关闭,如 [headerBuilder] 内调用 `close`。 |
diff --git a/tdesign-component/example/assets/api/progress_api.md b/tdesign-component/example/assets/api/progress_api.md
index 44b2617be..090884e04 100644
--- a/tdesign-component/example/assets/api/progress_api.md
+++ b/tdesign-component/example/assets/api/progress_api.md
@@ -4,12 +4,12 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| animationDuration | int? | 300 | 动画持续时间(正整数,单位为毫秒) |
+| animationDuration | int | 300 | 动画持续时间(正整数,单位为毫秒) |
 | backgroundColor | Color? | - | 进度条背景颜色 |
 | circleRadius | double? | - | 环形进度条半径(正数) |
 | color | Color? | - | 进度条颜色 |
 | customProgressLabel | Widget? | - | 自定义标签 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | TLabelWidget? | - | 进度条标签 |
 | labelWidgetAlignment | Alignment? | - | 自定义标签对齐方式 |
 | labelWidgetWidth | double? | - | 自定义标签宽度 |
@@ -22,3 +22,38 @@
 | strokeWidth | double? | - | 进度条粗细(正数) |
 | type | TProgressType | - | 进度条类型 |
 | value | double? | - | 进度值(0.0 到 1.0 之间的正数) |
+
+
+### TProgressType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| linear | - |
+| circular | - |
+| micro | - |
+| button | - |
+
+
+### TProgressLabelPosition
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| inside | - |
+| left | - |
+| right | - |
+
+
+### TProgressStatus
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| primary | - |
+| warning | - |
+| danger | - |
+| success | - |
diff --git a/tdesign-component/example/assets/api/pull-down-refresh_api.md b/tdesign-component/example/assets/api/pull-down-refresh_api.md
index 3ade18761..c819e7919 100644
--- a/tdesign-component/example/assets/api/pull-down-refresh_api.md
+++ b/tdesign-component/example/assets/api/pull-down-refresh_api.md
@@ -8,38 +8,38 @@ TDesign刷新头部
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | backgroundColor | Color? | - | 背景颜色 |
-| clamping |  | - |  |
+| clamping | bool? | - | - |
 | completeDuration | Duration? | - | 完成延时 |
 | enableHapticFeedback | bool | true | 开启震动反馈 |
 | enableInfiniteRefresh | bool | false | 是否开启无限刷新 |
 | extent | double? | 48.0 | Header容器高度 |
 | float | bool | false | 是否悬浮 |
-| frictionFactor |  | - |  |
-| hapticFeedback |  | - |  |
-| hitOver |  | - |  |
-| horizontalFrictionFactor |  | - |  |
-| horizontalReadySpringBuilder |  | - |  |
-| horizontalSpring |  | - |  |
-| infiniteHitOver |  | - |  |
+| frictionFactor | - | - | - |
+| hapticFeedback | bool? | - | - |
+| hitOver | - | - | - |
+| horizontalFrictionFactor | - | - | - |
+| horizontalReadySpringBuilder | - | - | - |
+| horizontalSpring | - | - | - |
+| infiniteHitOver | bool? | - | - |
 | infiniteOffset | double? | - | 无限刷新偏移量 |
 | key | Key? | - | Key |
-| listenable |  | - |  |
+| listenable | - | - | - |
 | loadingIcon | TLoadingIcon | TLoadingIcon.circle | loading样式 |
-| maxOverOffset |  | - |  |
-| notifyWhenInvisible |  | - |  |
+| maxOverOffset | - | - | - |
+| notifyWhenInvisible | - | - | - |
 | overScroll | bool | true | 越界滚动([enableInfiniteRefresh]为true或[infiniteOffset]有值时生效) |
-| position |  | - |  |
-| processedDuration |  | - |  |
-| readySpringBuilder |  | - |  |
-| safeArea |  | false |  |
-| secondaryCloseTriggerOffset |  | - |  |
-| secondaryDimension |  | - |  |
-| secondaryTriggerOffset |  | - |  |
-| secondaryVelocity |  | - |  |
-| spring |  | - |  |
-| springRebound |  | - |  |
+| position | - | - | - |
+| processedDuration | Duration? | - | - |
+| readySpringBuilder | - | - | - |
+| safeArea | - | false | - |
+| secondaryCloseTriggerOffset | - | - | - |
+| secondaryDimension | - | - | - |
+| secondaryTriggerOffset | - | - | - |
+| secondaryVelocity | - | - | - |
+| spring | - | - | - |
+| springRebound | - | - | - |
 | triggerDistance | double | 48.0 | 触发刷新任务的偏移量,同[triggerOffset] |
-| triggerOffset |  | - |  |
-| triggerWhenReach |  | - |  |
-| triggerWhenRelease |  | - |  |
-| triggerWhenReleaseNoWait |  | - |  |
+| triggerOffset | double? | - | - |
+| triggerWhenReach | - | - | - |
+| triggerWhenRelease | - | - | - |
+| triggerWhenReleaseNoWait | - | - | - |
diff --git a/tdesign-component/example/assets/api/radio_api.md b/tdesign-component/example/assets/api/radio_api.md
index 74529e483..3ae5d569b 100644
--- a/tdesign-component/example/assets/api/radio_api.md
+++ b/tdesign-component/example/assets/api/radio_api.md
@@ -6,34 +6,32 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| backgroundColor |  | - |  |
-| cardMode |  | - |  |
-| checkBoxLeftSpace |  | - |  |
-| contentDirection |  | TContentDirection.right |  |
-| customContentBuilder |  | - |  |
-| customIconBuilder |  | - |  |
-| customSpace |  | - |  |
-| disableColor |  | - |  |
-| enable |  | true |  |
-| id |  | - |  |
-| insetSpacing |  | - |  |
-| key |  | - |  |
+| backgroundColor | Color? | - | - |
+| cardMode | bool? | - | - |
+| checkBoxLeftSpace | double? | - | - |
+| contentDirection | TContentDirection | TContentDirection.right | - |
+| customContentBuilder | ContentBuilder? | - | - |
+| customIconBuilder | IconBuilder? | - | - |
+| customSpace | EdgeInsetsGeometry? | - | - |
+| disableColor | Color? | - | - |
+| enable | bool | true | - |
+| id | String? | - | - |
+| insetSpacing | double? | - | - |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | radioStyle | TRadioStyle | TRadioStyle.circle | 单选框按钮样式 |
-| selectColor |  | - |  |
-| showDivider | bool | - | 是否显示下划线 |
-| size |  | TCheckBoxSize.small |  |
-| spacing |  | - |  |
-| subTitle |  | - |  |
-| subTitleColor |  | - |  |
-| subTitleFont |  | - |  |
-| subTitleMaxLine |  | 1 |  |
-| title |  | - |  |
-| titleColor |  | - |  |
-| titleFont |  | - |  |
-| titleMaxLine |  | 1 |  |
+| selectColor | Color? | - | - |
+| showDivider | bool? | - | - |
+| size | TCheckBoxSize | TCheckBoxSize.small | - |
+| spacing | double? | - | - |
+| subTitle | String? | - | - |
+| subTitleColor | Color? | - | - |
+| subTitleFont | Font? | - | - |
+| subTitleMaxLine | int | 1 | - |
+| title | String? | - | - |
+| titleColor | Color? | - | - |
+| titleFont | Font? | - | - |
+| titleMaxLine | int | 1 | - |
 
-```
-```
 
 ### TRadioGroup
 #### 简介
@@ -46,22 +44,42 @@ RadioGroup分组对象,继承自TCheckboxGroup,字段含义与父类一致
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| cardMode |  | false |  |
-| child |  | - |  |
-| contentDirection |  | - |  |
-| controller |  | - |  |
-| customContentBuilder |  | - |  |
-| customIconBuilder |  | - |  |
-| direction |  | - |  |
-| directionalTdRadios |  | - |  |
+| cardMode | bool | false | - |
+| child | Widget? | - | - |
+| contentDirection | TContentDirection? | - | - |
+| controller | TCheckboxGroupController? | - | - |
+| customContentBuilder | ContentBuilder? | - | - |
+| customIconBuilder | IconBuilder? | - | - |
+| direction | Axis? | - | - |
+| directionalTdRadios | List? | - | - |
 | divider | Widget? | - | 自定义下划线 |
-| key |  | - |  |
-| onRadioGroupChange |  | - |  |
-| passThrough |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onRadioGroupChange | OnRadioGroupChange? | - | - |
+| passThrough | bool? | - | - |
 | radioCheckStyle | TRadioStyle? | - | 勾选样式 |
 | rowCount | int | 1 | 每行几列 |
-| selectId |  | - |  |
+| selectId | String? | - | - |
 | showDivider | bool | false | 是否显示下划线 |
-| spacing |  | - |  |
+| spacing | double? | - | - |
 | strictMode | bool | true | 严格模式下,用户不能取消勾选,只能切换选择项, |
-| titleMaxLine |  | - |  |
+| titleMaxLine | int? | - | - |
+
+
+### TRadioStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
+| check | - |
+| hollowCircle | - |
+
+
+### OnRadioGroupChange
+#### 类型定义
+
+```dart
+typedef OnRadioGroupChange = void Function(String? selectedId);
+```
diff --git a/tdesign-component/example/assets/api/rate_api.md b/tdesign-component/example/assets/api/rate_api.md
index d3089c22e..6fd415996 100644
--- a/tdesign-component/example/assets/api/rate_api.md
+++ b/tdesign-component/example/assets/api/rate_api.md
@@ -5,7 +5,7 @@
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | allowHalf | bool? | false | 是否允许半选 |
-| builderText | Widget Function(BuildContext context, double value)? | - | 评分等级对应的辅助文字自定义构建,优先级高于[texts] |
+| builderText | Widget Function(BuildContext context, double value)? | - | 评分等级对应的辅助文字自定义构建,优先级高于[texts] 配置后,会忽略[texts],[textWidth],[iconTextGap] |
 | color | List? | - | 评分图标的颜色,示例:[选中颜色] / [选中颜色,未选中颜色],默认:[TTheme.of(context).warningColor5, TTheme.of(context).grayColor4] |
 | count | int? | 5 | 评分的数量 |
 | crossAxisAlignment | CrossAxisAlignment? | CrossAxisAlignment.center | 评分图标与辅助文字的交叉轴对齐方式 |
@@ -14,13 +14,24 @@
 | gap | double? | - | 评分图标的间距,默认:TTheme.of(context).spacer8 |
 | icon | List? | - | 自定义评分图标,[选中和未选中图标] / [选中图标,未选中图标],默认:[TIcons.star_filled] |
 | iconTextGap | double? | - | 评分图标与辅助文字的间距,默认:[TTheme.of(context).spacer16] |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | mainAxisAlignment | MainAxisAlignment? | MainAxisAlignment.start | 评分图标与辅助文字的主轴对齐方式 |
 | mainAxisSize | MainAxisSize? | MainAxisSize.min | 评分图标与辅助文字主轴方向上如何占用空间 |
 | onChange | void Function(double value)? | - | 评分数改变时触发 |
 | placement | PlacementEnum? | PlacementEnum.top | 选择评分弹框的位置,值为[PlacementEnum.none]表示不显示评分弹框。 |
 | showText | bool? | false | 是否显示对应的辅助文字 |
 | size | double? | 24.0 | 评分图标的大小 |
-| texts | List? | const ['极差', '失望', '一般', '满意', '惊喜'] | 评分等级对应的辅助文字, |
+| texts | List? | const ['极差', '失望', '一般', '满意', '惊喜'] | 评分等级对应的辅助文字, 当[allowHalf]为false时长度应与[count]一致, 当[allowHalf]为true时长度应为[count]的两倍, 自定义值示例:['1分', '2分', '3分', '4分', '5分']。 |
 | textWidth | double? | 48.0 | 评分等级对应的辅助文字宽度 |
 | value | double? | 0 | 选择评分的值 |
+
+
+### PlacementEnum
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| none | - |
+| top | - |
+| bottom | - |
diff --git a/tdesign-component/example/assets/api/result_api.md b/tdesign-component/example/assets/api/result_api.md
index 836cd2d1f..6b8595043 100644
--- a/tdesign-component/example/assets/api/result_api.md
+++ b/tdesign-component/example/assets/api/result_api.md
@@ -6,7 +6,19 @@
 | --- | --- | --- | --- |
 | description | String? | - | 描述文本,用于提供额外信息 |
 | icon | Widget? | - | 图标组件,用于在结果中显示一个图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | theme | TResultTheme | TResultTheme.defaultTheme | 主题样式,默认主题样式为defaultTheme |
 | title | String | '' | 标题文本,显示结果的主要信息,默认标题为空字符串 |
 | titleStyle | TextStyle? | - | 自定义字体样式,用于设置标题文本的样式 |
+
+
+### TResultTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | - |
+| success | - |
+| warning | - |
+| error | - |
diff --git a/tdesign-component/example/assets/api/search_api.md b/tdesign-component/example/assets/api/search_api.md
index 08e6d630e..dd658ea95 100644
--- a/tdesign-component/example/assets/api/search_api.md
+++ b/tdesign-component/example/assets/api/search_api.md
@@ -14,7 +14,7 @@
 | enabled | bool? | - | 是否禁用 |
 | focusNode | FocusNode? | - | 自定义焦点 |
 | inputAction | TextInputAction? | - | 键盘动作类型 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | mediumStyle | bool | false | 是否在导航栏中的样式 |
 | needCancel | bool | false | 是否需要取消按钮 |
 | onActionClick | TSearchBarEvent? | - | 自定义操作回调 |
@@ -28,3 +28,51 @@
 | placeHolder | String? | - | 预设文案 |
 | readOnly | bool? | - | 是否只读 |
 | style | TSearchStyle? | TSearchStyle.square | 样式 |
+
+
+### TSearchStyle
+#### 简介
+搜索框的样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| square | 方形 |
+| round | 圆形 |
+
+
+### TSearchAlignment
+#### 简介
+搜索框对齐方式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | 默认头部对齐 |
+| center | 居中 |
+
+
+### TSearchBarEvent
+#### 类型定义
+
+```dart
+typedef TSearchBarEvent = void Function(String value);
+```
+
+
+### TSearchBarClearEvent
+#### 类型定义
+
+```dart
+typedef TSearchBarClearEvent = bool? Function(String value);
+```
+
+
+### TSearchBarCallBack
+#### 类型定义
+
+```dart
+typedef TSearchBarCallBack = void Function();
+```
diff --git a/tdesign-component/example/assets/api/side-bar_api.md b/tdesign-component/example/assets/api/side-bar_api.md
index d36520c3d..76f810131 100644
--- a/tdesign-component/example/assets/api/side-bar_api.md
+++ b/tdesign-component/example/assets/api/side-bar_api.md
@@ -9,7 +9,7 @@
 | controller | TSideBarController? | - | 控制器 |
 | defaultValue | int? | - | 默认值 |
 | height | double? | - | 高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | loading | bool? | - | 加载效果 |
 | loadingWidget | Widget? | - | 自定义加载动画 |
 | onChanged | ValueChanged? | - | 选中值发生变化(Controller控制) |
@@ -22,8 +22,6 @@
 | unSelectedColor | Color? | - | 未选中颜色 |
 | value | int? | - | 选项值 |
 
-```
-```
 
 ### TSideBarItem
 #### 默认构造方法
@@ -33,7 +31,17 @@
 | badge | TBadge? | - | 徽标 |
 | disabled | bool | false | 是否禁用 |
 | icon | IconData? | - | 图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String | '' | 标签 |
 | textStyle | TextStyle? | - | 标签样式 |
 | value | int | -1 | 值 |
+
+
+### TSideBarStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| outline | - |
diff --git a/tdesign-component/example/assets/api/skeleton_api.md b/tdesign-component/example/assets/api/skeleton_api.md
index 7801ab935..5b13db47b 100644
--- a/tdesign-component/example/assets/api/skeleton_api.md
+++ b/tdesign-component/example/assets/api/skeleton_api.md
@@ -6,18 +6,29 @@
 | --- | --- | --- | --- |
 | animation | TSkeletonAnimation? | - | 动画效果 |
 | delay | int | 0 | 延迟显示加载时间 |
-| key |  | - |  |
-| theme |  | TSkeletonTheme.text |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| theme | TSkeletonTheme | TSkeletonTheme.text | - |
+
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| rowCol | TSkeletonRowCol | - | 自定义行列数量、宽度高度、间距等 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TSkeleton.fromRowCol  | 从行列框架创建骨架屏 |
+##### TSkeleton.fromRowCol
+
+从行列框架创建骨架屏
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| animation | TSkeletonAnimation? | - | 动画效果 |
+| delay | int | 0 | 延迟显示加载时间 |
+| rowCol | TSkeletonRowCol | - | 自定义行列数量、宽度高度、间距等 |
 
-```
-```
 
 ### TSkeletonRowColStyle
 #### 默认构造方法
@@ -26,8 +37,6 @@
 | --- | --- | --- | --- |
 | rowSpacing | double Function(BuildContext) | _defaultRowSpacing | 行间距 |
 
-```
-```
 
 ### TSkeletonRowCol
 #### 默认构造方法
@@ -37,8 +46,6 @@
 | objects | List> | - | 行列对象 |
 | style | TSkeletonRowColStyle | const TSkeletonRowColStyle() | 样式 |
 
-```
-```
 
 ### TSkeletonRowColObjStyle
 #### 默认构造方法
@@ -51,15 +58,36 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TSkeletonRowColObjStyle.circle  | 圆形 |
-| TSkeletonRowColObjStyle.rect  | 矩形 |
-| TSkeletonRowColObjStyle.spacer  | 空白占位符 |
-| TSkeletonRowColObjStyle.text  | 文本 |
+##### TSkeletonRowColObjStyle.circle
+
+圆形
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| background | Color Function(BuildContext) | _defaultBackground | 背景颜色 |
+
+
+##### TSkeletonRowColObjStyle.rect
+
+矩形
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| background | Color Function(BuildContext) | _defaultBackground | 背景颜色 |
+
+
+##### TSkeletonRowColObjStyle.spacer
+
+空白占位符
+
+##### TSkeletonRowColObjStyle.text
+
+文本
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| background | Color Function(BuildContext) | _defaultBackground | 背景颜色 |
 
-```
-```
 
 ### TSkeletonRowColObj
 #### 默认构造方法
@@ -75,9 +103,78 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TSkeletonRowColObj.circle  | 圆形 |
-| TSkeletonRowColObj.rect  | 矩形 |
-| TSkeletonRowColObj.spacer  | 空白占位符 |
-| TSkeletonRowColObj.text  | 文本 |
+##### TSkeletonRowColObj.circle
+
+圆形
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| width | double? | 48 | 宽度 |
+| height | double? | 48 | 高度 |
+| flex | int? | - | 弹性因子 |
+| margin | EdgeInsets | EdgeInsets.zero | 间距 |
+| style | TSkeletonRowColObjStyle | const TSkeletonRowColObjStyle.circle() | 样式 |
+
+
+##### TSkeletonRowColObj.rect
+
+矩形
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| width | double? | - | 宽度 |
+| height | double? | 16 | 高度 |
+| flex | int? | 1 | 弹性因子 |
+| margin | EdgeInsets | EdgeInsets.zero | 间距 |
+| style | TSkeletonRowColObjStyle | const TSkeletonRowColObjStyle.rect() | 样式 |
+
+
+##### TSkeletonRowColObj.spacer
+
+空白占位符
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| width | double? | - | 宽度 |
+| height | double? | - | 高度 |
+| flex | int? | - | 弹性因子 |
+| margin | EdgeInsets | EdgeInsets.zero | 间距 |
+
+
+##### TSkeletonRowColObj.text
+
+文本
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| width | double? | - | 宽度 |
+| height | double? | 16 | 高度 |
+| flex | int? | 1 | 弹性因子 |
+| margin | EdgeInsets | EdgeInsets.zero | 间距 |
+| style | TSkeletonRowColObjStyle | const TSkeletonRowColObjStyle.text() | 样式 |
+
+
+### TSkeletonAnimation
+#### 简介
+骨架图动画
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| gradient | 渐变 |
+| flashed | 闪烁 |
+
+
+### TSkeletonTheme
+#### 简介
+骨架图风格
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| avatar | 头像 |
+| image | 图片 |
+| text | 文本 |
+| paragraph | 段落 |
diff --git a/tdesign-component/example/assets/api/slider_api.md b/tdesign-component/example/assets/api/slider_api.md
index 7b00a6314..e7ee4332e 100644
--- a/tdesign-component/example/assets/api/slider_api.md
+++ b/tdesign-component/example/assets/api/slider_api.md
@@ -5,19 +5,17 @@
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | boxDecoration | Decoration? | - | 自定义盒子样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftLabel | String? | - | 左侧标签 |
-| onChanged | ValueChanged? | - | 滑动变化监听 |
-| onChangeEnd | ValueChanged? | - | 滑动结束监听 |
-| onChangeStart | ValueChanged? | - | 滑动开始监听 |
-| onTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击事件 位置、坐标、当前值 |
-| onThumbTextTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击浮标文字 位置、坐标、当前值 |
+| onChanged | ValueChanged? | - | 滑动变化监听 |
+| onChangeEnd | ValueChanged? | - | 滑动结束监听 |
+| onChangeStart | ValueChanged? | - | 滑动开始监听 |
+| onTap | Function(Offset offset, double value)? | - | Thumb 点击事件 坐标、当前值 |
+| onThumbTextTap | Function(Offset offset, double value)? | - | Thumb 点击浮标文字 坐标、当前值 |
 | rightLabel | String? | - | 右侧标签 |
 | sliderThemeData | TSliderThemeData? | - | 样式 |
-| value | RangeValues | - | 默认值 |
+| value | double | - | 默认值 |
 
-```
-```
 
 ### TRangeSlider
 #### 默认构造方法
@@ -25,13 +23,23 @@
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | boxDecoration | Decoration? | - | 自定义盒子样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftLabel | String? | - | 左侧标签 |
 | onChanged | ValueChanged? | - | 滑动变化监听 |
 | onChangeEnd | ValueChanged? | - | 滑动结束监听 |
 | onChangeStart | ValueChanged? | - | 滑动开始监听 |
-| onTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击事件 位置、坐标、当前值 |
-| onThumbTextTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击浮标文字 位置、坐标、当前值 |
+| onTap | Function(Position position, Offset offset, double value)? | - | Thumb 点击事件 位置、坐标、当前值 |
+| onThumbTextTap | Function(Position position, Offset offset, double value)? | - | Thumb 点击浮标文字 位置、坐标、当前值 |
 | rightLabel | String? | - | 右侧标签 |
 | sliderThemeData | TSliderThemeData? | - | 样式 |
 | value | RangeValues | - | 默认值 |
+
+
+### Position
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| start | - |
+| end | - |
diff --git a/tdesign-component/example/assets/api/stepper_api.md b/tdesign-component/example/assets/api/stepper_api.md
index 0ebfa602c..95debede2 100644
--- a/tdesign-component/example/assets/api/stepper_api.md
+++ b/tdesign-component/example/assets/api/stepper_api.md
@@ -10,7 +10,7 @@
 | disableInput | bool | false | 禁用输入框 |
 | eventController | StreamController? | - | 事件控制器 |
 | inputWidth | double? | - | 禁用全部操作 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | max | int | 100 | 最大值 |
 | min | int | 0 | 最小值 |
 | onBlur | VoidCallback? | - | 输入框失去焦点时触发 |
@@ -20,3 +20,70 @@
 | step | int | 1 | 步长 |
 | theme | TStepperTheme | TStepperTheme.normal | 组件风格 |
 | value | int? | 0 | 值 |
+
+
+### TStepperSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | - |
+| medium | - |
+| large | - |
+
+
+### TStepperTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| filled | - |
+| outline | - |
+
+
+### TStepperIconType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| remove | - |
+| add | - |
+
+
+### TStepperOverlimitType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| minus | - |
+| plus | - |
+
+
+### TStepperEventType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| cleanValue | - |
+
+
+### TStepperOverlimitFunction
+#### 类型定义
+
+```dart
+typedef TStepperOverlimitFunction = void Function(TStepperOverlimitType type);
+```
+
+
+### TTapFunction
+#### 类型定义
+
+```dart
+typedef TTapFunction = void Function();
+```
diff --git a/tdesign-component/example/assets/api/steps_api.md b/tdesign-component/example/assets/api/steps_api.md
index 62115981f..6cd49f650 100644
--- a/tdesign-component/example/assets/api/steps_api.md
+++ b/tdesign-component/example/assets/api/steps_api.md
@@ -6,15 +6,13 @@
 | --- | --- | --- | --- |
 | activeIndex | int | 0 | 步骤条当前激活的索引 |
 | direction | TStepsDirection | TStepsDirection.horizontal | 步骤条方向 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | readOnly | bool | false | 步骤条readOnly模式 |
 | simple | bool | false | 步骤条simple模式 |
 | status | TStepsStatus | TStepsStatus.success | 步骤条状态 |
 | steps | List | - | 步骤条数据 |
 | verticalSelect | bool | false | 步骤条垂直自定义步骤条选择模式 |
 
-```
-```
 
 ### TStepsItemData
 #### 默认构造方法
@@ -27,3 +25,27 @@
 | errorIcon | IconData? | - | 失败图标 |
 | successIcon | IconData? | - | 成功图标 |
 | title | String? | - | 标题 |
+
+
+### TStepsDirection
+#### 简介
+Steps步骤条方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| horizontal | - |
+| vertical | - |
+
+
+### TStepsStatus
+#### 简介
+steps步骤条状态
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| success | - |
+| error | - |
diff --git a/tdesign-component/example/assets/api/swipe-cell_api.md b/tdesign-component/example/assets/api/swipe-cell_api.md
index b23f2a9e4..be9049b40 100644
--- a/tdesign-component/example/assets/api/swipe-cell_api.md
+++ b/tdesign-component/example/assets/api/swipe-cell_api.md
@@ -8,16 +8,16 @@
 | --- | --- | --- | --- |
 | cell | Widget | - | 单元格 [TCell] |
 | closeWhenOpened | bool? | true | 当同一组([groupTag])中的一个[TSwipeCell]打开时,是否关闭组中的所有其他[TSwipeCell] |
-| closeWhenTapped | bool? | true | 当同一组([groupTag])中的一个[TSwipeCell]被点击时,是否应该关闭组中的所有[TSwipeCell] |
+| closeWhenTapped | bool? | true | 当同一组([groupTag])中的一个[TSwipeCell]被点击时,是否应该关闭组中的所有[TSwipeCell] [cell]组件被点击时必须传递点击事件,执行`TSwipeCellInherited.of(context)?.cellClick()` |
 | controller | SlidableController? | - | 自定义控制滑动窗口 |
 | direction | Axis? | Axis.horizontal | 可拖动的方向 |
 | disabled | bool? | false | 是否禁用滑动 |
 | dragStartBehavior | DragStartBehavior? | DragStartBehavior.start | 处理拖动开始行为的方式[GestureDetector.dragStartBehavior] |
 | duration | Duration? | const Duration(milliseconds: 200) | 打开关闭动画时长 |
 | groupTag | Object? | - | 组,配置后,[closeWhenOpened]、[closeWhenTapped]才起作用 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | left | TSwipeCellPanel? | - | 左侧滑动操作项面板 |
-| onChange |  Function(TSwipeDirection direction, bool open)? | - | 滑动展开事件 |
+| onChange | Function(TSwipeDirection direction, bool open)? | - | 滑动展开事件 |
 | opened | List? | const [false, false] | 默认打开,[left, right] |
 | right | TSwipeCellPanel? | - | 右侧滑动操作项面板 |
 | slidableKey | Key? | - | 滑动组件的 Key |
@@ -27,5 +27,15 @@
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| close |  |   required Object? tag,  SlidableController? current, | 根据[groupTag]关闭[TSwipeCell]     current:保留当前不关闭 |
-| of |  |   required BuildContext context, | 获取上下文最近的[controller] |
+| close | void | required Object? tag, SlidableController? current | 根据[groupTag]关闭[TSwipeCell] current:保留当前不关闭 |
+| of | SlidableController? | required BuildContext context | 获取上下文最近的[controller] |
+
+
+### TSwipeDirection
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| right | - |
+| left | - |
diff --git a/tdesign-component/example/assets/api/swiper_api.md b/tdesign-component/example/assets/api/swiper_api.md
index 5a23bdff5..a6b8650eb 100644
--- a/tdesign-component/example/assets/api/swiper_api.md
+++ b/tdesign-component/example/assets/api/swiper_api.md
@@ -6,13 +6,20 @@ TDesign风格的Swiper指示器样式,与flutter_swiper的Swiper结合使用
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| alignment | Alignment? | - | 当 scrollDirection== Axis.horizontal 时,默认Alignment.bottomCenter |
+| alignment | Alignment? | - | 当 scrollDirection== Axis.horizontal 时,默认Alignment.bottomCenter 当 scrollDirection== Axis.vertical 时,默认Alignment.centerRight |
 | builder | SwiperPlugin | TSwiperPagination.dots | 具体样式 |
-| key | Key? | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | margin | EdgeInsetsGeometry | const EdgeInsets.all(10.0) | 指示器和container之间的距离 |
 
-```
-```
+#### 静态成员
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| controls | SwiperPlugin | - | 箭头样式 |
+| dots | SwiperPlugin | - | 圆点样式 |
+| dotsBar | SwiperPlugin | - | 圆角矩形 + 圆点样式 默认宽度20,高度6 |
+| fraction | SwiperPlugin | - | 数字样式 |
+
 
 ### TPageTransformer
 #### 简介
@@ -28,7 +35,20 @@ TD默认PageTransformer
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPageTransformer.margin  | 普通margin的卡片式 |
-| TPageTransformer.scaleAndFade  | 缩放或透明的卡片式 |
+##### TPageTransformer.margin
+
+普通margin的卡片式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| margin | double? | 6.0 | 左右间隔 |
+
+
+##### TPageTransformer.scaleAndFade
+
+缩放或透明的卡片式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| fade | double? | 1 | 淡化比例 |
+| scale | double? | 0.8 | 缩放比例 |
diff --git a/tdesign-component/example/assets/api/switch_api.md b/tdesign-component/example/assets/api/switch_api.md
index fb8cb07d3..9491b21f0 100644
--- a/tdesign-component/example/assets/api/switch_api.md
+++ b/tdesign-component/example/assets/api/switch_api.md
@@ -7,7 +7,7 @@
 | closeText | String? | - | 关闭文案 |
 | enable | bool | true | 是否可点击 |
 | isOn | bool | false | 是否打开 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onChanged | OnSwitchChanged? | - | 改变事件 |
 | openText | String? | - | 打开文案 |
 | size | TSwitchSize? | TSwitchSize.medium | 尺寸:大、中、小 |
@@ -18,3 +18,36 @@
 | trackOffColor | Color? | - | 关闭时轨道颜色 |
 | trackOnColor | Color? | - | 开启时轨道颜色 |
 | type | TSwitchType? | TSwitchType.fill | 类型:填充、文本、加载 |
+
+
+### TSwitchSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+
+
+### TSwitchType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| fill | - |
+| text | - |
+| loading | - |
+| icon | - |
+
+
+### OnSwitchChanged
+#### 简介
+开关改变事件处理
+#### 类型定义
+
+```dart
+typedef OnSwitchChanged = bool Function(bool value);
+```
diff --git a/tdesign-component/example/assets/api/tab-bar_api.md b/tdesign-component/example/assets/api/tab-bar_api.md
index dad57643b..84af968b6 100644
--- a/tdesign-component/example/assets/api/tab-bar_api.md
+++ b/tdesign-component/example/assets/api/tab-bar_api.md
@@ -4,11 +4,11 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| basicType | TBottomTabBarBasicType | - | 基本样式(纯文本、纯图标、图标+文本) |
 | animationCurve | Curve | Curves.easeInOutCubic | 动画曲线 |
 | animationDuration | Duration | const Duration(milliseconds: 300) | 动画时长 |
 | backgroundColor | Color? | - | 背景颜色 (可选) |
 | barHeight | double? | _kDefaultTabBarHeight | tab高度 |
-| basicType | TBottomTabBarBasicType | basicType | 基本样式(纯文本、纯图标、图标+文本) |
 | centerDistance | double? | - | icon与文本中间距离(可选) |
 | componentType | TBottomTabBarComponentType? | TBottomTabBarComponentType.label | 选项样式 默认label |
 | currentIndex | int? | - | 选中的index(可选) |
@@ -16,7 +16,7 @@
 | dividerHeight | double? | - | 分割线高度(可选) |
 | dividerThickness | double? | - | 分割线厚度(可选) |
 | indicatorAnimation | TBottomTabBarIndicatorAnimation | TBottomTabBarIndicatorAnimation.none | 指示器动画类型 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | navigationTabs | List | - | tabs配置 |
 | needInkWell | bool | false | 是否需要水波纹效果 |
 | outlineType | TBottomTabBarOutlineType? | TBottomTabBarOutlineType.filled | 标签栏样式 默认filled |
@@ -28,8 +28,6 @@
 | useSafeArea | bool | true | 使用安全区域 |
 | useVerticalDivider | bool? | - | 是否使用竖线分隔(如果选项样式为 label,则强制为 false) |
 
-```
-```
 
 ### BadgeConfig
 #### 默认构造方法
@@ -41,8 +39,6 @@
 | showBadge | bool | - | 是否展示消息 |
 | tBadge | TBadge? | - | 消息样式(未设置但 showBadge 为 true,则默认使用红点) |
 
-```
-```
 
 ### TBottomTabBarTabConfig
 #### 默认构造方法
@@ -60,8 +56,6 @@
 | unselectedIcon | Widget? | - | 未选中时图标 |
 | unselectTabTextStyle | TextStyle? | - | 文本未选择样式 basicType为text时必填 |
 
-```
-```
 
 ### TBottomTabBarPopUpBtnConfig
 #### 默认构造方法
@@ -72,8 +66,6 @@
 | onChanged | ValueChanged | - | 统一在 onChanged 中处理各item点击事件 |
 | popUpDialogConfig | TBottomTabBarPopUpShapeConfig? | - | 弹窗UI配置 |
 
-```
-```
 
 ### TBottomTabBarPopUpShapeConfig
 #### 默认构造方法
@@ -87,8 +79,6 @@
 | popUpWidth | double? | - | 弹窗宽度(不设置,默认为按钮宽度 - 20) |
 | radius | double? | - | panel圆角 默认0 |
 
-```
-```
 
 ### PopUpMenuItem
 #### 默认构造方法
@@ -97,5 +87,48 @@
 | --- | --- | --- | --- |
 | alignment | AlignmentGeometry | AlignmentDirectional.center | 对齐方式 |
 | itemWidget | Widget? | - | 选项widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | value | String | - | 选项值 |
+
+
+### TBottomTabBarBasicType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| text | 单层级纯文本标签栏 |
+| iconText | 文本加图标标签栏 |
+| icon | 纯图标标签栏 |
+| expansionPanel | 双层级纯文本标签栏 |
+
+
+### TBottomTabBarComponentType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | 普通样式 |
+| label | 带胶囊背景的item选中样式 |
+
+
+### TBottomTabBarOutlineType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| filled | 填充样式 |
+| capsule | 胶囊样式 |
+
+
+### TBottomTabBarIndicatorAnimation
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| none | 无动画,瞬间切换 |
+| linear | 线性滑动:指示器匀速从一个 tab 滑到另一个 |
+| elastic | 弹性动画:指示器先拉伸后收缩 |
diff --git a/tdesign-component/example/assets/api/table_api.md b/tdesign-component/example/assets/api/table_api.md
index 30239b18d..b5b15b344 100644
--- a/tdesign-component/example/assets/api/table_api.md
+++ b/tdesign-component/example/assets/api/table_api.md
@@ -12,7 +12,7 @@
 | empty | TTableEmpty? | - | 空表格呈现样式 |
 | footerWidget | Widget? | - | 自定义表尾 |
 | height | double? | - | 表格高度,超出后会出现滚动条 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | loading | bool? | false | 加载中状态 |
 | loadingWidget | Widget? | - | 自定义加载中状态 |
 | onCellTap | OnCellTap? | - | 单元格点击事件 |
@@ -24,8 +24,6 @@
 | stripe | bool? | false | 斑马纹 |
 | width | double? | - | 表格宽度 |
 
-```
-```
 
 ### TTableCol
 #### 默认构造方法
@@ -45,8 +43,6 @@
 | title | String? | - | 表头标题 |
 | width | double? | - | 列宽 |
 
-```
-```
 
 ### TTableEmpty
 #### 默认构造方法
@@ -55,3 +51,73 @@
 | --- | --- | --- | --- |
 | assetUrl | String? | - | 空状态图片 |
 | text | String? | - | 空状态文字 |
+
+
+### TTableColFixed
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+| none | - |
+
+
+### TTableColAlign
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| center | - |
+| right | - |
+
+
+### SelectableFunc
+#### 类型定义
+
+```dart
+typedef SelectableFunc = bool Function(int index, dynamic row);
+```
+
+
+### RowCheckFunc
+#### 类型定义
+
+```dart
+typedef RowCheckFunc = bool Function(int index, dynamic row);
+```
+
+
+### OnCellTap
+#### 类型定义
+
+```dart
+typedef OnCellTap = void Function(int rowIndex, dynamic row, TTableCol col);
+```
+
+
+### OnScroll
+#### 类型定义
+
+```dart
+typedef OnScroll = void Function(ScrollController controller);
+```
+
+
+### OnSelect
+#### 类型定义
+
+```dart
+typedef OnSelect = void Function(List? data);
+```
+
+
+### OnRowSelect
+#### 类型定义
+
+```dart
+typedef OnRowSelect = void Function(int index, bool checked);
+```
diff --git a/tdesign-component/example/assets/api/tabs_api.md b/tdesign-component/example/assets/api/tabs_api.md
index b55e32e45..2bddc3c38 100644
--- a/tdesign-component/example/assets/api/tabs_api.md
+++ b/tdesign-component/example/assets/api/tabs_api.md
@@ -16,24 +16,22 @@
 | indicatorPadding | EdgeInsets? | - | 引导padding |
 | indicatorWidth | double? | - | tabBar下标宽度 |
 | isScrollable | bool | false | 是否滚动 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelColor | Color? | - | tabBar 已选标签颜色 |
 | labelPadding | EdgeInsetsGeometry? | - | tab间距 |
 | labelStyle | TextStyle? | - | 已选label字体 |
-| onTap |  Function(int)? | - | 点击事件 |
+| onTap | Function(int)? | - | 点击事件 |
 | outlineType | TTabBarOutlineType | TTabBarOutlineType.filled | 选项卡样式 |
 | physics | ScrollPhysics? | - | 自定义滑动 |
 | selectedBgColor | Color? | - | 被选中背景色,只有outlineType为capsule时有效 |
 | showIndicator | bool | false | 是否展示引导控件 |
-| tabAlignment |  | - |  |
+| tabAlignment | TabAlignment? | - | - |
 | tabs | List | - | tab数组 |
 | unSelectedBgColor | Color? | - | 未选中背景色,只有outlineType为capsule时有效 |
 | unselectedLabelColor | Color? | - | tabBar未选标签颜色 |
 | unselectedLabelStyle | TextStyle? | - | unselectedLabel字体 |
 | width | double? | - | tabBar宽度 |
 
-```
-```
 
 ### TTab
 #### 默认构造方法
@@ -47,14 +45,12 @@
 | height | double? | - | tab高度 |
 | icon | Widget? | - | 图标 |
 | iconMargin | EdgeInsetsGeometry | const EdgeInsets.only(bottom: 4.0, right: 4.0) | 图标间距 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | outlineType | TTabOutlineType | TTabOutlineType.filled | 选项卡样式 |
 | size | TTabSize | TTabSize.small | 选项卡尺寸 |
 | text | String? | - | 文字内容 |
 | textMargin | EdgeInsetsGeometry? | - | 中间内容宽度 |
 
-```
-```
 
 ### TTabBarView
 #### 默认构造方法
@@ -64,4 +60,36 @@
 | children | List | - | 子widget列表 |
 | controller | TabController? | - | 控制器 |
 | isSlideSwitch | bool | false | 是否可以滑动切换 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+
+
+### TTabSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| small | - |
+
+
+### TTabOutlineType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| filled | 填充样式 |
+| capsule | 胶囊样式 |
+| card | 卡片 |
+
+
+### TTabBarOutlineType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| filled | 填充样式 |
+| capsule | 胶囊样式 |
+| card | 卡片 |
diff --git a/tdesign-component/example/assets/api/tag_api.md b/tdesign-component/example/assets/api/tag_api.md
index 0493284b7..35b04aeab 100644
--- a/tdesign-component/example/assets/api/tag_api.md
+++ b/tdesign-component/example/assets/api/tag_api.md
@@ -4,6 +4,7 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| text | String | - | 标签内容 |
 | backgroundColor | Color? | - | 背景颜色,优先级高于style的backgroundColor |
 | disable | bool | false | 是否为禁用状态 |
 | fixedWidth | double? | - | 标签的固定宽度 |
@@ -14,7 +15,7 @@
 | iconWidget | Widget? | - | 自定义图标内容,需自处理颜色 |
 | isLight | bool | false | 是否为浅色 |
 | isOutline | bool | false | 是否为描边类型,默认不是 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | needCloseIcon | bool | false | 关闭图标 |
 | onCloseTap | GestureTapCallback? | - | 关闭图标点击事件 |
 | overflow | TextOverflow? | - | 文字溢出处理 |
@@ -22,18 +23,16 @@
 | shape | TTagShape | TTagShape.square | 标签形状 |
 | size | TTagSize | TTagSize.medium | 标签大小 |
 | style | TTagStyle? | - | 标签样式 |
-| text | String | text | 标签内容 |
 | textColor | Color? | - | 文字颜色,优先级高于style的textColor |
 | theme | TTagTheme? | - | 主题 |
 
-```
-```
 
 ### TSelectTag
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| text | String | - | 标签内容 |
 | disableSelect | bool | false | 是否禁用选择 |
 | disableSelectStyle | TTagStyle? | - | 不可选标签样式 |
 | fixedWidth | double? | - | 标签的固定宽度 |
@@ -43,7 +42,7 @@
 | isLight | bool | false | 是否为浅色 |
 | isOutline | bool | false | 是否为描边类型,默认不是 |
 | isSelected | bool | false | 是否选中 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | needCloseIcon | bool | false | 关闭图标 |
 | onCloseTap | GestureTapCallback? | - | 关闭图标点击事件 |
 | onSelectChanged | ValueChanged? | - | 标签点击,选中状态改变时的回调 |
@@ -51,12 +50,9 @@
 | selectStyle | TTagStyle? | - | 选中的标签样式 |
 | shape | TTagShape | TTagShape.square | 标签形状 |
 | size | TTagSize | TTagSize.medium | 标签大小 |
-| text | String | text | 标签内容 |
 | theme | TTagTheme? | - | 主题 |
 | unSelectStyle | TTagStyle? | - | 未选中标签样式 |
 
-```
-```
 
 ### TTagStyle
 #### 默认构造方法
@@ -72,11 +68,89 @@
 | fontWeight | FontWeight? | - | 字体粗细 |
 | textColor | Color? | - | 文字颜色 |
 
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| closeIconColor | Color? | - | 关闭图标颜色 |
+
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TTagStyle.generateDisableSelectStyle  | 根据主题生成禁用Tag样式 |
-| TTagStyle.generateFillStyleByTheme  | 根据主题生成填充Tag样式 |
-| TTagStyle.generateOutlineStyleByTheme  | 根据主题生成描边Tag样式 |
+##### TTagStyle.generateDisableSelectStyle
+
+根据主题生成禁用Tag样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文,方便获取主题内容 |
+| isLight | bool | - | - |
+| isOutline | bool | - | - |
+| shape | TTagShape | - | - |
+
+
+##### TTagStyle.generateFillStyleByTheme
+
+根据主题生成填充Tag样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文,方便获取主题内容 |
+| theme | TTagTheme? | - | - |
+| light | bool | - | - |
+| shape | TTagShape | - | - |
+
+
+##### TTagStyle.generateOutlineStyleByTheme
+
+根据主题生成描边Tag样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文,方便获取主题内容 |
+| theme | TTagTheme? | - | - |
+| light | bool | - | - |
+| shape | TTagShape | - | - |
+
+
+### TTagTheme
+#### 简介
+Tag展示类型
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | 默认 |
+| primary | 常规 |
+| warning | 警告 |
+| danger | 危险 |
+| success | 成功 |
+
+
+### TTagSize
+#### 简介
+标签尺寸
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| extraLarge | - |
+| large | - |
+| medium | - |
+| small | - |
+| custom | - |
+
+
+### TTagShape
+#### 简介
+标签形状
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| square | - |
+| round | - |
+| mark | - |
diff --git a/tdesign-component/example/assets/api/text_api.md b/tdesign-component/example/assets/api/text_api.md
index e394cf480..07aaf746c 100644
--- a/tdesign-component/example/assets/api/text_api.md
+++ b/tdesign-component/example/assets/api/text_api.md
@@ -4,8 +4,8 @@
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| data | - | - | 以下系统 text 属性,释义请参考系统 [Text] 中注释 |
 | backgroundColor | Color? | - | 背景颜色 |
-| data | null | data | 以下系统 text 属性,释义请参考系统 [Text] 中注释 |
 | font | Font? | - | 字体尺寸,包含 大小size 和 行高height |
 | fontFamily | FontFamily? | - | 字体ttf |
 | fontFamilyUrl | String? | - | 是否禁用懒加载 FontFamily 的能力 |
@@ -13,64 +13,94 @@
 | forceVerticalCenter | bool | false | 是否强制居中 |
 | isInFontLoader | bool | false | 是否在 FontLoader 中使用 |
 | isTextThrough | bool? | false | 是否是横线穿过样式(删除线) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | lineThroughColor | Color? | - | 删除线颜色,对应 TestStyle 的 decorationColor |
-| locale |  | - |  |
-| maxLines |  | - |  |
-| overflow |  | - |  |
+| locale | Locale? | - | - |
+| maxLines | int? | - | - |
+| overflow | TextOverflow? | - | - |
 | package | String? | - | 字体包名 |
-| semanticsLabel |  | - |  |
-| softWrap |  | - |  |
-| strutStyle |  | - |  |
+| semanticsLabel | String? | - | - |
+| softWrap | bool? | - | - |
+| strutStyle | StrutStyle? | - | - |
 | style | TextStyle? | - | 自定义的 TextStyle,其中指定的属性,将覆盖扩展的外层属性 |
-| textAlign |  | - |  |
+| textAlign | TextAlign? | - | - |
 | textColor | Color? | - | 文本颜色 |
-| textDirection |  | - |  |
-| textHeightBehavior |  | - |  |
-| textScaleFactor |  | - |  |
-| textWidthBasis |  | - |  |
+| textDirection | TextDirection? | - | - |
+| textHeightBehavior | ui.TextHeightBehavior? | - | - |
+| textScaleFactor | double? | - | - |
+| textWidthBasis | TextWidthBasis? | - | - |
+
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| textSpan | InlineSpan? | - | - |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TText.rich  | 富文本构造方法 |
+##### TText.rich
+
+富文本构造方法
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| textSpan | InlineSpan? | - | - |
+| font | Font? | - | 字体尺寸,包含 大小size 和 行高height |
+| fontWeight | FontWeight? | - | 字体粗细 |
+| fontFamily | FontFamily? | - | 字体ttf |
+| textColor | Color? | - | 文本颜色 |
+| backgroundColor | Color? | - | 背景颜色 |
+| isTextThrough | bool? | false | 是否是横线穿过样式(删除线) |
+| lineThroughColor | Color? | - | 删除线颜色,对应 TestStyle 的 decorationColor |
+| package | String? | - | 字体包名 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| style | TextStyle? | - | 自定义的 TextStyle,其中指定的属性,将覆盖扩展的外层属性 |
+| strutStyle | StrutStyle? | - | - |
+| textAlign | TextAlign? | - | - |
+| textDirection | TextDirection? | - | - |
+| locale | Locale? | - | - |
+| softWrap | bool? | - | - |
+| overflow | TextOverflow? | - | - |
+| textScaleFactor | double? | - | - |
+| maxLines | int? | - | - |
+| semanticsLabel | String? | - | - |
+| textWidthBasis | TextWidthBasis? | - | - |
+| textHeightBehavior | ui.TextHeightBehavior? | - | - |
+| forceVerticalCenter | bool | false | 是否强制居中 |
+| isInFontLoader | bool | false | 是否在 FontLoader 中使用 |
+| fontFamilyUrl | String? | - | 是否禁用懒加载 FontFamily 的能力 |
 
-```
-```
 
 ### TTextSpan
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| children |  | - |  |
-| context |  | - |  |
-| font |  | - |  |
-| fontFamily |  | - |  |
-| fontWeight |  | - |  |
-| isTextThrough |  | false |  |
-| lineThroughColor |  | - |  |
-| mouseCursor |  | - |  |
-| onEnter |  | - |  |
-| onExit |  | - |  |
-| package |  | - |  |
-| recognizer |  | - |  |
-| semanticsLabel |  | - |  |
-| style |  | - |  |
-| text |  | - |  |
-| textColor |  | - |  |
+| children | List? | - | - |
+| context | BuildContext? | - | - |
+| font | Font? | - | - |
+| fontFamily | FontFamily? | - | - |
+| fontWeight | FontWeight? | - | - |
+| isTextThrough | bool? | false | - |
+| lineThroughColor | Color? | - | - |
+| mouseCursor | MouseCursor? | - | - |
+| onEnter | PointerEnterEventListener? | - | - |
+| onExit | PointerExitEventListener? | - | - |
+| package | String? | - | - |
+| recognizer | GestureRecognizer? | - | - |
+| semanticsLabel | String? | - | - |
+| style | TextStyle? | - | - |
+| text | String? | - | - |
+| textColor | Color? | - | - |
 
-```
-```
 
 ### TTextConfiguration
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| child |  | - |  |
+| child | Widget | - | - |
 | globalFontFamily | FontFamily? | - | 全局字体,kTextNeedGlobalFontFamily=true 时生效 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | paddingConfig | TTextPaddingConfig? | - | forceVerticalCenter=true 时,内置 padding 配置 |
diff --git a/tdesign-component/example/assets/api/textarea_api.md b/tdesign-component/example/assets/api/textarea_api.md
index f131dad57..0414512df 100644
--- a/tdesign-component/example/assets/api/textarea_api.md
+++ b/tdesign-component/example/assets/api/textarea_api.md
@@ -23,7 +23,7 @@
 | inputDecoration | InputDecoration? | - | 自定义输入框TextField组件样式 |
 | inputFormatters | List? | - | 显示输入内容,如限制长度(LengthLimitingTextInputFormatter(6)) |
 | inputType | TextInputType? | - | 键盘类型,数字、字母 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String? | - | 输入框标题 |
 | labelIcon | Widget? | - | 输入框标题图标 |
 | labelStyle | TextStyle? | - | 左侧标签文本样式 |
@@ -48,3 +48,13 @@
 | textInputBackgroundColor | Color? | - | 文本框背景色 |
 | textStyle | TextStyle? | - | 文本颜色 |
 | width | double? | - | 输入框宽度 |
+
+
+### TTextareaLayout
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| vertical | - |
+| horizontal | - |
diff --git a/tdesign-component/example/assets/api/theme_api.md b/tdesign-component/example/assets/api/theme_api.md
index 3dd48fc58..f4e32529d 100644
--- a/tdesign-component/example/assets/api/theme_api.md
+++ b/tdesign-component/example/assets/api/theme_api.md
@@ -6,7 +6,7 @@
 | --- | --- | --- | --- |
 | child | Widget | - | 子控件 |
 | data | TThemeData | - | 主题数据 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | systemData | ThemeData? | - | Flutter系统主题数据 |
 
 
@@ -14,14 +14,12 @@
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| defaultData |  |  | 获取默认主题数据,全局唯一 |
-| needMultiTheme |  |   bool value, | 开启多套主题功能 |
-| of |  |   BuildContext? context, | 获取主题数据,如果未传context则获取全局唯一的默认数据,   传了context,则获取最近的主题,取不到则会获取全局唯一默认数据 |
-| ofNullable |  |   BuildContext? context, | 获取主题数据,取不到则可空   传了context,则获取最近的主题,取不到或未传context则返回null, |
-| setResourceBuilder |  |   required TResourceBuilder delegate,  bool needAlwaysBuild, | 设置资源代理,   needAlwaysBuild=true:每次都会走build方法;如果全局有多个Delegate,需要区分情况去获取,则可以设置needAlwaysBuild为true,业务自己判断返回哪个delegate   needAlwaysBuild=false:返回delegate为null,则每次都会走build方法,返回了 |
+| defaultData | TThemeData | - | 获取默认主题数据,全局唯一 |
+| needMultiTheme | void | bool value | 开启多套主题功能 |
+| of | TThemeData | BuildContext? context | 获取主题数据,如果未传context则获取全局唯一的默认数据, 传了context,则获取最近的主题,取不到则会获取全局唯一默认数据 |
+| ofNullable | TThemeData? | BuildContext? context | 获取主题数据,取不到则可空 传了context,则获取最近的主题,取不到或未传context则返回null, |
+| setResourceBuilder | void | required TResourceBuilder delegate, bool needAlwaysBuild | 设置资源代理, needAlwaysBuild=true:每次都会走build方法;如果全局有多个Delegate,需要区分情况去获取,则可以设置needAlwaysBuild为true,业务自己判断返回哪个delegate needAlwaysBuild=false:返回delegate为null,则每次都会走build方法,返回了 |
 
-```
-```
 
 ### TThemeData
 #### 默认构造方法
@@ -38,11 +36,26 @@
 | shadowMap | TMap> | - | 阴影 |
 | spacerMap | TMap | - | 间隔 |
 
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| dark | TThemeData? | - | 暗色主题 |
+| light | TThemeData | - | 亮色主题 |
+
 
 #### 静态方法
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| defaultData |  |   TExtraThemeData? extraThemeData, | 获取默认Data,一个App里只有一个,用于没有context的地方 |
-| fromJson |  |   required String name,  required String themeJson,  String? darkName,  null recoverDefault,  TExtraThemeData? extraThemeData, | 解析配置的json文件为主题数据     [name] 主题名称,目前只支持一级键     [themeJson] 主题json字符串,要求json配置必须正确     [recoverDefault] 是否恢复为默认主题数据     [extraThemeData] 额外扩展的主题数据 |
-| parseThemeData |  |   required String name,  required null themeConfig,  required TExtraThemeData? extraThemeData, |  |
+| defaultData | TThemeData | TExtraThemeData? extraThemeData | 获取默认Data,一个App里只有一个,用于没有context的地方 |
+| fromJson | TThemeData? | required String name, required String themeJson, String? darkName, recoverDefault, TExtraThemeData? extraThemeData | 解析配置的json文件为主题数据 [name] 主题名称,目前只支持一级键 [themeJson] 主题json字符串,要求json配置必须正确 [recoverDefault] 是否恢复为默认主题数据 [extraThemeData] 额外扩展的主题数据 |
+| parseThemeData | TThemeData | required String name, required themeConfig, required TExtraThemeData? extraThemeData | - |
+
+
+### DefaultMapFactory
+#### 类型定义
+
+```dart
+typedef DefaultMapFactory = TMap? Function();
+```
diff --git a/tdesign-component/example/assets/api/time-counter_api.md b/tdesign-component/example/assets/api/time-counter_api.md
index 574d84ddb..5c81242ea 100644
--- a/tdesign-component/example/assets/api/time-counter_api.md
+++ b/tdesign-component/example/assets/api/time-counter_api.md
@@ -11,9 +11,9 @@
 | controller | TTimeCounterController? | - | 控制器,可控制开始/暂停/继续/重置 |
 | direction | TTimeCounterDirection | TTimeCounterDirection.down | 计时方向,默认倒计时 |
 | format | String | 'HH:mm:ss' | 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒(分隔符必须为长度为1的非空格的字符) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | millisecond | bool | false | 是否开启毫秒级渲染 |
-| onChange |  Function(int time)? | - | 时间变化时触发回调 |
+| onChange | Function(int time)? | - | 时间变化时触发回调 |
 | onFinish | VoidCallback? | - | 计时结束时触发回调 |
 | size | TTimeCounterSize | TTimeCounterSize.medium | 尺寸 |
 | splitWithUnit | bool | false | 使用时间单位分割 |
@@ -21,14 +21,10 @@
 | theme | TTimeCounterTheme | TTimeCounterTheme.defaultTheme | 风格 |
 | time | int | - | 必需;计时时长,单位毫秒 |
 
-```
-```
 
 ### TTimeCounterController
 #### 简介
 倒计时组件控制器,可控制开始(`start()`)/暂停(`pause()`)/继续(`resume()`)/重置(`reset([int? time])`)
-```
-```
 
 ### TTimeCounterStyle
 #### 简介
@@ -56,6 +52,66 @@
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TTimeCounterStyle.generateStyle  | 生成默认样式 |
+##### TTimeCounterStyle.generateStyle
+
+生成默认样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| size | TTimeCounterSize? | - | - |
+| theme | TTimeCounterTheme? | - | - |
+| splitWithUnit | bool? | - | - |
+
+
+### TTimeCounterStatus
+#### 简介
+计时组件控制器转态
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| start | 开始 |
+| pause | 暂停 |
+| resume | 继续 |
+| reset | 重置 |
+| idle | 空,默认值 |
+
+
+### TTimeCounterDirection
+#### 简介
+计时组件计时方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| down | 倒计时 |
+| up | 正向计时 |
+
+
+### TTimeCounterSize
+#### 简介
+计时组件尺寸
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | 小 |
+| medium | 中等 |
+| large | 大 |
+
+
+### TTimeCounterTheme
+#### 简介
+计时组件风格
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | 默认 |
+| round | 圆形 |
+| square | 方形 |
diff --git a/tdesign-component/example/assets/api/toast_api.md b/tdesign-component/example/assets/api/toast_api.md
index fd2a9575d..3dd94b63b 100644
--- a/tdesign-component/example/assets/api/toast_api.md
+++ b/tdesign-component/example/assets/api/toast_api.md
@@ -5,13 +5,23 @@
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| dismissAll |  |  | 关闭所有Toast |
-| dismissLoading |  |  | 关闭加载Toast(向后兼容) |
-| dismissToast |  |   required String toastId, | 关闭指定的Toast |
-| showFail |  |   required String? text,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 失败提示Toast |
-| showIconText |  |   required String? text,  IconData? icon,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 带图标的Toast |
-| showLoading |  |   required BuildContext context,  String? text,  Duration duration,  bool? preventTap,  Widget? customWidget,  Color? backgroundColor,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 带文案的加载Toast |
-| showLoadingWithoutText |  |   required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  double? iconSize,  Color? iconColor,  String? toastId, | 不带文案的加载Toast |
-| showSuccess |  |   required String? text,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 成功提示Toast |
-| showText |  |   required String? text,  required BuildContext context,  Duration duration,  int? maxLines,  BoxConstraints? constraints,  bool? preventTap,  Widget? customWidget,  Color? backgroundColor,  TextStyle? textStyle,  String? toastId, | 普通文本Toast |
-| showWarning |  |   required String? text,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 警告Toast |
+| dismissAll | void | - | 关闭所有Toast |
+| dismissLoading | void | - | 关闭加载Toast(向后兼容) |
+| dismissToast | void | required String toastId | 关闭指定的Toast |
+| showFail | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 失败提示Toast |
+| showIconText | String | required String? text, IconData? icon, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带图标的Toast |
+| showLoading | String | required BuildContext context, String? text, Duration duration, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带文案的加载Toast |
+| showLoadingWithoutText | String | required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, double? iconSize, Color? iconColor, String? toastId | 不带文案的加载Toast |
+| showSuccess | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 成功提示Toast |
+| showText | String | required String? text, required BuildContext context, Duration duration, int? maxLines, BoxConstraints? constraints, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, String? toastId | 普通文本Toast |
+| showWarning | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 警告Toast |
+
+
+### IconTextDirection
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| horizontal | 横向 |
+| vertical | 竖向 |
diff --git a/tdesign-component/example/assets/api/tree-select_api.md b/tdesign-component/example/assets/api/tree-select_api.md
index 7ac293da2..ddb46646a 100644
--- a/tdesign-component/example/assets/api/tree-select_api.md
+++ b/tdesign-component/example/assets/api/tree-select_api.md
@@ -6,15 +6,13 @@
 | --- | --- | --- | --- |
 | defaultValue | List | const [] | 初始值,对应options中的value值 |
 | height | double | 336 | 高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | multiple | bool | false | 支持多选 |
 | onChange | TTreeSelectChangeEvent? | - | 选中值发生变化 |
 | options | List | const [] | 展示的选项列表 |
 | outwardCornerRadius | double | 9 | 一级菜单选中项的外弯折圆角半径,默认为 9 |
 | style | TTreeSelectStyle | TTreeSelectStyle.normal | 一级菜单样式 |
 
-```
-```
 
 ### TSelectOption
 #### 默认构造方法
@@ -27,3 +25,21 @@
 | maxLines | int | 1 | 最大显示行数 |
 | multiple | bool | false | 当前子项支持多选 |
 | value | dynamic | - | 值 |
+
+
+### TTreeSelectStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| outline | - |
+
+
+### TTreeSelectChangeEvent
+#### 类型定义
+
+```dart
+typedef TTreeSelectChangeEvent = void Function(List, int level);
+```
diff --git a/tdesign-component/example/assets/api/upload_api.md b/tdesign-component/example/assets/api/upload_api.md
index 978ac3fc3..52cd31835 100644
--- a/tdesign-component/example/assets/api/upload_api.md
+++ b/tdesign-component/example/assets/api/upload_api.md
@@ -8,7 +8,7 @@
 | enabledReplaceType | bool? | false | 是否启用replace功能 |
 | files | List | - | 控制展示的文件列表 |
 | height | double? | 80.0 | 图片高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | max | int | 0 | 用于控制文件上传数量,0为不限制,仅在multiple为true时有效 |
 | mediaType | List | const [TUploadMediaType.image, TUploadMediaType.video] | 支持上传的文件类型,图片或视频 |
 | multiple | bool | false | 是否多选上传,默认false |
@@ -25,3 +25,88 @@
 | wrapAlignment | WrapAlignment? | - | 多图对齐方式 |
 | wrapRunSpacing | double? | - | 多图布局时的 runSpacing |
 | wrapSpacing | double? | - | 多图布局时的 spacing |
+
+
+### TUploadMediaType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| image | - |
+| video | - |
+
+
+### TUploadValidatorError
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| overSize | - |
+| overQuantity | - |
+
+
+### TUploadFileStatus
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| success | - |
+| loading | - |
+| error | - |
+| retry | - |
+
+
+### TUploadType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| add | - |
+| remove | - |
+| replace | - |
+
+
+### TUploadBoxType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| roundedSquare | - |
+| circle | - |
+
+
+### TUploadErrorEvent
+#### 类型定义
+
+```dart
+typedef TUploadErrorEvent = void Function(Object e);
+```
+
+
+### TUploadClickEvent
+#### 类型定义
+
+```dart
+typedef TUploadClickEvent = void Function(int value);
+```
+
+
+### TUploadValueChangedEvent
+#### 类型定义
+
+```dart
+typedef TUploadValueChangedEvent = void Function(List files, TUploadType type);
+```
+
+
+### TUploadValidatorEvent
+#### 类型定义
+
+```dart
+typedef TUploadValidatorEvent = void Function(TUploadValidatorError e);
+```
diff --git a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt b/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt
deleted file mode 100644
index 3b06a0c90..000000000
--- a/tdesign-component/example/assets/code/popup._buildApiMarginTop.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-
-  Widget _buildApiMarginTop(BuildContext context) {
-    return TButton(
-      text: 'bottom margin.top',
-      isBlock: true,
-      theme: TButtonTheme.primary,
-      type: TButtonType.outline,
-      size: TButtonSize.large,
-      onTap: () {
-        TPopup.show(
-          context,
-          options: TPopupOptions.bottom(
-              height: 320,
-              margin: const EdgeInsets.only(top: 120, left: 16, right: 16),
-              titleWidget: TText('日历式留白'),
-              child: Container(
-                height: 240,
-                color: TTheme.of(context).bgColorContainer,
-              )),
-        );
-      },
-    );
-  }
\ No newline at end of file
diff --git a/tdesign-site/src/action-sheet/README.md b/tdesign-site/src/action-sheet/README.md
index 79a1a954c..b2c7bf0b0 100644
--- a/tdesign-site/src/action-sheet/README.md
+++ b/tdesign-site/src/action-sheet/README.md
@@ -897,14 +897,12 @@ Widget _buildIconListLeftActionSheet(BuildContext context) {
 | badge | TBadge? | - | 角标 |
 | description | String? | - | 描述信息 |
 | disabled | bool | false | 是否禁用 |
-| group | String? | - | 分组,用于带描述多行滚动宫格 |
+| group | String? | - | 分组,用于带描述多行滚动宫格 当[TActionSheet.theme]等于[TActionSheetTheme.group]时有效 有效时,如果该值未配置整个[TActionSheetItem]会被忽略,即不会展示 |
 | icon | Widget? | - | 图标 |
 | iconSize | double? | - | 图标大小 |
 | label | String | - | 标题 |
 | textStyle | TextStyle? | - | 标题样式 |
 
-```
-```
 
 ### TActionSheet
 #### 简介
@@ -913,23 +911,23 @@ Widget _buildIconListLeftActionSheet(BuildContext context) {
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
 | align | TActionSheetAlign | TActionSheetAlign.center | 对齐方式 |
 | cancelText | String? | - | 取消按钮的文本 |
 | closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
-| context | BuildContext | context | 上下文 |
-| count | int | 8 | 每页显示的项目数 |
-| description | String? | - | 描述文本 |
-| itemHeight | double | 96.0 | 项目的行高 |
-| itemMinWidth | double | 80.0 | 项目的最小宽度 |
+| count | int | 8 | 每页显示的项目数 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为true时有效 |
+| description | String? | - | 描述文本 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.list]时有效 |
+| itemHeight | double | 96.0 | 项目的行高 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.group]时有效 |
+| itemMinWidth | double | 80.0 | 项目的最小宽度 当[theme]等于[TActionSheetTheme.grid]且[scrollable]为true时有效 或当[theme]等于[TActionSheetTheme.group]时有效 |
 | items | List | - | ActionSheet的项目列表 |
 | onCancel | VoidCallback? | - | 取消按钮的回调函数 |
 | onClose | VoidCallback? | - | 关闭时的回调函数 |
 | onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
-| rows | int | 2 | 显示的行数 |
-| scrollable | bool | false | 是否可以横向滚动 |
+| rows | int | 2 | 显示的行数 当[theme]等于[TActionSheetTheme.grid]时有效 |
+| scrollable | bool | false | 是否可以横向滚动 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为false时有效 |
 | showCancel | bool | true | 是否显示取消按钮 |
 | showOverlay | bool | true | 是否显示遮罩层 |
-| showPagination | bool | false | 是否显示分页 |
+| showPagination | bool | false | 是否显示分页 当[theme]等于[TActionSheetTheme.grid]时有效 |
 | theme | TActionSheetTheme | TActionSheetTheme.list | 主题样式 |
 | useSafeArea | bool | true | 使用安全区域 |
 | visible | bool | false | 是否立即显示 |
@@ -939,9 +937,39 @@ Widget _buildIconListLeftActionSheet(BuildContext context) {
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showGridActionSheet |  |   required BuildContext context,  required List items,  TActionSheetAlign align,  String? cancelText,  bool showCancel,  TActionSheetItemCallback? onSelected,  bool showOverlay,  bool closeOnOverlayClick,  int count,  int rows,  double itemHeight,  double itemMinWidth,  bool scrollable,  bool showPagination,  VoidCallback? onCancel,  String? description,  VoidCallback? onClose,  bool useSafeArea, | 显示宫格类型面板 |
-| showGroupActionSheet |  |   required BuildContext context,  required List items,  TActionSheetAlign align,  String? cancelText,  bool showCancel,  TActionSheetItemCallback? onSelected,  bool showOverlay,  bool closeOnOverlayClick,  double itemHeight,  double itemMinWidth,  VoidCallback? onCancel,  VoidCallback? onClose,  bool useSafeArea, | 显示分组类型面板 |
-| showListActionSheet |  |   required BuildContext context,  required List items,  TActionSheetAlign align,  String? cancelText,  bool showCancel,  VoidCallback? onCancel,  TActionSheetItemCallback? onSelected,  bool showOverlay,  bool closeOnOverlayClick,  VoidCallback? onClose,  bool useSafeArea, | 显示列表类型面板 |
+| showGridActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, int count, int rows, double itemHeight, double itemMinWidth, bool scrollable, bool showPagination, VoidCallback? onCancel, String? description, VoidCallback? onClose, bool useSafeArea | 显示宫格类型面板 |
+| showGroupActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, double itemHeight, double itemMinWidth, VoidCallback? onCancel, VoidCallback? onClose, bool useSafeArea | 显示分组类型面板 |
+| showListActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, VoidCallback? onCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, VoidCallback? onClose, bool useSafeArea | 显示列表类型面板 |
+
+
+### TActionSheetTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| list | - |
+| grid | - |
+| group | - |
+
+
+### TActionSheetAlign
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| center | - |
+| left | - |
+| right | - |
+
+
+### TActionSheetItemCallback
+#### 类型定义
+
+```dart
+typedef TActionSheetItemCallback = void Function(TActionSheetItem item, int index);
+```
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/avatar/README.md b/tdesign-site/src/avatar/README.md
index b5f0e4be4..87922dfbb 100644
--- a/tdesign-site/src/avatar/README.md
+++ b/tdesign-site/src/avatar/README.md
@@ -327,8 +327,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | displayText | String? | - | 纯展示类型末尾文字 |
 | fit | BoxFit? | - | 自定义图片对齐方式 |
 | icon | IconData? | - | 自定义图标 |
-| key |  | - |  |
-| onTap |  Function()? | - | 操作点击事件 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onTap | Function()? | - | 操作点击事件 |
 | radius | double? | - | 自定义圆角 |
 | shape | TAvatarShape | TAvatarShape.circle | 头像形状 |
 | size | TAvatarSize | TAvatarSize.medium | 头像尺寸 |
@@ -336,4 +336,38 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | type | TAvatarType | TAvatarType.normal | 头像类型 |
 
 
+### TAvatarSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+
+
+### TAvatarType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| icon | - |
+| normal | - |
+| customText | - |
+| display | - |
+| operation | - |
+
+
+### TAvatarShape
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/back-top/README.md b/tdesign-site/src/back-top/README.md
index a2be1fa25..15324ce21 100644
--- a/tdesign-site/src/back-top/README.md
+++ b/tdesign-site/src/back-top/README.md
@@ -80,11 +80,31 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | controller | ScrollController? | - | 页面滚动的控制器 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onClick | VoidCallback? | - | 按钮点击事件 |
 | showText | bool | false | 是否展示文字 |
 | style | TBackTopStyle | TBackTopStyle.circle | 样式,圆形和半圆 |
 | theme | TBackTopTheme | TBackTopTheme.light | 主题 |
 
 
+### TBackTopTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| light | - |
+| dark | - |
+
+
+### TBackTopStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| halfCircle | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/badge/README.md b/tdesign-site/src/badge/README.md
index 07dbc85a6..2f629456f 100644
--- a/tdesign-site/src/badge/README.md
+++ b/tdesign-site/src/badge/README.md
@@ -460,19 +460,52 @@ Medium
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| type | TBadgeType | - | 红点样式 |
 | border | TBadgeBorder | TBadgeBorder.large | 红点圆角大小 |
 | color | Color? | - | 红点颜色 |
 | count | String? | - | 红点数量 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | maxCount | String? | '99' | 最大红点数量 |
 | message | String? | - | 消息内容 |
 | padding | EdgeInsetsGeometry? | - | 角标自定义padding |
 | showZero | bool | true | 值为0是否显示 |
 | size | TBadgeSize | TBadgeSize.small | 红点尺寸 |
 | textColor | Color? | - | 文字颜色 |
-| type | TBadgeType | type | 红点样式 |
 | widthLarge | double | 32 | 角标大三角形宽 |
 | widthSmall | double | 12 | 角标小三角形宽 |
 
 
+### TBadgeType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| redPoint | 红点样式 |
+| message | 消息样式 |
+| bubble | 气泡样式 |
+| square | 方形样式 |
+| subscript | 角标样式 |
+
+
+### TBadgeBorder
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | 大圆角 8px |
+| small | 小圆角 2px |
+
+
+### TBadgeSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | 宽 20px |
+| small | 宽 16px |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/button/README.md b/tdesign-site/src/button/README.md
index b21519c3a..99fb16e95 100644
--- a/tdesign-site/src/button/README.md
+++ b/tdesign-site/src/button/README.md
@@ -755,7 +755,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | iconTextSpacing | double? | - | 自定义图标与文本之间距离 |
 | iconWidget | Widget? | - | 自定义图标 icon 控件 |
 | isBlock | bool | false | 是否为通栏按钮 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | margin | EdgeInsetsGeometry? | - | 自定义 margin |
 | onLongPress | TButtonEvent? | - | 长按事件 |
 | onTap | TButtonEvent? | - | 点击事件 |
@@ -769,8 +769,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | type | TButtonType | TButtonType.fill | 类型:填充,描边,文字 |
 | width | double? | - | 自定义宽度 |
 
-```
-```
 
 ### TButtonStyle
 #### 默认构造方法
@@ -787,12 +785,126 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TButtonStyle.generateFillStyleByTheme  | 生成不同主题的填充按钮样式 |
-| TButtonStyle.generateGhostStyleByTheme  | 生成不同主题的幽灵按钮样式 |
-| TButtonStyle.generateOutlineStyleByTheme  | 生成不同主题的描边按钮样式 |
-| TButtonStyle.generateTextStyleByTheme  | 生成不同主题的文本按钮样式 |
+##### TButtonStyle.generateFillStyleByTheme
+
+生成不同主题的填充按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+##### TButtonStyle.generateGhostStyleByTheme
+
+生成不同主题的幽灵按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+##### TButtonStyle.generateOutlineStyleByTheme
+
+生成不同主题的描边按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+##### TButtonStyle.generateTextStyleByTheme
+
+生成不同主题的文本按钮样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| theme | TButtonTheme? | - | - |
+| status | TButtonStatus | - | - |
+
+
+### TButtonSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+| extraSmall | - |
+
+
+### TButtonType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| fill | - |
+| outline | - |
+| text | - |
+| ghost | - |
+
+
+### TButtonShape
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| rectangle | - |
+| round | - |
+| square | - |
+| circle | - |
+| filled | - |
+
+
+### TButtonTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | - |
+| primary | - |
+| danger | - |
+| light | - |
+
+
+### TButtonStatus
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultState | - |
+| active | - |
+| disable | - |
+
+
+### TButtonIconPosition
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+
+
+### TButtonEvent
+#### 类型定义
+
+```dart
+typedef TButtonEvent = void Function();
+```
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/calendar/README.md b/tdesign-site/src/calendar/README.md
index 5d05d7424..bb1062ed8 100644
--- a/tdesign-site/src/calendar/README.md
+++ b/tdesign-site/src/calendar/README.md
@@ -1461,7 +1461,7 @@ Widget _buildLunar(BuildContext context) {
 | format | CalendarFormat? | - | 用于格式化日期的函数,可定义日期前后的显示内容和日期样式 |
 | height | double? | - | 高度 |
 | isTimeUnit | bool? | true | 是否显示时间单位 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | maxDate | int? | - | 最大可选的日期(fromMillisecondsSinceEpoch),不传则默认半年后 |
 | minDate | int? | - | 最小可选的日期(fromMillisecondsSinceEpoch),不传则默认今天 |
 | monthTitleBuilder | Widget Function(BuildContext context, DateTime monthDate)? | - | 月标题构建器 |
@@ -1484,26 +1484,22 @@ Widget _buildLunar(BuildContext context) {
 | value | List? | - | 当前选择的日期(fromMillisecondsSinceEpoch),不传则默认今天,当 type = single 时数组长度为1 |
 | width | double? | - | 宽度 |
 
-```
-```
 
 ### TCalendarPopup
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
 | autoClose | bool? | true | 自动关闭;在点击关闭按钮、确认按钮、遮罩层时自动关闭 |
 | builder | CalendarBuilder? | - | 控件构建器,优先级高于[child] |
 | child | TCalendar? | - | 日历控件 |
 | confirmBtn | Widget? | - | 自定义确认按钮 |
-| context | BuildContext | context | 上下文 |
 | onClose | VoidCallback? | - | 关闭时触发 |
 | onConfirm | void Function(List value)? | - | 点击确认按钮时触发 |
 | top | double? | - | 距离顶部的距离 |
 | visible | bool? | - | 默认是否显示日历 |
 
-```
-```
 
 ### TCalendarStyle
 #### 默认构造方法
@@ -1515,27 +1511,63 @@ Widget _buildLunar(BuildContext context) {
 | cellStyle | TextStyle? | - | 日期样式 |
 | cellSuffixStyle | TextStyle? | - | 日期后面的字符串的样式 |
 | centreColor | Color? | - | 日期范围内背景样式 |
-| decoration |  | - |  |
+| decoration | BoxDecoration? | - | - |
 | monthTitleStyle | TextStyle? | - | body区域 年月文字样式 |
 | titleCloseColor | Color? | - | header区域 关闭图标的颜色 |
 | titleMaxLine | int? | - | header区域 [TCalendar.title]的行数 |
 | titleStyle | TextStyle? | - | header区域 [TCalendar.title]的样式 |
 | weekdayStyle | TextStyle? | - | header区域 周 文字样式 |
 
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| bodyPadding | double? | - | 月与月之间的垂直间距 |
+| todayStyle | TextStyle? | - | 当天日期样式 |
+| verticalGap | double? | - | 日期垂直间距,水平间距为[verticalGap] / 2 |
+
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TCalendarStyle.cellStyle  | 日期样式 |
-| TCalendarStyle.generateStyle  | 生成默认样式 |
+##### TCalendarStyle.cellStyle
+
+日期样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| type | DateSelectType? | - | - |
+
+
+##### TCalendarStyle.generateStyle
+
+生成默认样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
 
-```
-```
 
 ### TCalendarDataSource
-```
-```
+#### 简介
+日历数据源接口
+ 
+ 开发者需要实现此接口来提供农历转换能力。
+ 组件内部不包含农历算法和数据,完全依赖外部实现。
+
+#### 方法
+
+| 名称 | 返回类型 | 参数 | 说明 |
+| --- | --- | --- | --- |
+| getLunarInfo | TLunarInfo? | required DateTime solarDate | 获取指定阳历日期的农历信息 [solarDate] 阳历日期 返回 null 表示不显示农历信息 |
+| formatDate | String | required DateTime date, required TCalendarDateType type, TLunarInfo? lunarInfo | 格式化日期文本 [date] 阳历日期 [type] 日历类型 [lunarInfo] 农历信息(可选) 返回格式化后的日期字符串 |
+| getSolarTerm | String? | required DateTime date | 获取节气信息(可选实现) [date] 阳历日期 返回节气名称,如"春分"、"秋分"等,无节气则返回 null |
+| getFestival | String? | required DateTime date, TLunarInfo? lunarInfo | 获取节日信息(可选实现) [date] 阳历日期 [lunarInfo] 农历信息(可选) 返回节日名称,如"春节"、"中秋节"等,无节日则返回 null |
+| getHolidayInfo | Map? | required DateTime date | 获取假期信息(可选实现) [date] 阳历日期 返回假期类型和名称: - 'holiday': 法定节假日/公共假期(如"国庆节") - 'workday': 调休工作日(如"补班") - null: 正常日期 示例返回值: - {'type': 'holiday', 'name': '国庆节'} - {'type': 'workday', 'name': '补班'} - null |
+| formatYear | String | required int year, required TCalendarDateType type | 格式化年份文本 [year] 年份 [type] 日历类型 返回格式化后的年份字符串 阳历示例:2025 -> "2025年" 阴历示例:2025 -> "二〇二五年" |
+| formatMonth | String | required int month, required TCalendarDateType type, bool isLeapMonth | 格式化月份文本 [month] 月份(1-12) [type] 日历类型 [isLeapMonth] 是否是闰月(仅农历有效) 返回格式化后的月份字符串 阳历示例:3 -> "3月" 阴历示例:3 -> "三月",闰3月 -> "闰三月" |
+| formatDay | String | required int day, required TCalendarDateType type | 格式化日期文本 [day] 日期(1-31) [type] 日历类型 返回格式化后的日期字符串 阳历示例:7 -> "7日" 阴历示例:7 -> "初七" |
+
 
 ### TLunarInfo
 #### 默认构造方法
@@ -1551,4 +1583,68 @@ Widget _buildLunar(BuildContext context) {
 | yearText | String | - | 年份文本(如:二〇二五) |
 
 
+### TCalendarDateType
+#### 简介
+日历类型枚举
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| solar | 阳历(公历) |
+| lunar | 阴历(农历) |
+
+
+### CalendarType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| single | - |
+| multiple | - |
+| range | - |
+
+
+### CalendarTrigger
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| closeBtn | - |
+| confirmBtn | - |
+| overlay | - |
+
+
+### DateSelectType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| selected | - |
+| disabled | - |
+| start | - |
+| centre | - |
+| end | - |
+| empty | - |
+
+
+### CalendarFormat
+#### 类型定义
+
+```dart
+typedef CalendarFormat = TDate? Function(TDate? day);
+```
+
+
+### CalendarBuilder
+#### 类型定义
+
+```dart
+typedef CalendarBuilder = Widget Function(BuildContext context);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/cascader/README.md b/tdesign-site/src/cascader/README.md
index f7482529e..4a310a832 100644
--- a/tdesign-site/src/cascader/README.md
+++ b/tdesign-site/src/cascader/README.md
@@ -249,7 +249,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | initialData | String? | - | 初始化数据 |
 | initialIndexes | List? | - | 若为null表示全部从零开始 |
 | isLetterSort | bool | false | 是否开启字母排序 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onChange | MultiCascaderCallback | - | 值发生变更时触发 |
 | onClose | Function? | - | 选择器关闭按钮回调 |
 | subTitles | List? | - | 每级展示的次标题 |
@@ -259,4 +259,12 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | topRadius | double? | - | 顶部圆角 |
 
 
+### MultiCascaderCallback
+#### 类型定义
+
+```dart
+typedef MultiCascaderCallback = void Function(List selected);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/cell/README.md b/tdesign-site/src/cell/README.md
index 163f51ad7..d345ad750 100644
--- a/tdesign-site/src/cell/README.md
+++ b/tdesign-site/src/cell/README.md
@@ -177,7 +177,7 @@ Widget _buildCard(BuildContext context) {
 | imageCircle | double? | 50 | 主图圆角,默认50(圆形) |
 | imageSize | double? | - | 主图尺寸 |
 | imageWidget | Widget? | - | 主图组件 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftIcon | IconData? | - | 左侧图标,出现在单元格标题的左侧 |
 | leftIconWidget | Widget? | - | 左侧图标组件 |
 | note | String? | - | 和标题同行的说明文字 |
@@ -194,8 +194,6 @@ Widget _buildCard(BuildContext context) {
 | title | String? | - | 标题 |
 | titleWidget | Widget? | - | 标题组件 |
 
-```
-```
 
 ### TCellGroup
 #### 简介
@@ -208,15 +206,13 @@ Widget _buildCard(BuildContext context) {
 | builder | CellBuilder? | - | cell构建器,可自定义cell父组件,如Dismissible |
 | cells | List | - | 单元格列表 |
 | isShowLastBordered | bool? | false | 是否显示最后一个cell的下边框 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | scrollable | bool? | false | 可滚动 |
 | style | TCellStyle? | - | 自定义样式 |
 | theme | TCellGroupTheme? | TCellGroupTheme.defaultTheme | 单元格组风格。可选项:default/card |
 | title | String? | - | 单元格组标题 |
 | titleWidget | Widget? | - | 单元格组标题组件 |
 
-```
-```
 
 ### TCellStyle
 #### 简介
@@ -247,9 +243,50 @@ Widget _buildCard(BuildContext context) {
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TCellStyle.cellStyle  | 生成单元格默认样式 |
+##### TCellStyle.cellStyle
+
+生成单元格默认样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 传递context,会生成默认样式 |
+
+
+### TCellAlign
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| top | - |
+| middle | - |
+| bottom | - |
+
+
+### TCellGroupTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | - |
+| cardTheme | - |
+
+
+### TCellClick
+#### 类型定义
+
+```dart
+typedef TCellClick = void Function(TCell cell);
+```
+
+
+### CellBuilder
+#### 类型定义
+
+```dart
+typedef CellBuilder = Widget Function(BuildContext context, TCell cell, int index);
+```
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/checkbox/README.md b/tdesign-site/src/checkbox/README.md
index 3fde5c091..1252d2903 100644
--- a/tdesign-site/src/checkbox/README.md
+++ b/tdesign-site/src/checkbox/README.md
@@ -434,16 +434,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | backgroundColor | Color? | - | 背景颜色 |
 | cardMode | bool | false | 展示为卡片模式 |
 | checkBoxLeftSpace | double? | - | 选项框左侧间距 |
-| checked | bool | false | 选中状态。默认为`false` |
+| checked | bool | false | 选中状态。默认为`false` 当FuiCheckBox嵌入到FuiCheckBoxGroup的时候,这个值表示初始状态,后续的状态会由Group管理 |
 | contentDirection | TContentDirection | TContentDirection.right | 文字相对icon的方位 |
 | customContentBuilder | ContentBuilder? | - | 完全自定义内容 |
 | customIconBuilder | IconBuilder? | - | 自定义Checkbox显示样式 |
 | customSpace | EdgeInsetsGeometry? | - | 自定义组件间距 |
 | disableColor | Color? | - | 禁用选择颜色 |
 | enable | bool | true | 不可用 |
-| id | String? | - | id |
+| id | String? | - | id 当FuiCheckBox嵌入到FuiCheckBoxGroup内时,这个值需要赋值,否则不会被纳入Group管理 |
 | insetSpacing | double? | 16 | 文字和非图标侧的距离 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onCheckBoxChanged | OnCheckValueChanged? | - | 切换监听 |
 | selectColor | Color? | - | 选择颜色 |
 | showDivider | bool | true | 是否展示分割线 |
@@ -459,8 +459,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | titleFont | Font? | - | 标题字体大小 |
 | titleMaxLine | int? | - | 标题的行数 |
 
-```
-```
 
 ### TCheckboxGroup
 #### 默认构造方法
@@ -468,12 +466,12 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | checkedIds | List? | - | 勾选的CheckBox id列表 |
-| child |  | - |  |
+| child | Widget | - | 可以是任意包含TCheckBox的容器,比如: ``` Row( children: [ TCheckBox(), TCheckBox(), ... ] ) ``` |
 | contentDirection | TContentDirection? | - | 文字相对icon的方位 |
 | controller | TCheckboxGroupController? | - | 可以通过控制器操作勾选状态 |
 | customContentBuilder | ContentBuilder? | - | CheckBox完全自定义内容 |
 | customIconBuilder | IconBuilder? | - | 自定义选择icon的样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | maxChecked | int? | - | 最多可以勾选多少 |
 | onChangeGroup | OnGroupChange? | - | 状态变化监听器 |
 | onOverloadChecked | VoidCallback? | - | 超过最大可勾选的个数 |
@@ -482,4 +480,85 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | titleMaxLine | int? | - | CheckBox标题的行数 |
 
 
+### TCheckboxStyle
+#### 简介
+选择框的样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
+| check | - |
+
+
+### TContentDirection
+#### 简介
+内容相对icon的位置,上、下、左、右,默认内容在icon的右边
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+
+
+### TCheckBoxSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| small | - |
+
+
+### IconBuilder
+#### 简介
+自定义Icon
+#### 类型定义
+
+```dart
+typedef IconBuilder = Widget? Function(BuildContext context, bool checked);
+```
+
+
+### ContentBuilder
+#### 简介
+自定义Content
+#### 类型定义
+
+```dart
+typedef ContentBuilder = Widget Function(BuildContext context, bool checked, String? content);
+```
+
+
+### OnCheckValueChanged
+#### 类型定义
+
+```dart
+typedef OnCheckValueChanged = void Function(bool selected);
+```
+
+
+### OnGroupChange
+#### 简介
+CheckBoxGroup变化监听器
+#### 类型定义
+
+```dart
+typedef OnGroupChange = void Function(List checkedIds);
+```
+
+
+### OnCheckBoxGroupChange
+#### 类型定义
+
+```dart
+typedef OnCheckBoxGroupChange = void Function(List ids);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/collapse/README.md b/tdesign-site/src/collapse/README.md
index a1f767ff2..0174ec848 100644
--- a/tdesign-site/src/collapse/README.md
+++ b/tdesign-site/src/collapse/README.md
@@ -177,16 +177,42 @@ Card Style 卡片样式
 | animationDuration | Duration | kThemeAnimationDuration | 折叠面板列表的动画时长 |
 | children | List | - | 折叠面板列表的子组件 |
 | elevation | double | 0 | 折叠面板列表的阴影 |
-| expansionCallback | ExpansionPanelCallback? | - | 折叠面板列表的回调函数; |
-| key |  | - |  |
-| style | TCollapseStyle | TCollapseStyle.block | 折叠面板列表的样式 |
+| expansionCallback | ExpansionPanelCallback? | - | 折叠面板列表的回调函数; 回调时,入参为当前点击的折叠面板的索引 index 和是否展开的状态 isExpanded |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| style | TCollapseStyle | TCollapseStyle.block | 折叠面板列表的样式 - [TCollapseStyle.block] 通栏风格 - [TCollapseStyle.card] 卡片风格 |
+
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| initialOpenPanelValue | Object? | - | 折叠面板列表的默认展开面板的值; 当使用 [TCollapse.accordion] 时,此值生效 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TCollapse.accordion  |  |
+##### TCollapse.accordion
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| children | List | - | 折叠面板列表的子组件 |
+| style | TCollapseStyle | TCollapseStyle.block | 折叠面板列表的样式 - [TCollapseStyle.block] 通栏风格 - [TCollapseStyle.card] 卡片风格 |
+| expansionCallback | ExpansionPanelCallback? | - | 折叠面板列表的回调函数; 回调时,入参为当前点击的折叠面板的索引 index 和是否展开的状态 isExpanded |
+| animationDuration | Duration | kThemeAnimationDuration | 折叠面板列表的动画时长 |
+| elevation | double | 0 | 折叠面板列表的阴影 |
+| initialOpenPanelValue | Object? | - | 折叠面板列表的默认展开面板的值; 当使用 [TCollapse.accordion] 时,此值生效 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+
+
+### TCollapseStyle
+#### 简介
+折叠面板的组件样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| block | Block 通栏风格 |
+| card | Card 卡片风格 |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/dialog/README.md b/tdesign-site/src/dialog/README.md
index 287b1a962..68ee496dc 100644
--- a/tdesign-site/src/dialog/README.md
+++ b/tdesign-site/src/dialog/README.md
@@ -709,19 +709,19 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | backgroundColor | Color? | - | 背景颜色 |
-| buttonStyle |  | TDialogButtonStyle.normal |  |
+| buttonStyle | TDialogButtonStyle | TDialogButtonStyle.normal | - |
 | buttonWidget | Widget? | - | 自定义按钮 |
 | content | String? | - | 内容 |
 | contentColor | Color? | - | 内容颜色 |
 | contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
 | contentWidget | Widget? | - | 内容Widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions? | - | 左侧按钮配置 |
-| leftBtnAction |  Function()? | - | 左侧按钮默认点击 |
+| leftBtnAction | Function()? | - | 左侧按钮默认点击 |
 | padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
 | rightBtn | TDialogButtonOptions? | - | 右侧按钮配置 |
-| rightBtnAction |  Function()? | - | 右侧按钮默认点击 |
+| rightBtnAction | Function()? | - | 右侧按钮默认点击 |
 | showCloseButton | bool? | - | 显示右上角关闭按钮 |
 | title | String? | - | 标题 |
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
@@ -730,21 +730,36 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TAlertDialog.vertical  | 纵向按钮排列的对话框
+##### TAlertDialog.vertical
 
- [buttons]参数是必须的,纵向按钮默认样式都是[TButtonTheme.primary] |
+纵向按钮排列的对话框
+
+ [buttons]参数是必须的,纵向按钮默认样式都是[TButtonTheme.primary]
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| buttons | List | - | - |
+| backgroundColor | Color? | - | 背景颜色 |
+| radius | double | 12.0 | 圆角 |
+| title | String? | - | 标题 |
+| titleColor | Color? | - | 标题颜色 |
+| titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
+| contentWidget | Widget? | - | 内容Widget |
+| content | String? | - | 内容 |
+| contentColor | Color? | - | 内容颜色 |
+| contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
+| showCloseButton | bool? | - | 显示右上角关闭按钮 |
+| padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
+| buttonWidget | Widget? | - | 自定义按钮 |
 
-```
-```
 
 ### TConfirmDialog
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| action |  Function()? | - | 点击 |
+| action | Function()? | - | 点击 |
 | backgroundColor | Color? | - | 背景颜色 |
 | buttonStyle | TDialogButtonStyle | TDialogButtonStyle.normal | 按钮样式 |
 | buttonStyleCustom | TButtonStyle? | - | 按钮自定义样式属性,背景色、边框... |
@@ -755,35 +770,31 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | contentColor | Color? | - | 内容颜色 |
 | contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
 | contentWidget | Widget? | - | 内容Widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
 | showCloseButton | bool? | - | 右上角关闭按钮 |
 | title | String? | - | 标题 |
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
 | titleColor | Color? | - | 标题颜色 |
-| width |  | - |  |
+| width | double? | - | - |
 
-```
-```
 
 ### TDialogButtonOptions
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| action |  Function()? | - | 点击操作 |
+| action | Function()? | - | 点击操作 |
 | fontWeight | FontWeight? | - | 字体粗细 |
-| height | double? | - | 按钮高度 |
-| style | TButtonStyle? | - | 按钮样式 |
+| height | double? | - | 按钮高度 建议使用默认高度 |
+| style | TButtonStyle? | - | 按钮样式 设置单个按钮的样式会覆盖Dialog的默认样式 |
 | theme | TButtonTheme? | - | 按钮类型 |
 | title | String | - | 标题内容 |
 | titleColor | Color? | - | 标题颜色 |
 | titleSize | double? | - | 字体大小 |
 | type | TButtonType? | - | 按钮类型 |
 
-```
-```
 
 ### TDialogScaffold
 #### 默认构造方法
@@ -792,25 +803,21 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | --- | --- | --- | --- |
 | backgroundColor | Color? | - | 背景色 |
 | body | Widget | - | Dialog主体 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | radius | double | 12.0 | 圆角 |
 | showCloseButton | bool? | - | 显示右上角关闭按钮 |
 | width | double? | - | 弹窗宽度 |
 
-```
-```
 
 ### TDialogTitle
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | title | String? | - | 标题文字 |
 | titleColor | Color? | - | 标题颜色 |
 
-```
-```
 
 ### TDialogContent
 #### 默认构造方法
@@ -819,10 +826,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | --- | --- | --- | --- |
 | content | String? | - | 标题文字 |
 | contentColor | Color? | - | 标题颜色 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 
-```
-```
 
 ### TDialogInfoWidget
 #### 默认构造方法
@@ -833,38 +838,32 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | contentColor | Color? | - | 内容颜色 |
 | contentMaxHeight | double | 0 | 内容的最大高度,默认为0,也就是不限制高度 |
 | contentWidget | Widget? | - | 内容Widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | padding | EdgeInsetsGeometry? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容的内边距 |
 | title | String? | - | 标题 |
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
 | titleColor | Color? | - | 标题颜色 |
 
-```
-```
 
 ### HorizontalNormalButtons
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions | - | 左按钮 |
 | rightBtn | TDialogButtonOptions | - | 右按钮 |
 
-```
-```
 
 ### HorizontalTextButtons
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions | - | 左按钮 |
 | rightBtn | TDialogButtonOptions | - | 右按钮 |
 
-```
-```
 
 ### TDialogButton
 #### 默认构造方法
@@ -880,12 +879,10 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | buttonType | TButtonType? | - | 按钮类型 |
 | height | double? | 40.0 | 按钮高度 |
 | isBlock | bool | true | 按钮高度 |
-| key |  | - |  |
-| onPressed |  Function() | - | 点击 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onPressed | Function() | - | 点击 |
 | width | double? | - | 按钮宽度 |
 
-```
-```
 
 ### TImageDialog
 #### 默认构造方法
@@ -899,7 +896,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | contentWidget | Widget? | - | 内容Widget |
 | image | Image | - | 图片 |
 | imagePosition | TDialogImagePosition? | TDialogImagePosition.top | 图片位置 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions? | - | 左侧按钮配置 |
 | padding | EdgeInsets? | - | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
@@ -909,8 +906,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | titleAlignment | AlignmentGeometry? | - | 标题对齐模式 |
 | titleColor | Color? | - | 标题颜色 |
 
-```
-```
 
 ### TInputDialog
 #### 默认构造方法
@@ -924,7 +919,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | contentWidget | Widget? | - | 内容Widget |
 | customInputWidget | Widget? | - | 自定义输入框 |
 | hintText | String? | '' | 输入提示 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBtn | TDialogButtonOptions? | - | 左侧按钮配置 |
 | padding | EdgeInsets? | const EdgeInsets.fromLTRB(24, 32, 24, 0) | 内容内边距 |
 | radius | double | 12.0 | 圆角 |
@@ -936,4 +931,29 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | titleColor | Color? | - | 标题颜色 |
 
 
+### TDialogButtonStyle
+#### 简介
+Dialog按钮样式
+
+ 用于在Dialog层面配置按钮样式
+ Dialog内支持配置每个按钮的样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| text | - |
+
+
+### TDialogImagePosition
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| top | - |
+| middle | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/divider/README.md b/tdesign-site/src/divider/README.md
index 56fabf0ee..37251b43a 100644
--- a/tdesign-site/src/divider/README.md
+++ b/tdesign-site/src/divider/README.md
@@ -152,7 +152,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | height | double | 0.5 | 高度,横向线条使用 |
 | hideLine | bool | false | 隐藏线条,使用纯文本分割 |
 | isDashed | bool | false | 是否为虚线 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | margin | EdgeInsetsGeometry? | - | 外部填充 |
 | text | String? | - | 文本字符串,使用默认样式 |
 | textStyle | TextStyle? | - | 自定义文本样式 |
@@ -160,4 +160,15 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | width | double? | - | 宽度,需要竖向线条时使用 |
 
 
+### TextAlignment
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| center | - |
+| right | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/drawer/README.md b/tdesign-site/src/drawer/README.md
index 1e4ad50dd..1bdf26a3c 100644
--- a/tdesign-site/src/drawer/README.md
+++ b/tdesign-site/src/drawer/README.md
@@ -292,11 +292,11 @@ Widget _buildBottomSimple(BuildContext context) {
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
 | backgroundColor | Color? | - | 组件背景颜色 |
 | bordered | bool? | true | 是否显示边框 |
 | closeOnOverlayClick | bool? | true | 点击蒙层时是否关闭抽屉 |
 | contentWidget | Widget? | - | 自定义内容,优先级高于[items]/[footer]/[title] |
-| context | BuildContext | context | 上下文 |
 | drawerTop | double? | - | 距离顶部的距离 |
 | footer | Widget? | - | 抽屉的底部 |
 | hover | bool? | true | 是否开启点击反馈 |
@@ -312,8 +312,6 @@ Widget _buildBottomSimple(BuildContext context) {
 | visible | bool? | - | 组件是否可见 |
 | width | double? | 280 | 宽度 |
 
-```
-```
 
 ### TDrawerWidget
 #### 简介
@@ -330,15 +328,13 @@ Widget _buildBottomSimple(BuildContext context) {
 | hover | bool? | true | 是否开启点击反馈 |
 | isShowLastBordered | bool? | true | 是否显示最后一行分割线 |
 | items | List? | - | 抽屉里的列表项 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onItemClick | TDrawerItemClickCallback? | - | 点击抽屉里的列表项触发 |
 | style | TCellStyle? | - | 列表自定义样式 |
 | title | String? | - | 抽屉的标题 |
 | titleWidget | Widget? | - | 抽屉的标题组件 |
 | width | double? | 280 | 宽度 |
 
-```
-```
 
 ### TDrawerItem
 #### 简介
@@ -352,4 +348,24 @@ Widget _buildBottomSimple(BuildContext context) {
 | title | String? | - | 每列标题 |
 
 
+### TDrawerPlacement
+#### 简介
+抽屉方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+
+
+### TDrawerItemClickCallback
+#### 类型定义
+
+```dart
+typedef TDrawerItemClickCallback = void Function(int index, TDrawerItem item);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/dropdown-menu/README.md b/tdesign-site/src/dropdown-menu/README.md
index ec977c8ed..cfa91abfd 100644
--- a/tdesign-site/src/dropdown-menu/README.md
+++ b/tdesign-site/src/dropdown-menu/README.md
@@ -283,7 +283,7 @@ TDropdownMenu _buildGroup(BuildContext context) {
 | height | double? | 48 | menu的高度 |
 | isScrollable | bool? | false | 是否开启滚动列表 |
 | items | List? | - | 下拉菜单 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelBuilder | LabelBuilder? | - | 自定义标签内容 |
 | onMenuClosed | ValueChanged? | - | 关闭菜单事件 |
 | onMenuOpened | ValueChanged? | - | 展开菜单事件 |
@@ -291,8 +291,6 @@ TDropdownMenu _buildGroup(BuildContext context) {
 | tabBarAlign | MainAxisAlignment? | MainAxisAlignment.center | [TDropdownItem.label]和[arrowIcon]/[TDropdownItem.arrowIcon]的对齐方式 |
 | width | double? | - | menu的宽度 |
 
-```
-```
 
 ### TDropdownItem
 #### 简介
@@ -306,7 +304,7 @@ TDropdownMenu _buildGroup(BuildContext context) {
 | builder | TDropdownItemContentBuilder? | - | 完全自定义展示内容 |
 | controller | TDropdownItemController? | - | 下拉菜单控制器 |
 | disabled | bool? | false | 是否禁用 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String? | - | 标题 |
 | maxHeight | double? | - | 内容最大高度 |
 | minHeight | double? | - | 内容最小高度 |
@@ -320,8 +318,12 @@ TDropdownMenu _buildGroup(BuildContext context) {
 | tabBarFlex | int? | 1 | 该item在menu上的宽度占比,仅在[TDropdownMenu.isScrollable]为false时有效 |
 | tabBarWidth | double? | - | 该item在menu上的宽度,仅在[TDropdownMenu.isScrollable]为true时有效 |
 
-```
-```
+#### 静态成员
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| operateHeight | double | - | - |
+
 
 ### TDropdownItemOption
 #### 简介
@@ -338,11 +340,58 @@ TDropdownMenu _buildGroup(BuildContext context) {
 | selectedColor | Color? | - | 选中颜色 |
 | value | String | - | 选项值 |
 
-```
-```
 
 ### TDropdownItemController
 #### 简介
 下拉菜单控制器
 
+### TDropdownMenuDirection
+#### 简介
+菜单展开方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| down | 向下 |
+| up | 向上 |
+| auto | 根据内容高度动态展示方向 |
+
+
+### TDropdownItemContentBuilder
+#### 类型定义
+
+```dart
+typedef TDropdownItemContentBuilder = Widget Function(BuildContext context, _TDropdownItemState itemState, TDropdownPopup? popupState);
+```
+
+
+### TDropdownItemOptionsCallback
+#### 类型定义
+
+```dart
+typedef TDropdownItemOptionsCallback = void Function(List? options);
+```
+
+
+### TDropdownItemBuilder
+#### 简介
+下拉菜单构建器
+#### 类型定义
+
+```dart
+typedef TDropdownItemBuilder = List Function(BuildContext context);
+```
+
+
+### LabelBuilder
+#### 简介
+自定义标签内容
+#### 类型定义
+
+```dart
+typedef LabelBuilder = Widget Function(BuildContext context, String label, bool isOpened, int index);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/empty/README.md b/tdesign-site/src/empty/README.md
index 8f5075b70..b7ed502d3 100644
--- a/tdesign-site/src/empty/README.md
+++ b/tdesign-site/src/empty/README.md
@@ -131,11 +131,29 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | emptyTextFont | Font? | - | 描述文字大小 |
 | icon | IconData? | TIcons.info_circle_filled | 图标 |
 | image | Widget? | - | 展示图片 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onTapEvent | TTapEvent? | - | 点击事件 |
 | operationText | String? | - | 操作按钮文案 |
 | operationTheme | TButtonTheme? | - | 操作按钮文案主题色 |
 | type | TEmptyType | TEmptyType.plain | 类型,为operation有操作按钮,plain无按钮 |
 
 
+### TEmptyType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| plain | - |
+| operation | - |
+
+
+### TTapEvent
+#### 类型定义
+
+```dart
+typedef TTapEvent = void Function();
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/fab/README.md b/tdesign-site/src/fab/README.md
index c24020665..4badca22f 100644
--- a/tdesign-site/src/fab/README.md
+++ b/tdesign-site/src/fab/README.md
@@ -167,7 +167,7 @@ Fab Size 悬浮按钮尺寸
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | icon | Icon? | - | 图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onClick | VoidCallback? | - | 点击事件 |
 | shape | TFabShape | TFabShape.circle | 形状 |
 | size | TFabSize | TFabSize.large | 大小 |
@@ -175,4 +175,38 @@ Fab Size 悬浮按钮尺寸
 | theme | TFabTheme | TFabTheme.defaultTheme | 主题 |
 
 
+### TFabTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| primary | - |
+| defaultTheme | - |
+| light | - |
+| danger | - |
+
+
+### TFabShape
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
+
+
+### TFabSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+| extraSmall | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/footer/README.md b/tdesign-site/src/footer/README.md
index d6323dbda..d761bcc58 100644
--- a/tdesign-site/src/footer/README.md
+++ b/tdesign-site/src/footer/README.md
@@ -117,13 +117,24 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| type | TFooterType | - | 样式 |
 | height | double? | - | 自定义图片高 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | links | List | const [] | 链接 |
 | logo | String? | - | 品牌图片 |
 | text | String | '' | 文字 |
-| type | TFooterType | type | 样式 |
 | width | double? | - | 自定义图片宽 |
 
 
+### TFooterType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| text | 文字样式 |
+| link | 链接样式 |
+| brand | 品牌样式 |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/form/README.md b/tdesign-site/src/form/README.md
index 25d02941b..02c02781a 100644
--- a/tdesign-site/src/form/README.md
+++ b/tdesign-site/src/form/README.md
@@ -719,24 +719,22 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | data | Map | - | 表单数据 |
 | disabled | bool | false | 是否禁用整个表单 |
 | errorMessage | Object? | - | 表单信息错误信息配置 |
-| formContentAlign | TextAlign | TextAlign.left | 表单内容对齐方式: 左对齐、右对齐、居中对齐 |
+| formContentAlign | TextAlign | TextAlign.left | 表单内容对齐方式: 左对齐、右对齐、居中对齐 可选项: left/right/center 默认为左对齐 优先级低于 TFormItem 的对齐 API TODO: TStepper TRate 等组件没用实现通用性 |
 | formController | FormController? | - | 表单控制器 |
-| formLabelAlign | TextAlign? | TextAlign.left | 表单字段标签的对齐方式: |
-| formShowErrorMessage | bool? | true | 校验不通过时,是否显示错误提示信息,统一控制全部表单项 |
+| formLabelAlign | TextAlign? | TextAlign.left | 表单字段标签的对齐方式: 左对齐、右对齐、顶部对齐 可选项: left/right/top TODO: 表单总体标签对齐方式 |
+| formShowErrorMessage | bool? | true | 校验不通过时,是否显示错误提示信息,统一控制全部表单项 如果希望控制单个表单项,请给 FormItem 设置该属性 |
 | isHorizontal | bool | true | 表单排列方式是否为 水平方向 |
 | items | List | - | 表单内容 items |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelWidth | double? | 20.0 | 可以整体设置 label 标签宽度 |
 | onReset | Function? | - | 表单重置时触发 |
 | onSubmit | Function | - | 表单提交时触发 |
-| preventSubmitDefault | bool? | true | 是否阻止表单提交默认事件(表单提交默认事件会刷新页面) |
+| preventSubmitDefault | bool? | true | 是否阻止表单提交默认事件(表单提交默认事件会刷新页面) 设置为 true 可以避免刷新 |
 | requiredMark | bool? | true | 是否显示必填符号(*),默认显示 |
 | rules | Map | - | 整个表单字段校验规则 |
-| scrollToFirstError | String? | - | 表单校验不通过时,是否自动滚动到第一个校验不通过的字段,平滑滚动或是瞬间直达。 |
+| scrollToFirstError | String? | - | 表单校验不通过时,是否自动滚动到第一个校验不通过的字段,平滑滚动或是瞬间直达。 值为空则表示不滚动。可选项:''/smooth/auto |
 | submitWithWarningMessage | bool? | false | 【讨论中】当校验结果只有告警信息时,是否触发 submit 提交事件 |
 
-```
-```
 
 ### TFormItem
 #### 默认构造方法
@@ -746,20 +744,20 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | additionInfo | String? | - | TInput的辅助信息 |
 | backgroundColor | Color? | - | 背景色 |
 | child | Widget? | - | 表单子组件 |
-| contentAlign | TextAlign? | - | 表单显示内容对齐方式: |
-| formItemNotifier |  | - |  |
+| contentAlign | TextAlign? | - | 表单显示内容对齐方式: left、right、top TODO: TStepper TRate 等组件没用实现通用性 |
+| formItemNotifier | FormItemNotifier? | - | - |
 | formRules | List? | - | 整个表单的校验规则 |
 | help | String? | - | TInput 默认显示文字 |
-| hintText | null | '' | 提示内容 |
+| hintText | - | '' | 提示内容 |
 | indicator | bool? | - | TTextarea 的属性,指示器 |
 | itemRule | List? | - | 表单项验证规则 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String? | - | 表单项标签左侧展示的内容 |
-| labelAlign | TextAlign? | - | TODO: item 标签对齐方式 |
+| labelAlign | TextAlign? | - | TODO: item 标签对齐方式 可选: left、right、top |
 | labelWidget | Widget? | - | 自定义标签 |
 | labelWidth | double? | - | 标签宽度,如果提供则覆盖Form的labelWidth |
 | name | String? | - | 表单字段名称 |
-| radios |  | - |  |
+| radios | Map? | - | - |
 | requiredMark | bool? | true | 是否显示必填标记(*) |
 | select | String | '' | 选择器 适用于日期选择器等 |
 | selectFn | Function? | - | 选择器方法 适用于日期选择器等 |
@@ -767,8 +765,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | tipAlign | TextAlign? | - | 组件提示内容对齐方式 |
 | type | TFormItemType | - | 表格单元需要使用的组件类型 |
 
-```
-```
 
 ### TFormValidation
 #### 默认构造方法
@@ -780,4 +776,22 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | validate | String? Function(dynamic) | - | 校验方法 |
 
 
+### TFormItemType
+#### 简介
+表格单元选用组件类型的枚举
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| input | - |
+| radios | - |
+| dateTimePicker | - |
+| cascader | - |
+| stepper | - |
+| rate | - |
+| textarea | - |
+| upLoadImg | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/image-viewer/README.md b/tdesign-site/src/image-viewer/README.md
index 4f159dc3e..6652845fc 100644
--- a/tdesign-site/src/image-viewer/README.md
+++ b/tdesign-site/src/image-viewer/README.md
@@ -75,10 +75,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showImageViewer |  |   required BuildContext context,  required List images,  List? labels,  bool? closeBtn,  bool? deleteBtn,  bool? showIndex,  bool? loop,  bool? autoplay,  int? duration,  Color? bgColor,  Color? navBarBgColor,  Color? iconColor,  TextStyle? labelStyle,  TextStyle? indexStyle,  Color? modalBarrierColor,  bool? barrierDismissible,  int? defaultIndex,  double? width,  double? height,  OnIndexChange? onIndexChange,  OnClose? onClose,  OnDelete? onDelete,  bool? ignoreDeleteError,  OnImageTap? onTap,  OnLongPress? onLongPress,  LeftItemBuilder? leftItemBuilder,  RightItemBuilder? rightItemBuilder, | 显示图片预览 |
+| showImageViewer | void | required BuildContext context, required List images, List? labels, bool? closeBtn, bool? deleteBtn, bool? showIndex, bool? loop, bool? autoplay, int? duration, Color? bgColor, Color? navBarBgColor, Color? iconColor, TextStyle? labelStyle, TextStyle? indexStyle, Color? modalBarrierColor, bool? barrierDismissible, int? defaultIndex, double? width, double? height, OnIndexChange? onIndexChange, OnClose? onClose, OnDelete? onDelete, bool? ignoreDeleteError, OnImageTap? onTap, OnLongPress? onLongPress, LeftItemBuilder? leftItemBuilder, RightItemBuilder? rightItemBuilder | 显示图片预览 |
 
-```
-```
 
 ### TImageViewerWidget
 #### 默认构造方法
@@ -96,7 +94,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | ignoreDeleteError | bool? | false | 是否忽略单张图片删除错误提示 |
 | images | List | - | 图片数组 |
 | indexStyle | TextStyle? | - | 页码样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labels | List? | - | 图片描述 |
 | labelStyle | TextStyle? | - | label文字样式 |
 | leftItemBuilder | LeftItemBuilder? | - | 左侧自定义操作 |
@@ -112,4 +110,60 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | width | double? | - | 图片宽度 |
 
 
+### OnIndexChange
+#### 类型定义
+
+```dart
+typedef OnIndexChange =  Function(int index);
+```
+
+
+### OnClose
+#### 类型定义
+
+```dart
+typedef OnClose =  Function(int index);
+```
+
+
+### OnDelete
+#### 类型定义
+
+```dart
+typedef OnDelete =  Function(int index);
+```
+
+
+### OnImageTap
+#### 类型定义
+
+```dart
+typedef OnImageTap =  Function(int index);
+```
+
+
+### OnLongPress
+#### 类型定义
+
+```dart
+typedef OnLongPress =  Function(int index);
+```
+
+
+### LeftItemBuilder
+#### 类型定义
+
+```dart
+typedef LeftItemBuilder = Widget Function(BuildContext context, int index);
+```
+
+
+### RightItemBuilder
+#### 类型定义
+
+```dart
+typedef RightItemBuilder = Widget Function(BuildContext context, int index);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/image/README.md b/tdesign-site/src/image/README.md
index 03464495a..ee95db5cb 100644
--- a/tdesign-site/src/image/README.md
+++ b/tdesign-site/src/image/README.md
@@ -1906,33 +1906,48 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| alignment |  | Alignment.center |  |
+| alignment | AlignmentGeometry | Alignment.center | - |
 | assetUrl | String? | - | 本地素材地址 |
-| cacheHeight |  | - |  |
-| cacheWidth |  | - |  |
-| centerSlice |  | - |  |
-| color |  | - |  |
-| colorBlendMode |  | - |  |
-| errorBuilder |  | - |  |
+| cacheHeight | int? | - | - |
+| cacheWidth | int? | - | - |
+| centerSlice | Rect? | - | - |
+| color | Color? | - | - |
+| colorBlendMode | BlendMode? | - | - |
+| errorBuilder | ImageErrorWidgetBuilder? | - | - |
 | errorWidget | Widget? | - | 失败自定义提示 |
-| excludeFromSemantics |  | false |  |
-| filterQuality |  | FilterQuality.low |  |
+| excludeFromSemantics | bool | false | - |
+| filterQuality | FilterQuality | FilterQuality.low | - |
 | fit | BoxFit? | - | 适配样式 |
 | frameBuilder | ImageFrameBuilder? | - | 以下系统Image属性,释义请参考系统[Image]中注释 |
-| gaplessPlayback |  | false |  |
+| gaplessPlayback | bool | false | - |
 | height | double? | - | 自定义高 |
 | imageFile | File? | - | 图片文件路径 |
 | imgUrl | String? | - | 图片地址 |
-| isAntiAlias |  | false |  |
-| key |  | - |  |
-| loadingBuilder |  | - |  |
+| isAntiAlias | bool | false | - |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| loadingBuilder | ImageLoadingBuilder? | - | - |
 | loadingWidget | Widget? | - | 加载自定义提示 |
-| matchTextDirection |  | false |  |
-| opacity |  | - |  |
-| repeat |  | ImageRepeat.noRepeat |  |
-| semanticLabel |  | - |  |
+| matchTextDirection | bool | false | - |
+| opacity | Animation? | - | - |
+| repeat | ImageRepeat | ImageRepeat.noRepeat | - |
+| semanticLabel | String? | - | - |
 | type | TImageType | TImageType.roundedSquare | 图片类型 |
 | width | double? | - | 自定义宽 |
 
 
+### TImageType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| clip | 裁剪 |
+| fitHeight | 适应高 |
+| fitWidth | 适应宽 |
+| stretch | 拉伸 |
+| square | 方形, |
+| roundedSquare | 圆角方形 |
+| circle | 圆形 |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/indexes/README.md b/tdesign-site/src/indexes/README.md
index 2d1d214f7..5deedd0b4 100644
--- a/tdesign-site/src/indexes/README.md
+++ b/tdesign-site/src/indexes/README.md
@@ -40,7 +40,7 @@ Widget _buildSimple(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               builderContent: (context, index) {
@@ -78,7 +78,7 @@ Widget _buildSimple(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               builderContent: (context, index) {
@@ -119,7 +119,7 @@ Widget _buildOther(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               capsuleTheme: true,
@@ -158,7 +158,7 @@ Widget _buildOther(BuildContext context) {
         context,
         options: TPopupOptions.right(
             width: 280,
-            margin: EdgeInsets.only(top: renderBox?.size.height ?? 0),
+            inset: TPopupRightInset(top: renderBox?.size.height ?? 0),
             child: TIndexes(
               indexList: indexList,
               capsuleTheme: true,
@@ -194,7 +194,7 @@ Widget _buildOther(BuildContext context) {
 | capsuleTheme | bool? | false | 锚点是否为胶囊式样式 |
 | indexList | List? | - | 索引字符列表。不传默认 A-Z |
 | indexListMaxHeight | double? | 0.8 | 索引列表最大高度(父容器高度的百分比,默认 0.8) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onChange | void Function(String index)? | - | 索引发生变更时触发事件 |
 | onSelect | void Function(String index)? | - | 点击侧边栏时触发事件 |
 | reverse | bool? | false | 反方向滚动置顶 |
@@ -202,8 +202,6 @@ Widget _buildOther(BuildContext context) {
 | sticky | bool? | true | 锚点是否吸顶 |
 | stickyOffset | double? | 0 | 锚点吸顶时与顶部的距离 |
 
-```
-```
 
 ### TIndexesAnchor
 #### 简介
@@ -215,12 +213,10 @@ Widget _buildOther(BuildContext context) {
 | activeIndex | ValueNotifier | - | 选中索引 |
 | builderAnchor | Widget? Function(BuildContext context, String index, bool isPinnedToTop)? | - | 索引锚点构建 |
 | capsuleTheme | bool | - | 是否为胶囊式样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | sticky | bool | - | 索引是否吸顶 |
 | text | String | - | 锚点文本 |
 
-```
-```
 
 ### TIndexesList
 #### 简介
@@ -233,7 +229,7 @@ Widget _buildOther(BuildContext context) {
 | builderIndex | Widget Function(BuildContext context, String index, bool isActive)? | - | 索引文本自定义构建,包括索引激活左侧提示 |
 | indexList | List | - | 索引字符列表。不传默认 A-Z |
 | indexListMaxHeight | double | 0.8 | 索引列表最大高度(父容器高度的百分比,默认0.8) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onSelect | void Function(String newIndex, String oldIndex) | - | 点击侧边栏时触发事件 |
 
 
diff --git a/tdesign-site/src/input/README.md b/tdesign-site/src/input/README.md
index 32958cd3d..c66dd4440 100644
--- a/tdesign-site/src/input/README.md
+++ b/tdesign-site/src/input/README.md
@@ -1042,7 +1042,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inputDecoration | InputDecoration? | - | 自定义输入框样式,默认圆角 |
 | inputFormatters | List? | - | 显示输入内容,如限制长度(LengthLimitingTextInputFormatter(6)) |
 | inputType | TextInputType? | - | 键盘类型,数字、字母 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelWidget | Widget? | - | leftLabel右侧组件,支持自定义 |
 | leftContentSpace | double? | - | 输入框内容左侧间距 |
 | leftIcon | Widget? | - | 带图标的输入框 |
@@ -1067,7 +1067,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | selectionControls | TextSelectionControls? | - | 自定义选择控制器 |
 | showBottomDivider | bool | true | 是否展示底部分割线 |
 | size | TInputSize | TInputSize.large | 输入框规格 |
-| spacer | TInputSpacer | - | 组件各模块间间距 |
+| spacer | TInputSpacer? | - | 组件各模块间间距 |
 | textAlign | TextAlign? | - | 文字对齐方向 |
 | textInputBackgroundColor | Color? | - | 文本框背景色 |
 | textStyle | TextStyle? | - | 文本颜色 |
@@ -1075,4 +1075,39 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | width | double? | - | 输入框宽度(TCardStyle时必须设置该参数) |
 
 
+### TInputType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| twoLine | - |
+| longText | - |
+| special | - |
+| normalMaxTwoLine | - |
+| cardStyle | - |
+
+
+### TInputSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | - |
+| large | - |
+
+
+### TCardStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| topText | - |
+| topTextWithBlueBorder | - |
+| errorStyle | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/link/README.md b/tdesign-site/src/link/README.md
index c4d51d609..de56f59f4 100644
--- a/tdesign-site/src/link/README.md
+++ b/tdesign-site/src/link/README.md
@@ -150,7 +150,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | color | Color? | - | link 文本的颜色,如果不设置则根据状态和风格进行计算 |
 | fontSize | double? | - | link 文本的字体大小,如果不设置则根据状态和风格进行计算 |
 | iconSize | double? | - | link icon 大小,如果不设置则根据状态和风格进行计算 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String | - | link 展示的文本 |
 | leftGapWithIcon | double? | - | 前置icon和文本之间的间隔,如果不设置则根据状态和风格进行计算 |
 | linkClick | LinkClick? | - | link 被点击之后所采取的动作,会将uri当做参数传入到该方法当中 |
@@ -164,4 +164,61 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | uri | Uri? | - | link 跳转的uri |
 
 
+### TLinkType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| basic | - |
+| withUnderline | - |
+| withPrefixIcon | - |
+| withSuffixIcon | - |
+
+
+### TLinkStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| primary | - |
+| defaultStyle | - |
+| danger | - |
+| warning | - |
+| success | - |
+
+
+### TLinkState
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| active | - |
+| disabled | - |
+
+
+### TLinkSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | - |
+| medium | - |
+| large | - |
+
+
+### LinkClick
+#### 简介
+限制Function类型,防止传递错误的Function,导致参数对不上
+#### 类型定义
+
+```dart
+typedef LinkClick =  Function(Uri? uri);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/loading/README.md b/tdesign-site/src/loading/README.md
index cf203e71c..519d3af70 100644
--- a/tdesign-site/src/loading/README.md
+++ b/tdesign-site/src/loading/README.md
@@ -260,11 +260,37 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | duration | int | 2000 | 一次刷新的时间,控制动画速度 |
 | icon | TLoadingIcon? | TLoadingIcon.circle | 图标,支持圆形、点状、菊花状 |
 | iconColor | Color? | - | 图标颜色 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | refreshWidget | Widget? | - | 失败刷新组件 |
 | size | TLoadingSize | - | 尺寸 |
 | text | String? | - | 文案 |
 | textColor | Color? | - | 文案颜色 |
 
 
+### TLoadingSize
+#### 简介
+Loading 尺寸
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | 小尺寸 |
+| medium | 中尺寸 |
+| large | 大尺寸 |
+
+
+### TLoadingIcon
+#### 简介
+Loading图标
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | 圆形 |
+| point | 点状 |
+| activity | 菊花状 |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/message/README.md b/tdesign-site/src/message/README.md
index 176eafef1..e573959b5 100644
--- a/tdesign-site/src/message/README.md
+++ b/tdesign-site/src/message/README.md
@@ -295,7 +295,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | content | String? | - | 通知内容 |
 | duration | int? | 3000 | 消息内置计时器 |
 | icon | dynamic | true | 自定义消息前面的图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | link | dynamic | - | 链接名称 |
 | marquee | MessageMarquee? | - | 跑马灯效果 |
 | offset | List? | - | 相对于 placement 的偏移量 |
@@ -310,10 +310,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showMessage |  |   required BuildContext context,  String? content,  bool? visible,  int? duration,  dynamic closeBtn,  dynamic icon,  dynamic link,  MessageMarquee? marquee,  List? offset,  MessageTheme? theme,  VoidCallback? onCloseBtnClick,  VoidCallback? onDurationEnd,  VoidCallback? onLinkClick, |  |
+| showMessage | void | required BuildContext context, String? content, bool? visible, int? duration, dynamic closeBtn, dynamic icon, dynamic link, MessageMarquee? marquee, List? offset, MessageTheme? theme, VoidCallback? onCloseBtnClick, VoidCallback? onDurationEnd, VoidCallback? onLinkClick | - |
 
-```
-```
 
 ### MessageMarquee
 #### 默认构造方法
@@ -324,8 +322,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | loop | int? | - | 循环次数 |
 | speed | int? | - | 速度 |
 
-```
-```
 
 ### MessageLink
 #### 默认构造方法
@@ -337,4 +333,18 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | uri | Uri? | - | 资源链接 |
 
 
+### MessageTheme
+#### 简介
+定义消息主题枚举
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| info | 普通通知 |
+| success | 成功通知 |
+| warning | 警示通知 |
+| error | 错误通知 |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/navbar/README.md b/tdesign-site/src/navbar/README.md
index 10d795948..a92a54be2 100644
--- a/tdesign-site/src/navbar/README.md
+++ b/tdesign-site/src/navbar/README.md
@@ -303,7 +303,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | centerTitle | bool | true | 标题是否居中 |
 | flexibleSpace | Widget? | - | 固定背景 |
 | height | double | 48 | 高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftBarItems | List? | - | 左边操作项 |
 | onBack | VoidCallback? | - | 返回事件 |
 | opacity | double | 1.0 | 透明度 |
@@ -320,8 +320,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | useBorderStyle | bool | false | 是否使用边框模式 |
 | useDefaultBack | bool | true | 是否使用默认的返回 |
 
-```
-```
 
 ### TNavBarItem
 #### 默认构造方法
@@ -334,7 +332,15 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | iconColor | Color? | - | 图标颜色 |
 | iconSize | double? | 24.0 | 图标尺寸 |
 | iconWidget | Widget? | - | 图标组件,优先级高于 icon |
-| padding | EdgeInsetsGeometry? | - |  |
+| padding | EdgeInsetsGeometry? | - | 内部填充 |
+
+
+### TBarItemAction
+#### 类型定义
+
+```dart
+typedef TBarItemAction = void Function();
+```
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/notice-bar/README.md b/tdesign-site/src/notice-bar/README.md
index f9b95f595..6ad02fea7 100644
--- a/tdesign-site/src/notice-bar/README.md
+++ b/tdesign-site/src/notice-bar/README.md
@@ -286,8 +286,6 @@ Widget _cardNoticeBar(BuildContext context) {
 
 ## API
 ### TNoticeBar
-#### 简介
-
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -297,7 +295,7 @@ Widget _cardNoticeBar(BuildContext context) {
 | direction | Axis? | Axis.horizontal | 滚动方向 |
 | height | double | 22 | 文字高度 (当使用prefixIcon或suffixIcon时,icon大小值等于该属性) |
 | interval | int? | 3000 | 步进滚动间隔时间(毫秒) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | left | Widget? | - | 左侧内容(自定义左侧内容,优先级高于prefixIcon) |
 | marquee | bool? | false | 跑马灯效果 |
 | maxLines | int? | 1 | 文本行数(仅静态有效) |
@@ -309,8 +307,6 @@ Widget _cardNoticeBar(BuildContext context) {
 | suffixIcon | IconData? | - | 右侧图标 |
 | theme | TNoticeBarTheme? | TNoticeBarTheme.info | 主题 |
 
-```
-```
 
 ### TNoticeBarStyle
 #### 简介
@@ -329,9 +325,41 @@ Widget _cardNoticeBar(BuildContext context) {
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TNoticeBarStyle.generateTheme  | 根据主题生成样式 |
+##### TNoticeBarStyle.generateTheme
+
+根据主题生成样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
+| theme | TNoticeBarTheme? | TNoticeBarTheme.info | - |
+
+
+### TNoticeBarType
+#### 简介
+公告栏类型
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| none | 静止(默认) |
+| scroll | 滚动 |
+| step | 步进 |
+
+
+### TNoticeBarTheme
+#### 简介
+公告栏主题
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| info | 信息(默认) |
+| success | 成功 |
+| warning | 警告 |
+| error | 错误 |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/picker/README.md b/tdesign-site/src/picker/README.md
index 4762ebb45..f061a540b 100644
--- a/tdesign-site/src/picker/README.md
+++ b/tdesign-site/src/picker/README.md
@@ -512,25 +512,23 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| cancel | Widget? | - | 工具栏左侧自定义插槽,默认使用 [TResourceDelegate.cancel] |
-| confirm | Widget? | - | 工具栏右侧自定义插槽,默认使用 [TResourceDelegate.confirm] |
+| cancel | Widget? | - | 工具栏左侧自定义插槽,默认使用 [TResourceDelegate.cancel] 可用于渲染图标、图标+文字组合等。点击事件依然由外层 [GestureDetector] 处理,触发 [onCancel] 回调——所以插槽内的 Widget 不需要自己处理点击。 ```dart // 简单改文字 TPicker( cancel: const Text('关闭'), onCancel: () => Navigator.of(context).pop(), ) // 带图标 TPicker( cancel: const Icon(Icons.close, size: 22), onCancel: () => Navigator.of(context).pop(), ) ``` |
+| confirm | Widget? | - | 工具栏右侧自定义插槽,默认使用 [TResourceDelegate.confirm] 可用于渲染图标、图标+文字组合等。点击事件依然由外层 [GestureDetector] 处理,触发 [onConfirm] 回调——所以插槽内的 Widget 不需要自己处理点击。 ```dart // 简单改文字 TPicker( confirm: const Text('确定'), onConfirm: (v) => Navigator.of(context).pop(v), ) // 带图标 TPicker( confirm: const Icon(Icons.check, size: 22), onConfirm: (v) => Navigator.of(context).pop(v), ) ``` |
 | disabled | bool | false | 是否禁用整个选择器(禁止滚动和操作),默认 false |
 | height | double | 200 | 视窗高度,默认 200 |
 | initialValue | List? | - | 初始选中值列表(按 value 匹配) |
 | itemBuilder | ItemBuilderType? | - | 自定义子项构建器(disabled 项仍由内部统一渲染,不会走此 builder) |
 | itemCount | int | 5 | 每屏显示 item 数,默认 5 |
 | itemDistanceCalculator | ItemDistanceCalculator? | - | 自定义距离计算器(控制颜色/字重/字号随"离中心距离"的变化) |
-| items | TPickerItems | - | 数据源(必填) |
-| key |  | - |  |
-| onCancel | VoidCallback? | - | 点击「取消」按钮回调 |
-| onChange | void Function(TPickerValue)? | - | 值改变回调(滚动时实时触发) |
-| onConfirm | void Function(TPickerValue)? | - | 点击「确认」按钮回调 |
-| onLoad | void Function(TPickerLoadEvent)? | - | 列选中项变化的事件回调 |
-| title | String? | - | 工具栏中部标题(可选,不传时中部留白) |
-| titleWidget | Widget? | - | 工具栏中部自定义标题插槽 |
+| items | TPickerItems | - | 数据源(必填) 使用密封类 [TPickerItems] 编译期强制二选一: - [TPickerColumns] → 多列独立选择 - [TPickerLinked] → 联动选择 自由结构数据通过 `.fromRaw()` 工厂构造归一化。 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onCancel | VoidCallback? | - | 点击「取消」按钮回调 仅作为点击事件通知,不携带任何参数。组件本身不会做任何 popup 操作,业务层可在此自行决定是否关闭弹窗、重置状态等。 |
+| onChange | void Function(TPickerValue)? | - | 值改变回调(滚动时实时触发) 触发时机: - 用户滚动经过某个 enabled 项并稳定时 - disabled 修正动画完成后,回调最终落点 **注意**:此回调代表"滚动时实时变化",不代表"用户已确认选择"。 如需"已确认"语义,请使用 [onConfirm]。 如需做网络请求/埋点等去抖处理,请在业务层自行 debounce。 |
+| onConfirm | void Function(TPickerValue)? | - | 点击「确认」按钮回调 携带当前选中的完整 [TPickerValue],包含: - `selectedOptions`: 当前选中的所有 [TPickerOption] - `values`: 各列选中项的 value 列表 - `labels`: 各列选中项的 label 列表 - `indexes`: 各列选中项的索引 与 [onChange] 不同——只有用户点击「确认」时才触发,代表"已确认选择"。 组件本身不会做任何 popup 操作,业务层可在此自行决定是否关闭弹窗、 提交表单等。 |
+| onLoad | void Function(TPickerLoadEvent)? | - | 列选中项变化的事件回调 **触发时机**:每次用户滚动到一个 enabled 项后都会触发(联动模式下还会 在新展开的列就位后触发)。组件本身不做"距底部多少项"的阈值判断——把 决策权交给业务层。 **事件参数**包含: - [TPickerLoadEvent.column]:触发列索引 - [TPickerLoadEvent.remaining]:当前列距底部剩余项数 - [TPickerLoadEvent.displayedCount]:当前列总项数 - [TPickerLoadEvent.parentValue]:联动模式下父级选中值(首列为 null) **典型用法**:业务层根据 [TPickerLoadEvent.remaining] 自行判断是否加载更多。 ```dart onLoad: (e) async { if (e.remaining > 5 \|\| _isLoading) return; // 距底部还远 / 已在加载,跳过 _isLoading = true; final more = await fetchMore(parent: e.parentValue); setState(() { _data.addAll(more); _isLoading = false; }); } ``` |
+| title | String? | - | 工具栏中部标题(可选,不传时中部留白) 顶部工具栏永远显示,包含「取消」「标题」「确认」三块。 用户点击「取消」触发 [onCancel],点击「确认」触发 [onConfirm]。 选择器与弹窗(popup)完全解耦——关闭/打开弹窗的逻辑由业务层在 这两个回调中自行控制。 典型用法(与 popup 弹窗组合): ```dart TPicker( items: items, title: '请选择地区', onCancel: () => setState(() => visible = false), onConfirm: (value) { setState(() { selected = value; visible = false; }); }, ) ``` |
+| titleWidget | Widget? | - | 工具栏中部自定义标题插槽 传入后会**完全替换**默认的 [title] 文字,可用于渲染更复杂的标题(副标题、图标+文字等)。 标题区域不响应点击。 |
 
-```
-```
 
 ### TPickerOption
 #### 默认构造方法
@@ -541,8 +539,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | label | String | - | 展示文字(可包含 emoji、单位、国际化等) |
 | value | dynamic | - | 业务值(onChange 回调返回此字段) |
 
-```
-```
 
 ### TPickerValue
 #### 默认构造方法
@@ -552,8 +548,13 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | indexes | List | - | 每列选中项的索引 |
 | selectedOptions | List | - | 每列选中的完整 option |
 
-```
-```
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| labels | List | - | 所有选中项的 label(展示用) 顺序与列顺序对应,可直接用于 UI 展示。 懒计算并缓存,生命周期内只计算一次。 |
+| values | List | - | 所有选中项的 value(提交表单用) 顺序与列顺序对应,可直接用于表单提交。 懒计算并缓存,生命周期内只计算一次。 |
+
 
 ### TPickerLoadEvent
 #### 默认构造方法
@@ -562,63 +563,71 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | --- | --- | --- | --- |
 | column | int | - | 触发事件的列索引(0 表示第一列) |
 | displayedCount | int | - | 当前列已展示的选项总数 |
-| parentValue | dynamic | - | 当前列的父级选中值(联动模式下使用) |
+| parentValue | dynamic | - | 当前列的父级选中值(联动模式下使用) 第一列时为 null;业务层可用此值从原始数据中筛选子级选项。 |
 | remaining | int | - | 距底部剩余的选项数(业务可用此值做"接近底部时加载"判断) |
 
-```
-```
 
 ### TPickerColumns
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| columns | List> | columns | 每列的选项列表 |
+| columns | List> | - | 每列的选项列表 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPickerColumns.fromRaw  | 从自由结构的 raw 数据创建,自动归一化
+##### TPickerColumns.fromRaw
+
+从自由结构的 raw 数据创建,自动归一化
 
  ```dart
  TPickerColumns.fromRaw(
    [['北京', '上海', '广州']],
    keys: const TPickerKeys(label: 'name', value: 'code'),
  )
- ``` |
+ ```
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| rawColumns | List | - | - |
+| keys | TPickerKeys | TPickerKeys.defaults | - |
 
-```
-```
 
 ### TPickerLinked
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| tree | Map | tree | 联动树结构:`Map` |
+| tree | Map | - | 联动树结构:`Map` value 可以是: - `Map` → 下一级联动 - `List` → 叶子级选项 |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPickerLinked.fromRaw  | 从自由结构的 raw Map 数据创建,自动归一化
+##### TPickerLinked.fromRaw
+
+从自由结构的 raw Map 数据创建,自动归一化
 
  ```dart
  TPickerLinked.fromRaw({
    '广东': {'深圳': ['南山', '福田'], '广州': ['天河']},
    '浙江': {'杭州': ['西湖']},
  })
- ``` |
+ ```
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| rawTree | Map | - | - |
+| keys | TPickerKeys | TPickerKeys.defaults | - |
 
-```
-```
 
 ### TPickerItems
-```
-```
+#### 简介
+选择器数据源密封类
+
+ 编译期强制二选一,消除运行时类型错误:
+ - [TPickerColumns] → 多列独立选择
+ - [TPickerLinked] → 联动选择
 
 ### TPickerKeys
 #### 默认构造方法
@@ -630,5 +639,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | label | String | 'label' | 展示文案对应的字段名,默认 `label` |
 | value | String | 'value' | 业务值对应的字段名,默认 `value` |
 
+#### 静态成员
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| defaults | TPickerKeys | - | 默认配置(`label / value / disabled / children`) |
+
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/popover/README.md b/tdesign-site/src/popover/README.md
index 60e8283db..7e41ef0b9 100644
--- a/tdesign-site/src/popover/README.md
+++ b/tdesign-site/src/popover/README.md
@@ -1276,21 +1276,15 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 ## API
 ### TPopover
-#### 简介
-
 
 #### 静态方法
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| showPopover |  |   required BuildContext context,  String? content,  Widget? contentWidget,  double offset,  TPopoverTheme? theme,  bool closeOnClickOutside,  TPopoverPlacement? placement,  bool? showArrow,  double arrowSize,  EdgeInsetsGeometry? padding,  double? width,  double? height,  Color? overlayColor,  OnTap? onTap,  OnLongTap? onLongTap,  BorderRadius? radius, |  |
+| showPopover | Future | required BuildContext context, String? content, Widget? contentWidget, double offset, TPopoverTheme? theme, bool closeOnClickOutside, TPopoverPlacement? placement, bool? showArrow, double arrowSize, EdgeInsetsGeometry? padding, double? width, double? height, Color? overlayColor, OnTap? onTap, OnLongTap? onLongTap, BorderRadius? radius | - |
 
-```
-```
 
 ### TPopoverWidget
-#### 简介
-
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -1300,7 +1294,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | contentWidget | Widget? | - | 自定义内容 |
 | context | BuildContext | - | 上下文 |
 | height | double? | - | 内容高度(包含padding,实际高度:height - paddingTop - paddingBottom) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | offset | double | 4 | 偏移 |
 | onLongTap | OnLongTap? | - | 长按事件 |
 | onTap | OnTap? | - | 点击事件 |
@@ -1312,4 +1306,54 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | width | double? | - | 内容宽度(包含padding,实际高度:height - paddingLeft - paddingRight) |
 
 
+### TPopoverTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| dark | 暗色 |
+| light | 亮色 |
+| info | 品牌色 |
+| success | 成功 |
+| warning | 警告 |
+| error | 错误 |
+
+
+### TPopoverPlacement
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| topLeft | 上左 |
+| top | 上 |
+| topRight | 上右 |
+| rightTop | 右上 |
+| right | 右 |
+| rightBottom | 右下 |
+| bottomRight | 下右 |
+| bottom | 下 |
+| bottomLeft | 下左 |
+| leftBottom | 左下 |
+| left | 左 |
+| leftTop | 左上 |
+
+
+### OnTap
+#### 类型定义
+
+```dart
+typedef OnTap =  Function(String? content);
+```
+
+
+### OnLongTap
+#### 类型定义
+
+```dart
+typedef OnLongTap =  Function(String? content);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index fb755c87a..5188ae3b6 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -217,10 +217,10 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           options: TPopupOptions.bottom(
               height: 280,
               cancelBuilder: (_, __) => TText(
-                '关闭',
-                textColor: TTheme.of(context).textColorSecondary,
-                font: TTheme.of(context).fontBodyLarge,
-              ),
+                    '关闭',
+                    textColor: TTheme.of(context).textColorSecondary,
+                    font: TTheme.of(context).fontBodyLarge,
+                  ),
               titleWidget: Row(
                 mainAxisSize: MainAxisSize.min,
                 children: [
@@ -235,11 +235,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
                 ],
               ),
               confirmBuilder: (_, __) => TText(
-                '完成',
-                textColor: TTheme.of(context).brandNormalColor,
-                font: TTheme.of(context).fontTitleMedium,
-                fontWeight: FontWeight.w600,
-              ),
+                    '完成',
+                    textColor: TTheme.of(context).brandNormalColor,
+                    font: TTheme.of(context).fontTitleMedium,
+                    fontWeight: FontWeight.w600,
+                  ),
               child: Container(height: 200)),
         );
       },
@@ -548,19 +548,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPopup._  |  |
-
+##### TPopup._
 
 #### 静态方法
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| show |  |   required BuildContext context,  required TPopupOptions options,  BuildContext? navigatorContext,  bool useRootNavigator, | 打开浮层并压入独立 [PopupRoute]。     [context] 用于查找 [Navigator] 并展示浮层。     [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。     返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、   [TPopupHandle.isShowing] 控制与查询。     重复调用会继续 push 新的浮层;若需互斥请在业务层管理。     [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。     [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
+| show | TPopupHandle | required BuildContext context, required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator | 打开浮层并压入独立 [PopupRoute]。 [context] 用于查找 [Navigator] 并展示浮层。 [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 [TPopupHandle.isShowing] 控制与查询。 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。 [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
 
-```
-```
 
 ### TPopupOptions
 #### 简介
@@ -579,10 +574,10 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
  | [TPopupPlacement] | 头部 / 关闭区 | 尺寸 |
  |-------------------|-------------|------|
-| [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[inset] |
+ | [TPopupPlacement.bottom] | [headerBuilder]、[titleWidget]、[cancelBuilder]、[confirmBuilder] | [height]、[inset] |
  | [TPopupPlacement.center] | [closeBuilder] | [width]、[height] |
-| [TPopupPlacement.top] | — | [height]、[inset] |
-| [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[inset] |
+ | [TPopupPlacement.top] | — | [height]、[inset] |
+ | [TPopupPlacement.left]、[TPopupPlacement.right] | — | [width]、[inset] |
 
  ## Builder 三态([headerBuilder]、[cancelBuilder]、[confirmBuilder]、[closeBuilder])
 
@@ -592,24 +587,24 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  | 显式 `null` | 隐藏该区域 |
  | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
 
-[titleWidget] 默认为 `null`,表示无标题内容。
+ [titleWidget] 默认为 `null`,表示无标题内容。
 
  生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 |
+| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
 | child | Widget | - | 浮层主体内容(必填)。 |
-| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 |
+| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
-| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 |
+| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
-| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
-| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 |
+| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
 | height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
-| inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 |
+| inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
 | onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -628,27 +623,151 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPopupOptions.bottom  | 创建 [TPopupPlacement.bottom] 配置。
+##### TPopupOptions.bottom
+
+创建 [TPopupPlacement.bottom] 配置。
 
  固定 [placement] 为 [TPopupPlacement.bottom];默认带内置头部。
- 蒙层、动画、生命周期等字段语义见同名成员文档。 |
-| TPopupOptions.center  | 创建 [TPopupPlacement.center] 配置。
+ 蒙层、动画、生命周期等字段语义见同名成员文档。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
+| inset | TPopupBottomInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
+| titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
+| cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
+| confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
- 固定 [placement] 为 [TPopupPlacement.center];默认展示面板外下方圆形关闭按钮。 |
-| TPopupOptions.left  | 创建 [TPopupPlacement.left] 配置。
 
- 固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。 |
-| TPopupOptions.right  | 创建 [TPopupPlacement.right] 配置。
+##### TPopupOptions.center
 
- 固定 [placement] 为 [TPopupPlacement.right];未传 [width] 时布局默认宽度 280。 |
-| TPopupOptions.top  | 创建 [TPopupPlacement.top] 配置。
+创建 [TPopupPlacement.center] 配置。
 
- 固定 [placement] 为 [TPopupPlacement.top];无内置头部。 |
+ 固定 [placement] 为 [TPopupPlacement.center];默认展示面板外下方圆形关闭按钮。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
+| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
+| closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
+
+
+##### TPopupOptions.left
+
+创建 [TPopupPlacement.left] 配置。
+
+ 固定 [placement] 为 [TPopupPlacement.left];未传 [width] 时布局默认宽度 280。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
+| inset | TPopupLeftInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
+
+
+##### TPopupOptions.right
+
+创建 [TPopupPlacement.right] 配置。
+
+ 固定 [placement] 为 [TPopupPlacement.right];未传 [width] 时布局默认宽度 280。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
+| inset | TPopupRightInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
+
+
+##### TPopupOptions.top
+
+创建 [TPopupPlacement.top] 配置。
+
+ 固定 [placement] 为 [TPopupPlacement.top];无内置头部。
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| child | Widget | - | 浮层主体内容(必填)。 |
+| height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
+| inset | TPopupTopInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| radius | double? | - | 内容区圆角,默认主题大圆角。 |
+| backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
+| overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
+| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
+| animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
+| onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
+| onOpened | VoidCallback? | - | 打开动画结束。 |
+| onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
+| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
+| onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
-```
-```
 
 ### TPopupHandle
 #### 简介
@@ -664,12 +783,67 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  handle.close();
  handle.open(); // 可省略 context,复用已缓存的 Navigator
  ```
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
+| options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
+| useRootNavigator | bool | - | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
+
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPopupHandle._  |  |
+##### TPopupHandle._
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
+| navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
+| useRootNavigator | bool | false | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
+
+
+### TPopupPlacement
+#### 简介
+浮层出现方向;决定 [TPopupOptions] 中哪些字段生效。
+
+ 与 [TPopupOptions] 类文档中的「字段与 placement」表对应。
+ 方向固定时请用 [TPopupOptions.bottom]、[TPopupOptions.center] 等命名工厂。
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| top | 自顶部滑入;使用 [TPopupOptions.height]、[TPopupOptions.inset]([TPopupTopInset])。 |
+| left | 自左侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.inset]([TPopupLeftInset])。 |
+| right | 自右侧滑入;使用 [TPopupOptions.width]、[TPopupOptions.inset]([TPopupRightInset])。 |
+| bottom | 自底部滑入;默认内置头部;使用 [TPopupOptions.height]、[TPopupOptions.inset]([TPopupBottomInset])。 |
+| center | 屏幕居中;使用 [TPopupOptions.closeBuilder] 控制面板外下方关闭区。 |
+
+
+### TPopupTrigger
+#### 简介
+浮层关闭或显隐变化时的触发来源。
+
+ 作为 [TPopupVisibleChangeCallback] 的第二个参数,以及关闭流程中的语义标记。
+
+ 内置控件会映射为 [TPopupTrigger.overlay]、[TPopupTrigger.cancel]、
+ [TPopupTrigger.confirm]、[TPopupTrigger.close];
+ [TPopupHandle.close] 为 [TPopupTrigger.api];系统返回为
+ [TPopupTrigger.systemBack];[headerBuilder] 内调用 `close` 等为
+ [TPopupTrigger.custom]。
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| overlay | 点击蒙层,且 [TPopupOptions.closeOnOverlayClick] 为 true。 |
+| cancel | 点击 bottom 取消语义槽位(含默认与自定义 [TPopupOptions.cancelBuilder])。 |
+| confirm | 点击 bottom 确认语义槽位(含默认与自定义 [TPopupOptions.confirmBuilder])。 |
+| close | 点击 center 关闭语义槽位(含默认与自定义 [TPopupOptions.closeBuilder])。 |
+| api | 外部 API 主动触发的显隐变化,如 [TPopupHandle.close] 或打开事件。 |
+| systemBack | 系统返回键或系统路由返回触发的关闭。 |
+| custom | 无框架预设动作语义的自定义关闭,如 [headerBuilder] 内调用 `close`。 |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/progress/README.md b/tdesign-site/src/progress/README.md
index 67c7a2a62..5c04f47bb 100644
--- a/tdesign-site/src/progress/README.md
+++ b/tdesign-site/src/progress/README.md
@@ -323,12 +323,12 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| animationDuration | int? | 300 | 动画持续时间(正整数,单位为毫秒) |
+| animationDuration | int | 300 | 动画持续时间(正整数,单位为毫秒) |
 | backgroundColor | Color? | - | 进度条背景颜色 |
 | circleRadius | double? | - | 环形进度条半径(正数) |
 | color | Color? | - | 进度条颜色 |
 | customProgressLabel | Widget? | - | 自定义标签 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | TLabelWidget? | - | 进度条标签 |
 | labelWidgetAlignment | Alignment? | - | 自定义标签对齐方式 |
 | labelWidgetWidth | double? | - | 自定义标签宽度 |
@@ -343,4 +343,39 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | value | double? | - | 进度值(0.0 到 1.0 之间的正数) |
 
 
+### TProgressType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| linear | - |
+| circular | - |
+| micro | - |
+| button | - |
+
+
+### TProgressLabelPosition
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| inside | - |
+| left | - |
+| right | - |
+
+
+### TProgressStatus
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| primary | - |
+| warning | - |
+| danger | - |
+| success | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/pull-down-refresh/README.md b/tdesign-site/src/pull-down-refresh/README.md
index d063b28cf..8bc3e0705 100644
--- a/tdesign-site/src/pull-down-refresh/README.md
+++ b/tdesign-site/src/pull-down-refresh/README.md
@@ -90,41 +90,41 @@ TDesign刷新头部
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | backgroundColor | Color? | - | 背景颜色 |
-| clamping |  | - |  |
+| clamping | bool? | - | - |
 | completeDuration | Duration? | - | 完成延时 |
 | enableHapticFeedback | bool | true | 开启震动反馈 |
 | enableInfiniteRefresh | bool | false | 是否开启无限刷新 |
 | extent | double? | 48.0 | Header容器高度 |
 | float | bool | false | 是否悬浮 |
-| frictionFactor |  | - |  |
-| hapticFeedback |  | - |  |
-| hitOver |  | - |  |
-| horizontalFrictionFactor |  | - |  |
-| horizontalReadySpringBuilder |  | - |  |
-| horizontalSpring |  | - |  |
-| infiniteHitOver |  | - |  |
+| frictionFactor | - | - | - |
+| hapticFeedback | bool? | - | - |
+| hitOver | - | - | - |
+| horizontalFrictionFactor | - | - | - |
+| horizontalReadySpringBuilder | - | - | - |
+| horizontalSpring | - | - | - |
+| infiniteHitOver | bool? | - | - |
 | infiniteOffset | double? | - | 无限刷新偏移量 |
 | key | Key? | - | Key |
-| listenable |  | - |  |
+| listenable | - | - | - |
 | loadingIcon | TLoadingIcon | TLoadingIcon.circle | loading样式 |
-| maxOverOffset |  | - |  |
-| notifyWhenInvisible |  | - |  |
+| maxOverOffset | - | - | - |
+| notifyWhenInvisible | - | - | - |
 | overScroll | bool | true | 越界滚动([enableInfiniteRefresh]为true或[infiniteOffset]有值时生效) |
-| position |  | - |  |
-| processedDuration |  | - |  |
-| readySpringBuilder |  | - |  |
-| safeArea |  | false |  |
-| secondaryCloseTriggerOffset |  | - |  |
-| secondaryDimension |  | - |  |
-| secondaryTriggerOffset |  | - |  |
-| secondaryVelocity |  | - |  |
-| spring |  | - |  |
-| springRebound |  | - |  |
+| position | - | - | - |
+| processedDuration | Duration? | - | - |
+| readySpringBuilder | - | - | - |
+| safeArea | - | false | - |
+| secondaryCloseTriggerOffset | - | - | - |
+| secondaryDimension | - | - | - |
+| secondaryTriggerOffset | - | - | - |
+| secondaryVelocity | - | - | - |
+| spring | - | - | - |
+| springRebound | - | - | - |
 | triggerDistance | double | 48.0 | 触发刷新任务的偏移量,同[triggerOffset] |
-| triggerOffset |  | - |  |
-| triggerWhenReach |  | - |  |
-| triggerWhenRelease |  | - |  |
-| triggerWhenReleaseNoWait |  | - |  |
+| triggerOffset | double? | - | - |
+| triggerWhenReach | - | - | - |
+| triggerWhenRelease | - | - | - |
+| triggerWhenReleaseNoWait | - | - | - |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/radio/README.md b/tdesign-site/src/radio/README.md
index 7464c515b..4c9136787 100644
--- a/tdesign-site/src/radio/README.md
+++ b/tdesign-site/src/radio/README.md
@@ -338,34 +338,32 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| backgroundColor |  | - |  |
-| cardMode |  | - |  |
-| checkBoxLeftSpace |  | - |  |
-| contentDirection |  | TContentDirection.right |  |
-| customContentBuilder |  | - |  |
-| customIconBuilder |  | - |  |
-| customSpace |  | - |  |
-| disableColor |  | - |  |
-| enable |  | true |  |
-| id |  | - |  |
-| insetSpacing |  | - |  |
-| key |  | - |  |
+| backgroundColor | Color? | - | - |
+| cardMode | bool? | - | - |
+| checkBoxLeftSpace | double? | - | - |
+| contentDirection | TContentDirection | TContentDirection.right | - |
+| customContentBuilder | ContentBuilder? | - | - |
+| customIconBuilder | IconBuilder? | - | - |
+| customSpace | EdgeInsetsGeometry? | - | - |
+| disableColor | Color? | - | - |
+| enable | bool | true | - |
+| id | String? | - | - |
+| insetSpacing | double? | - | - |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | radioStyle | TRadioStyle | TRadioStyle.circle | 单选框按钮样式 |
-| selectColor |  | - |  |
-| showDivider | bool | - | 是否显示下划线 |
-| size |  | TCheckBoxSize.small |  |
-| spacing |  | - |  |
-| subTitle |  | - |  |
-| subTitleColor |  | - |  |
-| subTitleFont |  | - |  |
-| subTitleMaxLine |  | 1 |  |
-| title |  | - |  |
-| titleColor |  | - |  |
-| titleFont |  | - |  |
-| titleMaxLine |  | 1 |  |
+| selectColor | Color? | - | - |
+| showDivider | bool? | - | - |
+| size | TCheckBoxSize | TCheckBoxSize.small | - |
+| spacing | double? | - | - |
+| subTitle | String? | - | - |
+| subTitleColor | Color? | - | - |
+| subTitleFont | Font? | - | - |
+| subTitleMaxLine | int | 1 | - |
+| title | String? | - | - |
+| titleColor | Color? | - | - |
+| titleFont | Font? | - | - |
+| titleMaxLine | int | 1 | - |
 
-```
-```
 
 ### TRadioGroup
 #### 简介
@@ -378,25 +376,45 @@ RadioGroup分组对象,继承自TCheckboxGroup,字段含义与父类一致
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| cardMode |  | false |  |
-| child |  | - |  |
-| contentDirection |  | - |  |
-| controller |  | - |  |
-| customContentBuilder |  | - |  |
-| customIconBuilder |  | - |  |
-| direction |  | - |  |
-| directionalTdRadios |  | - |  |
+| cardMode | bool | false | - |
+| child | Widget? | - | - |
+| contentDirection | TContentDirection? | - | - |
+| controller | TCheckboxGroupController? | - | - |
+| customContentBuilder | ContentBuilder? | - | - |
+| customIconBuilder | IconBuilder? | - | - |
+| direction | Axis? | - | - |
+| directionalTdRadios | List? | - | - |
 | divider | Widget? | - | 自定义下划线 |
-| key |  | - |  |
-| onRadioGroupChange |  | - |  |
-| passThrough |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| onRadioGroupChange | OnRadioGroupChange? | - | - |
+| passThrough | bool? | - | - |
 | radioCheckStyle | TRadioStyle? | - | 勾选样式 |
 | rowCount | int | 1 | 每行几列 |
-| selectId |  | - |  |
+| selectId | String? | - | - |
 | showDivider | bool | false | 是否显示下划线 |
-| spacing |  | - |  |
+| spacing | double? | - | - |
 | strictMode | bool | true | 严格模式下,用户不能取消勾选,只能切换选择项, |
-| titleMaxLine |  | - |  |
+| titleMaxLine | int? | - | - |
+
+
+### TRadioStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| circle | - |
+| square | - |
+| check | - |
+| hollowCircle | - |
+
+
+### OnRadioGroupChange
+#### 类型定义
+
+```dart
+typedef OnRadioGroupChange = void Function(String? selectedId);
+```
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/rate/README.md b/tdesign-site/src/rate/README.md
index f5472215c..5d53b6ba7 100644
--- a/tdesign-site/src/rate/README.md
+++ b/tdesign-site/src/rate/README.md
@@ -217,7 +217,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | allowHalf | bool? | false | 是否允许半选 |
-| builderText | Widget Function(BuildContext context, double value)? | - | 评分等级对应的辅助文字自定义构建,优先级高于[texts] |
+| builderText | Widget Function(BuildContext context, double value)? | - | 评分等级对应的辅助文字自定义构建,优先级高于[texts] 配置后,会忽略[texts],[textWidth],[iconTextGap] |
 | color | List? | - | 评分图标的颜色,示例:[选中颜色] / [选中颜色,未选中颜色],默认:[TTheme.of(context).warningColor5, TTheme.of(context).grayColor4] |
 | count | int? | 5 | 评分的数量 |
 | crossAxisAlignment | CrossAxisAlignment? | CrossAxisAlignment.center | 评分图标与辅助文字的交叉轴对齐方式 |
@@ -226,16 +226,27 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | gap | double? | - | 评分图标的间距,默认:TTheme.of(context).spacer8 |
 | icon | List? | - | 自定义评分图标,[选中和未选中图标] / [选中图标,未选中图标],默认:[TIcons.star_filled] |
 | iconTextGap | double? | - | 评分图标与辅助文字的间距,默认:[TTheme.of(context).spacer16] |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | mainAxisAlignment | MainAxisAlignment? | MainAxisAlignment.start | 评分图标与辅助文字的主轴对齐方式 |
 | mainAxisSize | MainAxisSize? | MainAxisSize.min | 评分图标与辅助文字主轴方向上如何占用空间 |
 | onChange | void Function(double value)? | - | 评分数改变时触发 |
 | placement | PlacementEnum? | PlacementEnum.top | 选择评分弹框的位置,值为[PlacementEnum.none]表示不显示评分弹框。 |
 | showText | bool? | false | 是否显示对应的辅助文字 |
 | size | double? | 24.0 | 评分图标的大小 |
-| texts | List? | const ['极差', '失望', '一般', '满意', '惊喜'] | 评分等级对应的辅助文字, |
+| texts | List? | const ['极差', '失望', '一般', '满意', '惊喜'] | 评分等级对应的辅助文字, 当[allowHalf]为false时长度应与[count]一致, 当[allowHalf]为true时长度应为[count]的两倍, 自定义值示例:['1分', '2分', '3分', '4分', '5分']。 |
 | textWidth | double? | 48.0 | 评分等级对应的辅助文字宽度 |
 | value | double? | 0 | 选择评分的值 |
 
 
+### PlacementEnum
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| none | - |
+| top | - |
+| bottom | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/result/README.md b/tdesign-site/src/result/README.md
index 47fa4faf5..9763982e9 100644
--- a/tdesign-site/src/result/README.md
+++ b/tdesign-site/src/result/README.md
@@ -290,10 +290,22 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | --- | --- | --- | --- |
 | description | String? | - | 描述文本,用于提供额外信息 |
 | icon | Widget? | - | 图标组件,用于在结果中显示一个图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | theme | TResultTheme | TResultTheme.defaultTheme | 主题样式,默认主题样式为defaultTheme |
 | title | String | '' | 标题文本,显示结果的主要信息,默认标题为空字符串 |
 | titleStyle | TextStyle? | - | 自定义字体样式,用于设置标题文本的样式 |
 
 
+### TResultTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | - |
+| success | - |
+| warning | - |
+| error | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/search/README.md b/tdesign-site/src/search/README.md
index 51146c4ee..93b973a62 100644
--- a/tdesign-site/src/search/README.md
+++ b/tdesign-site/src/search/README.md
@@ -130,7 +130,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | enabled | bool? | - | 是否禁用 |
 | focusNode | FocusNode? | - | 自定义焦点 |
 | inputAction | TextInputAction? | - | 键盘动作类型 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | mediumStyle | bool | false | 是否在导航栏中的样式 |
 | needCancel | bool | false | 是否需要取消按钮 |
 | onActionClick | TSearchBarEvent? | - | 自定义操作回调 |
@@ -146,4 +146,52 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | style | TSearchStyle? | TSearchStyle.square | 样式 |
 
 
+### TSearchStyle
+#### 简介
+搜索框的样式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| square | 方形 |
+| round | 圆形 |
+
+
+### TSearchAlignment
+#### 简介
+搜索框对齐方式
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | 默认头部对齐 |
+| center | 居中 |
+
+
+### TSearchBarEvent
+#### 类型定义
+
+```dart
+typedef TSearchBarEvent = void Function(String value);
+```
+
+
+### TSearchBarClearEvent
+#### 类型定义
+
+```dart
+typedef TSearchBarClearEvent = bool? Function(String value);
+```
+
+
+### TSearchBarCallBack
+#### 类型定义
+
+```dart
+typedef TSearchBarCallBack = void Function();
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/side-bar/README.md b/tdesign-site/src/side-bar/README.md
index 9e5e9cc99..b68ec1183 100644
--- a/tdesign-site/src/side-bar/README.md
+++ b/tdesign-site/src/side-bar/README.md
@@ -645,7 +645,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | controller | TSideBarController? | - | 控制器 |
 | defaultValue | int? | - | 默认值 |
 | height | double? | - | 高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | loading | bool? | - | 加载效果 |
 | loadingWidget | Widget? | - | 自定义加载动画 |
 | onChanged | ValueChanged? | - | 选中值发生变化(Controller控制) |
@@ -658,8 +658,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | unSelectedColor | Color? | - | 未选中颜色 |
 | value | int? | - | 选项值 |
 
-```
-```
 
 ### TSideBarItem
 #### 默认构造方法
@@ -669,10 +667,20 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | badge | TBadge? | - | 徽标 |
 | disabled | bool | false | 是否禁用 |
 | icon | IconData? | - | 图标 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String | '' | 标签 |
 | textStyle | TextStyle? | - | 标签样式 |
 | value | int | -1 | 值 |
 
 
+### TSideBarStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| outline | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/slider/README.md b/tdesign-site/src/slider/README.md
index 781aa3218..6e309ab97 100644
--- a/tdesign-site/src/slider/README.md
+++ b/tdesign-site/src/slider/README.md
@@ -772,19 +772,17 @@ onThumbTextTap
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | boxDecoration | Decoration? | - | 自定义盒子样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftLabel | String? | - | 左侧标签 |
-| onChanged | ValueChanged? | - | 滑动变化监听 |
-| onChangeEnd | ValueChanged? | - | 滑动结束监听 |
-| onChangeStart | ValueChanged? | - | 滑动开始监听 |
-| onTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击事件 位置、坐标、当前值 |
-| onThumbTextTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击浮标文字 位置、坐标、当前值 |
+| onChanged | ValueChanged? | - | 滑动变化监听 |
+| onChangeEnd | ValueChanged? | - | 滑动结束监听 |
+| onChangeStart | ValueChanged? | - | 滑动开始监听 |
+| onTap | Function(Offset offset, double value)? | - | Thumb 点击事件 坐标、当前值 |
+| onThumbTextTap | Function(Offset offset, double value)? | - | Thumb 点击浮标文字 坐标、当前值 |
 | rightLabel | String? | - | 右侧标签 |
 | sliderThemeData | TSliderThemeData? | - | 样式 |
-| value | RangeValues | - | 默认值 |
+| value | double | - | 默认值 |
 
-```
-```
 
 ### TRangeSlider
 #### 默认构造方法
@@ -792,16 +790,26 @@ onThumbTextTap
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
 | boxDecoration | Decoration? | - | 自定义盒子样式 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | leftLabel | String? | - | 左侧标签 |
 | onChanged | ValueChanged? | - | 滑动变化监听 |
 | onChangeEnd | ValueChanged? | - | 滑动结束监听 |
 | onChangeStart | ValueChanged? | - | 滑动开始监听 |
-| onTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击事件 位置、坐标、当前值 |
-| onThumbTextTap |  Function(Position position, Offset offset, double value)? | - | Thumb 点击浮标文字 位置、坐标、当前值 |
+| onTap | Function(Position position, Offset offset, double value)? | - | Thumb 点击事件 位置、坐标、当前值 |
+| onThumbTextTap | Function(Position position, Offset offset, double value)? | - | Thumb 点击浮标文字 位置、坐标、当前值 |
 | rightLabel | String? | - | 右侧标签 |
 | sliderThemeData | TSliderThemeData? | - | 样式 |
 | value | RangeValues | - | 默认值 |
 
 
+### Position
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| start | - |
+| end | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/stepper/README.md b/tdesign-site/src/stepper/README.md
index df9abaeb5..6c8d1f950 100644
--- a/tdesign-site/src/stepper/README.md
+++ b/tdesign-site/src/stepper/README.md
@@ -126,7 +126,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | disableInput | bool | false | 禁用输入框 |
 | eventController | StreamController? | - | 事件控制器 |
 | inputWidth | double? | - | 禁用全部操作 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | max | int | 100 | 最大值 |
 | min | int | 0 | 最小值 |
 | onBlur | VoidCallback? | - | 输入框失去焦点时触发 |
@@ -138,4 +138,71 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | value | int? | 0 | 值 |
 
 
+### TStepperSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | - |
+| medium | - |
+| large | - |
+
+
+### TStepperTheme
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| filled | - |
+| outline | - |
+
+
+### TStepperIconType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| remove | - |
+| add | - |
+
+
+### TStepperOverlimitType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| minus | - |
+| plus | - |
+
+
+### TStepperEventType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| cleanValue | - |
+
+
+### TStepperOverlimitFunction
+#### 类型定义
+
+```dart
+typedef TStepperOverlimitFunction = void Function(TStepperOverlimitType type);
+```
+
+
+### TTapFunction
+#### 类型定义
+
+```dart
+typedef TTapFunction = void Function();
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/steps/README.md b/tdesign-site/src/steps/README.md
index 2209c4d34..e23a2da3f 100644
--- a/tdesign-site/src/steps/README.md
+++ b/tdesign-site/src/steps/README.md
@@ -710,15 +710,13 @@ Vertical Customize Steps 垂直自定义步骤条
 | --- | --- | --- | --- |
 | activeIndex | int | 0 | 步骤条当前激活的索引 |
 | direction | TStepsDirection | TStepsDirection.horizontal | 步骤条方向 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | readOnly | bool | false | 步骤条readOnly模式 |
 | simple | bool | false | 步骤条simple模式 |
 | status | TStepsStatus | TStepsStatus.success | 步骤条状态 |
 | steps | List | - | 步骤条数据 |
 | verticalSelect | bool | false | 步骤条垂直自定义步骤条选择模式 |
 
-```
-```
 
 ### TStepsItemData
 #### 默认构造方法
@@ -733,4 +731,28 @@ Vertical Customize Steps 垂直自定义步骤条
 | title | String? | - | 标题 |
 
 
+### TStepsDirection
+#### 简介
+Steps步骤条方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| horizontal | - |
+| vertical | - |
+
+
+### TStepsStatus
+#### 简介
+steps步骤条状态
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| success | - |
+| error | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/swipe-cell/README.md b/tdesign-site/src/swipe-cell/README.md
index 4d30bc4bc..27316a636 100644
--- a/tdesign-site/src/swipe-cell/README.md
+++ b/tdesign-site/src/swipe-cell/README.md
@@ -346,16 +346,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | --- | --- | --- | --- |
 | cell | Widget | - | 单元格 [TCell] |
 | closeWhenOpened | bool? | true | 当同一组([groupTag])中的一个[TSwipeCell]打开时,是否关闭组中的所有其他[TSwipeCell] |
-| closeWhenTapped | bool? | true | 当同一组([groupTag])中的一个[TSwipeCell]被点击时,是否应该关闭组中的所有[TSwipeCell] |
+| closeWhenTapped | bool? | true | 当同一组([groupTag])中的一个[TSwipeCell]被点击时,是否应该关闭组中的所有[TSwipeCell] [cell]组件被点击时必须传递点击事件,执行`TSwipeCellInherited.of(context)?.cellClick()` |
 | controller | SlidableController? | - | 自定义控制滑动窗口 |
 | direction | Axis? | Axis.horizontal | 可拖动的方向 |
 | disabled | bool? | false | 是否禁用滑动 |
 | dragStartBehavior | DragStartBehavior? | DragStartBehavior.start | 处理拖动开始行为的方式[GestureDetector.dragStartBehavior] |
 | duration | Duration? | const Duration(milliseconds: 200) | 打开关闭动画时长 |
 | groupTag | Object? | - | 组,配置后,[closeWhenOpened]、[closeWhenTapped]才起作用 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | left | TSwipeCellPanel? | - | 左侧滑动操作项面板 |
-| onChange |  Function(TSwipeDirection direction, bool open)? | - | 滑动展开事件 |
+| onChange | Function(TSwipeDirection direction, bool open)? | - | 滑动展开事件 |
 | opened | List? | const [false, false] | 默认打开,[left, right] |
 | right | TSwipeCellPanel? | - | 右侧滑动操作项面板 |
 | slidableKey | Key? | - | 滑动组件的 Key |
@@ -365,8 +365,18 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| close |  |   required Object? tag,  SlidableController? current, | 根据[groupTag]关闭[TSwipeCell]     current:保留当前不关闭 |
-| of |  |   required BuildContext context, | 获取上下文最近的[controller] |
+| close | void | required Object? tag, SlidableController? current | 根据[groupTag]关闭[TSwipeCell] current:保留当前不关闭 |
+| of | SlidableController? | required BuildContext context | 获取上下文最近的[controller] |
+
+
+### TSwipeDirection
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| right | - |
+| left | - |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/swiper/README.md b/tdesign-site/src/swiper/README.md
index 87b5dda46..1d0d2ed98 100644
--- a/tdesign-site/src/swiper/README.md
+++ b/tdesign-site/src/swiper/README.md
@@ -274,13 +274,20 @@ TDesign风格的Swiper指示器样式,与flutter_swiper的Swiper结合使用
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| alignment | Alignment? | - | 当 scrollDirection== Axis.horizontal 时,默认Alignment.bottomCenter |
+| alignment | Alignment? | - | 当 scrollDirection== Axis.horizontal 时,默认Alignment.bottomCenter 当 scrollDirection== Axis.vertical 时,默认Alignment.centerRight |
 | builder | SwiperPlugin | TSwiperPagination.dots | 具体样式 |
-| key | Key? | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | margin | EdgeInsetsGeometry | const EdgeInsets.all(10.0) | 指示器和container之间的距离 |
 
-```
-```
+#### 静态成员
+
+| 名称 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| controls | SwiperPlugin | - | 箭头样式 |
+| dots | SwiperPlugin | - | 圆点样式 |
+| dotsBar | SwiperPlugin | - | 圆角矩形 + 圆点样式 默认宽度20,高度6 |
+| fraction | SwiperPlugin | - | 数字样式 |
+
 
 ### TPageTransformer
 #### 简介
@@ -296,10 +303,23 @@ TD默认PageTransformer
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TPageTransformer.margin  | 普通margin的卡片式 |
-| TPageTransformer.scaleAndFade  | 缩放或透明的卡片式 |
+##### TPageTransformer.margin
+
+普通margin的卡片式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| margin | double? | 6.0 | 左右间隔 |
+
+
+##### TPageTransformer.scaleAndFade
+
+缩放或透明的卡片式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| fade | double? | 1 | 淡化比例 |
+| scale | double? | 0.8 | 缩放比例 |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/switch/README.md b/tdesign-site/src/switch/README.md
index fa6258b24..a31fbc1cc 100644
--- a/tdesign-site/src/switch/README.md
+++ b/tdesign-site/src/switch/README.md
@@ -227,7 +227,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeText | String? | - | 关闭文案 |
 | enable | bool | true | 是否可点击 |
 | isOn | bool | false | 是否打开 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | onChanged | OnSwitchChanged? | - | 改变事件 |
 | openText | String? | - | 打开文案 |
 | size | TSwitchSize? | TSwitchSize.medium | 尺寸:大、中、小 |
@@ -240,4 +240,37 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | type | TSwitchType? | TSwitchType.fill | 类型:填充、文本、加载 |
 
 
+### TSwitchSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| medium | - |
+| small | - |
+
+
+### TSwitchType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| fill | - |
+| text | - |
+| loading | - |
+| icon | - |
+
+
+### OnSwitchChanged
+#### 简介
+开关改变事件处理
+#### 类型定义
+
+```dart
+typedef OnSwitchChanged = bool Function(bool value);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/tab-bar/README.md b/tdesign-site/src/tab-bar/README.md
index f17afd5e9..e60bc0c42 100644
--- a/tdesign-site/src/tab-bar/README.md
+++ b/tdesign-site/src/tab-bar/README.md
@@ -576,11 +576,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| basicType | TBottomTabBarBasicType | - | 基本样式(纯文本、纯图标、图标+文本) |
 | animationCurve | Curve | Curves.easeInOutCubic | 动画曲线 |
 | animationDuration | Duration | const Duration(milliseconds: 300) | 动画时长 |
 | backgroundColor | Color? | - | 背景颜色 (可选) |
 | barHeight | double? | _kDefaultTabBarHeight | tab高度 |
-| basicType | TBottomTabBarBasicType | basicType | 基本样式(纯文本、纯图标、图标+文本) |
 | centerDistance | double? | - | icon与文本中间距离(可选) |
 | componentType | TBottomTabBarComponentType? | TBottomTabBarComponentType.label | 选项样式 默认label |
 | currentIndex | int? | - | 选中的index(可选) |
@@ -588,7 +588,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | dividerHeight | double? | - | 分割线高度(可选) |
 | dividerThickness | double? | - | 分割线厚度(可选) |
 | indicatorAnimation | TBottomTabBarIndicatorAnimation | TBottomTabBarIndicatorAnimation.none | 指示器动画类型 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | navigationTabs | List | - | tabs配置 |
 | needInkWell | bool | false | 是否需要水波纹效果 |
 | outlineType | TBottomTabBarOutlineType? | TBottomTabBarOutlineType.filled | 标签栏样式 默认filled |
@@ -600,8 +600,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | useSafeArea | bool | true | 使用安全区域 |
 | useVerticalDivider | bool? | - | 是否使用竖线分隔(如果选项样式为 label,则强制为 false) |
 
-```
-```
 
 ### BadgeConfig
 #### 默认构造方法
@@ -613,8 +611,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | showBadge | bool | - | 是否展示消息 |
 | tBadge | TBadge? | - | 消息样式(未设置但 showBadge 为 true,则默认使用红点) |
 
-```
-```
 
 ### TBottomTabBarTabConfig
 #### 默认构造方法
@@ -632,8 +628,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | unselectedIcon | Widget? | - | 未选中时图标 |
 | unselectTabTextStyle | TextStyle? | - | 文本未选择样式 basicType为text时必填 |
 
-```
-```
 
 ### TBottomTabBarPopUpBtnConfig
 #### 默认构造方法
@@ -644,8 +638,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onChanged | ValueChanged | - | 统一在 onChanged 中处理各item点击事件 |
 | popUpDialogConfig | TBottomTabBarPopUpShapeConfig? | - | 弹窗UI配置 |
 
-```
-```
 
 ### TBottomTabBarPopUpShapeConfig
 #### 默认构造方法
@@ -659,8 +651,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | popUpWidth | double? | - | 弹窗宽度(不设置,默认为按钮宽度 - 20) |
 | radius | double? | - | panel圆角 默认0 |
 
-```
-```
 
 ### PopUpMenuItem
 #### 默认构造方法
@@ -669,8 +659,51 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | --- | --- | --- | --- |
 | alignment | AlignmentGeometry | AlignmentDirectional.center | 对齐方式 |
 | itemWidget | Widget? | - | 选项widget |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | value | String | - | 选项值 |
 
 
+### TBottomTabBarBasicType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| text | 单层级纯文本标签栏 |
+| iconText | 文本加图标标签栏 |
+| icon | 纯图标标签栏 |
+| expansionPanel | 双层级纯文本标签栏 |
+
+
+### TBottomTabBarComponentType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | 普通样式 |
+| label | 带胶囊背景的item选中样式 |
+
+
+### TBottomTabBarOutlineType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| filled | 填充样式 |
+| capsule | 胶囊样式 |
+
+
+### TBottomTabBarIndicatorAnimation
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| none | 无动画,瞬间切换 |
+| linear | 线性滑动:指示器匀速从一个 tab 滑到另一个 |
+| elastic | 弹性动画:指示器先拉伸后收缩 |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/table/README.md b/tdesign-site/src/table/README.md
index fc54bf5a3..8203fc29c 100644
--- a/tdesign-site/src/table/README.md
+++ b/tdesign-site/src/table/README.md
@@ -288,7 +288,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | empty | TTableEmpty? | - | 空表格呈现样式 |
 | footerWidget | Widget? | - | 自定义表尾 |
 | height | double? | - | 表格高度,超出后会出现滚动条 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | loading | bool? | false | 加载中状态 |
 | loadingWidget | Widget? | - | 自定义加载中状态 |
 | onCellTap | OnCellTap? | - | 单元格点击事件 |
@@ -300,8 +300,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | stripe | bool? | false | 斑马纹 |
 | width | double? | - | 表格宽度 |
 
-```
-```
 
 ### TTableCol
 #### 默认构造方法
@@ -321,8 +319,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | title | String? | - | 表头标题 |
 | width | double? | - | 列宽 |
 
-```
-```
 
 ### TTableEmpty
 #### 默认构造方法
@@ -333,4 +329,74 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | text | String? | - | 空状态文字 |
 
 
+### TTableColFixed
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| right | - |
+| none | - |
+
+
+### TTableColAlign
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| left | - |
+| center | - |
+| right | - |
+
+
+### SelectableFunc
+#### 类型定义
+
+```dart
+typedef SelectableFunc = bool Function(int index, dynamic row);
+```
+
+
+### RowCheckFunc
+#### 类型定义
+
+```dart
+typedef RowCheckFunc = bool Function(int index, dynamic row);
+```
+
+
+### OnCellTap
+#### 类型定义
+
+```dart
+typedef OnCellTap = void Function(int rowIndex, dynamic row, TTableCol col);
+```
+
+
+### OnScroll
+#### 类型定义
+
+```dart
+typedef OnScroll = void Function(ScrollController controller);
+```
+
+
+### OnSelect
+#### 类型定义
+
+```dart
+typedef OnSelect = void Function(List? data);
+```
+
+
+### OnRowSelect
+#### 类型定义
+
+```dart
+typedef OnRowSelect = void Function(int index, bool checked);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/tabs/README.md b/tdesign-site/src/tabs/README.md
index a484905e1..99da59326 100644
--- a/tdesign-site/src/tabs/README.md
+++ b/tdesign-site/src/tabs/README.md
@@ -325,24 +325,22 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | indicatorPadding | EdgeInsets? | - | 引导padding |
 | indicatorWidth | double? | - | tabBar下标宽度 |
 | isScrollable | bool | false | 是否滚动 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | labelColor | Color? | - | tabBar 已选标签颜色 |
 | labelPadding | EdgeInsetsGeometry? | - | tab间距 |
 | labelStyle | TextStyle? | - | 已选label字体 |
-| onTap |  Function(int)? | - | 点击事件 |
+| onTap | Function(int)? | - | 点击事件 |
 | outlineType | TTabBarOutlineType | TTabBarOutlineType.filled | 选项卡样式 |
 | physics | ScrollPhysics? | - | 自定义滑动 |
 | selectedBgColor | Color? | - | 被选中背景色,只有outlineType为capsule时有效 |
 | showIndicator | bool | false | 是否展示引导控件 |
-| tabAlignment |  | - |  |
+| tabAlignment | TabAlignment? | - | - |
 | tabs | List | - | tab数组 |
 | unSelectedBgColor | Color? | - | 未选中背景色,只有outlineType为capsule时有效 |
 | unselectedLabelColor | Color? | - | tabBar未选标签颜色 |
 | unselectedLabelStyle | TextStyle? | - | unselectedLabel字体 |
 | width | double? | - | tabBar宽度 |
 
-```
-```
 
 ### TTab
 #### 默认构造方法
@@ -356,14 +354,12 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | height | double? | - | tab高度 |
 | icon | Widget? | - | 图标 |
 | iconMargin | EdgeInsetsGeometry | const EdgeInsets.only(bottom: 4.0, right: 4.0) | 图标间距 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | outlineType | TTabOutlineType | TTabOutlineType.filled | 选项卡样式 |
 | size | TTabSize | TTabSize.small | 选项卡尺寸 |
 | text | String? | - | 文字内容 |
 | textMargin | EdgeInsetsGeometry? | - | 中间内容宽度 |
 
-```
-```
 
 ### TTabBarView
 #### 默认构造方法
@@ -373,7 +369,39 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | children | List | - | 子widget列表 |
 | controller | TabController? | - | 控制器 |
 | isSlideSwitch | bool | false | 是否可以滑动切换 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+
+
+### TTabSize
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| large | - |
+| small | - |
+
+
+### TTabOutlineType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| filled | 填充样式 |
+| capsule | 胶囊样式 |
+| card | 卡片 |
+
+
+### TTabBarOutlineType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| filled | 填充样式 |
+| capsule | 胶囊样式 |
+| card | 卡片 |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/tag/README.md b/tdesign-site/src/tag/README.md
index 172fa64f6..056f0bab4 100644
--- a/tdesign-site/src/tag/README.md
+++ b/tdesign-site/src/tag/README.md
@@ -507,6 +507,7 @@ Mark标签
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| text | String | - | 标签内容 |
 | backgroundColor | Color? | - | 背景颜色,优先级高于style的backgroundColor |
 | disable | bool | false | 是否为禁用状态 |
 | fixedWidth | double? | - | 标签的固定宽度 |
@@ -517,7 +518,7 @@ Mark标签
 | iconWidget | Widget? | - | 自定义图标内容,需自处理颜色 |
 | isLight | bool | false | 是否为浅色 |
 | isOutline | bool | false | 是否为描边类型,默认不是 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | needCloseIcon | bool | false | 关闭图标 |
 | onCloseTap | GestureTapCallback? | - | 关闭图标点击事件 |
 | overflow | TextOverflow? | - | 文字溢出处理 |
@@ -525,18 +526,16 @@ Mark标签
 | shape | TTagShape | TTagShape.square | 标签形状 |
 | size | TTagSize | TTagSize.medium | 标签大小 |
 | style | TTagStyle? | - | 标签样式 |
-| text | String | text | 标签内容 |
 | textColor | Color? | - | 文字颜色,优先级高于style的textColor |
 | theme | TTagTheme? | - | 主题 |
 
-```
-```
 
 ### TSelectTag
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| text | String | - | 标签内容 |
 | disableSelect | bool | false | 是否禁用选择 |
 | disableSelectStyle | TTagStyle? | - | 不可选标签样式 |
 | fixedWidth | double? | - | 标签的固定宽度 |
@@ -546,7 +545,7 @@ Mark标签
 | isLight | bool | false | 是否为浅色 |
 | isOutline | bool | false | 是否为描边类型,默认不是 |
 | isSelected | bool | false | 是否选中 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | needCloseIcon | bool | false | 关闭图标 |
 | onCloseTap | GestureTapCallback? | - | 关闭图标点击事件 |
 | onSelectChanged | ValueChanged? | - | 标签点击,选中状态改变时的回调 |
@@ -554,12 +553,9 @@ Mark标签
 | selectStyle | TTagStyle? | - | 选中的标签样式 |
 | shape | TTagShape | TTagShape.square | 标签形状 |
 | size | TTagSize | TTagSize.medium | 标签大小 |
-| text | String | text | 标签内容 |
 | theme | TTagTheme? | - | 主题 |
 | unSelectStyle | TTagStyle? | - | 未选中标签样式 |
 
-```
-```
 
 ### TTagStyle
 #### 默认构造方法
@@ -575,14 +571,92 @@ Mark标签
 | fontWeight | FontWeight? | - | 字体粗细 |
 | textColor | Color? | - | 文字颜色 |
 
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| closeIconColor | Color? | - | 关闭图标颜色 |
+
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TTagStyle.generateDisableSelectStyle  | 根据主题生成禁用Tag样式 |
-| TTagStyle.generateFillStyleByTheme  | 根据主题生成填充Tag样式 |
-| TTagStyle.generateOutlineStyleByTheme  | 根据主题生成描边Tag样式 |
+##### TTagStyle.generateDisableSelectStyle
+
+根据主题生成禁用Tag样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文,方便获取主题内容 |
+| isLight | bool | - | - |
+| isOutline | bool | - | - |
+| shape | TTagShape | - | - |
+
+
+##### TTagStyle.generateFillStyleByTheme
+
+根据主题生成填充Tag样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文,方便获取主题内容 |
+| theme | TTagTheme? | - | - |
+| light | bool | - | - |
+| shape | TTagShape | - | - |
+
+
+##### TTagStyle.generateOutlineStyleByTheme
+
+根据主题生成描边Tag样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文,方便获取主题内容 |
+| theme | TTagTheme? | - | - |
+| light | bool | - | - |
+| shape | TTagShape | - | - |
+
+
+### TTagTheme
+#### 简介
+Tag展示类型
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | 默认 |
+| primary | 常规 |
+| warning | 警告 |
+| danger | 危险 |
+| success | 成功 |
+
+
+### TTagSize
+#### 简介
+标签尺寸
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| extraLarge | - |
+| large | - |
+| medium | - |
+| small | - |
+| custom | - |
+
+
+### TTagShape
+#### 简介
+标签形状
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| square | - |
+| round | - |
+| mark | - |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/text/README.md b/tdesign-site/src/text/README.md
index 351b6c18e..a72595af3 100644
--- a/tdesign-site/src/text/README.md
+++ b/tdesign-site/src/text/README.md
@@ -199,8 +199,8 @@ TText.rich测试:
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| data | - | - | 以下系统 text 属性,释义请参考系统 [Text] 中注释 |
 | backgroundColor | Color? | - | 背景颜色 |
-| data | null | data | 以下系统 text 属性,释义请参考系统 [Text] 中注释 |
 | font | Font? | - | 字体尺寸,包含 大小size 和 行高height |
 | fontFamily | FontFamily? | - | 字体ttf |
 | fontFamilyUrl | String? | - | 是否禁用懒加载 FontFamily 的能力 |
@@ -208,66 +208,96 @@ TText.rich测试:
 | forceVerticalCenter | bool | false | 是否强制居中 |
 | isInFontLoader | bool | false | 是否在 FontLoader 中使用 |
 | isTextThrough | bool? | false | 是否是横线穿过样式(删除线) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | lineThroughColor | Color? | - | 删除线颜色,对应 TestStyle 的 decorationColor |
-| locale |  | - |  |
-| maxLines |  | - |  |
-| overflow |  | - |  |
+| locale | Locale? | - | - |
+| maxLines | int? | - | - |
+| overflow | TextOverflow? | - | - |
 | package | String? | - | 字体包名 |
-| semanticsLabel |  | - |  |
-| softWrap |  | - |  |
-| strutStyle |  | - |  |
+| semanticsLabel | String? | - | - |
+| softWrap | bool? | - | - |
+| strutStyle | StrutStyle? | - | - |
 | style | TextStyle? | - | 自定义的 TextStyle,其中指定的属性,将覆盖扩展的外层属性 |
-| textAlign |  | - |  |
+| textAlign | TextAlign? | - | - |
 | textColor | Color? | - | 文本颜色 |
-| textDirection |  | - |  |
-| textHeightBehavior |  | - |  |
-| textScaleFactor |  | - |  |
-| textWidthBasis |  | - |  |
+| textDirection | TextDirection? | - | - |
+| textHeightBehavior | ui.TextHeightBehavior? | - | - |
+| textScaleFactor | double? | - | - |
+| textWidthBasis | TextWidthBasis? | - | - |
+
+#### 公开属性
+
+| 属性 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| textSpan | InlineSpan? | - | - |
 
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TText.rich  | 富文本构造方法 |
+##### TText.rich
+
+富文本构造方法
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| textSpan | InlineSpan? | - | - |
+| font | Font? | - | 字体尺寸,包含 大小size 和 行高height |
+| fontWeight | FontWeight? | - | 字体粗细 |
+| fontFamily | FontFamily? | - | 字体ttf |
+| textColor | Color? | - | 文本颜色 |
+| backgroundColor | Color? | - | 背景颜色 |
+| isTextThrough | bool? | false | 是否是横线穿过样式(删除线) |
+| lineThroughColor | Color? | - | 删除线颜色,对应 TestStyle 的 decorationColor |
+| package | String? | - | 字体包名 |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
+| style | TextStyle? | - | 自定义的 TextStyle,其中指定的属性,将覆盖扩展的外层属性 |
+| strutStyle | StrutStyle? | - | - |
+| textAlign | TextAlign? | - | - |
+| textDirection | TextDirection? | - | - |
+| locale | Locale? | - | - |
+| softWrap | bool? | - | - |
+| overflow | TextOverflow? | - | - |
+| textScaleFactor | double? | - | - |
+| maxLines | int? | - | - |
+| semanticsLabel | String? | - | - |
+| textWidthBasis | TextWidthBasis? | - | - |
+| textHeightBehavior | ui.TextHeightBehavior? | - | - |
+| forceVerticalCenter | bool | false | 是否强制居中 |
+| isInFontLoader | bool | false | 是否在 FontLoader 中使用 |
+| fontFamilyUrl | String? | - | 是否禁用懒加载 FontFamily 的能力 |
 
-```
-```
 
 ### TTextSpan
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| children |  | - |  |
-| context |  | - |  |
-| font |  | - |  |
-| fontFamily |  | - |  |
-| fontWeight |  | - |  |
-| isTextThrough |  | false |  |
-| lineThroughColor |  | - |  |
-| mouseCursor |  | - |  |
-| onEnter |  | - |  |
-| onExit |  | - |  |
-| package |  | - |  |
-| recognizer |  | - |  |
-| semanticsLabel |  | - |  |
-| style |  | - |  |
-| text |  | - |  |
-| textColor |  | - |  |
+| children | List? | - | - |
+| context | BuildContext? | - | - |
+| font | Font? | - | - |
+| fontFamily | FontFamily? | - | - |
+| fontWeight | FontWeight? | - | - |
+| isTextThrough | bool? | false | - |
+| lineThroughColor | Color? | - | - |
+| mouseCursor | MouseCursor? | - | - |
+| onEnter | PointerEnterEventListener? | - | - |
+| onExit | PointerExitEventListener? | - | - |
+| package | String? | - | - |
+| recognizer | GestureRecognizer? | - | - |
+| semanticsLabel | String? | - | - |
+| style | TextStyle? | - | - |
+| text | String? | - | - |
+| textColor | Color? | - | - |
 
-```
-```
 
 ### TTextConfiguration
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| child |  | - |  |
+| child | Widget | - | - |
 | globalFontFamily | FontFamily? | - | 全局字体,kTextNeedGlobalFontFamily=true 时生效 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | paddingConfig | TTextPaddingConfig? | - | forceVerticalCenter=true 时,内置 padding 配置 |
 
 
diff --git a/tdesign-site/src/textarea/README.md b/tdesign-site/src/textarea/README.md
index 110a484af..2de7d101d 100644
--- a/tdesign-site/src/textarea/README.md
+++ b/tdesign-site/src/textarea/README.md
@@ -271,7 +271,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inputDecoration | InputDecoration? | - | 自定义输入框TextField组件样式 |
 | inputFormatters | List? | - | 显示输入内容,如限制长度(LengthLimitingTextInputFormatter(6)) |
 | inputType | TextInputType? | - | 键盘类型,数字、字母 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | label | String? | - | 输入框标题 |
 | labelIcon | Widget? | - | 输入框标题图标 |
 | labelStyle | TextStyle? | - | 左侧标签文本样式 |
@@ -298,4 +298,14 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | width | double? | - | 输入框宽度 |
 
 
+### TTextareaLayout
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| vertical | - |
+| horizontal | - |
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/time-counter/README.md b/tdesign-site/src/time-counter/README.md
index f7dec7c0a..d0eb22c69 100644
--- a/tdesign-site/src/time-counter/README.md
+++ b/tdesign-site/src/time-counter/README.md
@@ -620,9 +620,9 @@ TTimeCounter _buildCustomUnitLargeSize(BuildContext context) {
 | controller | TTimeCounterController? | - | 控制器,可控制开始/暂停/继续/重置 |
 | direction | TTimeCounterDirection | TTimeCounterDirection.down | 计时方向,默认倒计时 |
 | format | String | 'HH:mm:ss' | 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒(分隔符必须为长度为1的非空格的字符) |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | millisecond | bool | false | 是否开启毫秒级渲染 |
-| onChange |  Function(int time)? | - | 时间变化时触发回调 |
+| onChange | Function(int time)? | - | 时间变化时触发回调 |
 | onFinish | VoidCallback? | - | 计时结束时触发回调 |
 | size | TTimeCounterSize | TTimeCounterSize.medium | 尺寸 |
 | splitWithUnit | bool | false | 使用时间单位分割 |
@@ -630,14 +630,10 @@ TTimeCounter _buildCustomUnitLargeSize(BuildContext context) {
 | theme | TTimeCounterTheme | TTimeCounterTheme.defaultTheme | 风格 |
 | time | int | - | 必需;计时时长,单位毫秒 |
 
-```
-```
 
 ### TTimeCounterController
 #### 简介
 倒计时组件控制器,可控制开始(`start()`)/暂停(`pause()`)/继续(`resume()`)/重置(`reset([int? time])`)
-```
-```
 
 ### TTimeCounterStyle
 #### 简介
@@ -665,9 +661,69 @@ TTimeCounter _buildCustomUnitLargeSize(BuildContext context) {
 
 #### 工厂构造方法
 
-| 名称  | 说明 |
-| --- |  --- |
-| TTimeCounterStyle.generateStyle  | 生成默认样式 |
+##### TTimeCounterStyle.generateStyle
+
+生成默认样式
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| size | TTimeCounterSize? | - | - |
+| theme | TTimeCounterTheme? | - | - |
+| splitWithUnit | bool? | - | - |
+
+
+### TTimeCounterStatus
+#### 简介
+计时组件控制器转态
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| start | 开始 |
+| pause | 暂停 |
+| resume | 继续 |
+| reset | 重置 |
+| idle | 空,默认值 |
+
+
+### TTimeCounterDirection
+#### 简介
+计时组件计时方向
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| down | 倒计时 |
+| up | 正向计时 |
+
+
+### TTimeCounterSize
+#### 简介
+计时组件尺寸
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| small | 小 |
+| medium | 中等 |
+| large | 大 |
+
+
+### TTimeCounterTheme
+#### 简介
+计时组件风格
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| defaultTheme | 默认 |
+| round | 圆形 |
+| square | 方形 |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/toast/README.md b/tdesign-site/src/toast/README.md
index 4556b339e..51f750262 100644
--- a/tdesign-site/src/toast/README.md
+++ b/tdesign-site/src/toast/README.md
@@ -388,16 +388,26 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 名称 | 返回类型 | 参数 | 说明 |
 | --- | --- | --- | --- |
-| dismissAll |  |  | 关闭所有Toast |
-| dismissLoading |  |  | 关闭加载Toast(向后兼容) |
-| dismissToast |  |   required String toastId, | 关闭指定的Toast |
-| showFail |  |   required String? text,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 失败提示Toast |
-| showIconText |  |   required String? text,  IconData? icon,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 带图标的Toast |
-| showLoading |  |   required BuildContext context,  String? text,  Duration duration,  bool? preventTap,  Widget? customWidget,  Color? backgroundColor,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 带文案的加载Toast |
-| showLoadingWithoutText |  |   required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  double? iconSize,  Color? iconColor,  String? toastId, | 不带文案的加载Toast |
-| showSuccess |  |   required String? text,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 成功提示Toast |
-| showText |  |   required String? text,  required BuildContext context,  Duration duration,  int? maxLines,  BoxConstraints? constraints,  bool? preventTap,  Widget? customWidget,  Color? backgroundColor,  TextStyle? textStyle,  String? toastId, | 普通文本Toast |
-| showWarning |  |   required String? text,  IconTextDirection direction,  required BuildContext context,  Duration duration,  bool? preventTap,  Color? backgroundColor,  int? maxLines,  TextStyle? textStyle,  double? iconSize,  Color? iconColor,  String? toastId, | 警告Toast |
+| dismissAll | void | - | 关闭所有Toast |
+| dismissLoading | void | - | 关闭加载Toast(向后兼容) |
+| dismissToast | void | required String toastId | 关闭指定的Toast |
+| showFail | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 失败提示Toast |
+| showIconText | String | required String? text, IconData? icon, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带图标的Toast |
+| showLoading | String | required BuildContext context, String? text, Duration duration, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带文案的加载Toast |
+| showLoadingWithoutText | String | required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, double? iconSize, Color? iconColor, String? toastId | 不带文案的加载Toast |
+| showSuccess | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 成功提示Toast |
+| showText | String | required String? text, required BuildContext context, Duration duration, int? maxLines, BoxConstraints? constraints, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, String? toastId | 普通文本Toast |
+| showWarning | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 警告Toast |
+
+
+### IconTextDirection
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| horizontal | 横向 |
+| vertical | 竖向 |
 
 
   
\ No newline at end of file
diff --git a/tdesign-site/src/tree-select/README.md b/tdesign-site/src/tree-select/README.md
index dbb2ecb37..0ddcb23ca 100644
--- a/tdesign-site/src/tree-select/README.md
+++ b/tdesign-site/src/tree-select/README.md
@@ -193,15 +193,13 @@ String类型ID(问题3)
 | --- | --- | --- | --- |
 | defaultValue | List | const [] | 初始值,对应options中的value值 |
 | height | double | 336 | 高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | multiple | bool | false | 支持多选 |
 | onChange | TTreeSelectChangeEvent? | - | 选中值发生变化 |
 | options | List | const [] | 展示的选项列表 |
 | outwardCornerRadius | double | 9 | 一级菜单选中项的外弯折圆角半径,默认为 9 |
 | style | TTreeSelectStyle | TTreeSelectStyle.normal | 一级菜单样式 |
 
-```
-```
 
 ### TSelectOption
 #### 默认构造方法
@@ -216,4 +214,22 @@ String类型ID(问题3)
 | value | dynamic | - | 值 |
 
 
+### TTreeSelectStyle
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| normal | - |
+| outline | - |
+
+
+### TTreeSelectChangeEvent
+#### 类型定义
+
+```dart
+typedef TTreeSelectChangeEvent = void Function(List, int level);
+```
+
+
   
\ No newline at end of file
diff --git a/tdesign-site/src/upload/README.md b/tdesign-site/src/upload/README.md
index f0ae803a5..005a474df 100644
--- a/tdesign-site/src/upload/README.md
+++ b/tdesign-site/src/upload/README.md
@@ -187,7 +187,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | enabledReplaceType | bool? | false | 是否启用replace功能 |
 | files | List | - | 控制展示的文件列表 |
 | height | double? | 80.0 | 图片高度 |
-| key |  | - |  |
+| key | Key? | - | 组件标识,用于区分或保留组件状态。 |
 | max | int | 0 | 用于控制文件上传数量,0为不限制,仅在multiple为true时有效 |
 | mediaType | List | const [TUploadMediaType.image, TUploadMediaType.video] | 支持上传的文件类型,图片或视频 |
 | multiple | bool | false | 是否多选上传,默认false |
@@ -206,4 +206,89 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | wrapSpacing | double? | - | 多图布局时的 spacing |
 
 
+### TUploadMediaType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| image | - |
+| video | - |
+
+
+### TUploadValidatorError
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| overSize | - |
+| overQuantity | - |
+
+
+### TUploadFileStatus
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| success | - |
+| loading | - |
+| error | - |
+| retry | - |
+
+
+### TUploadType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| add | - |
+| remove | - |
+| replace | - |
+
+
+### TUploadBoxType
+#### 枚举值
+
+
+| 名称 | 说明 |
+| --- | --- |
+| roundedSquare | - |
+| circle | - |
+
+
+### TUploadErrorEvent
+#### 类型定义
+
+```dart
+typedef TUploadErrorEvent = void Function(Object e);
+```
+
+
+### TUploadClickEvent
+#### 类型定义
+
+```dart
+typedef TUploadClickEvent = void Function(int value);
+```
+
+
+### TUploadValueChangedEvent
+#### 类型定义
+
+```dart
+typedef TUploadValueChangedEvent = void Function(List files, TUploadType type);
+```
+
+
+### TUploadValidatorEvent
+#### 类型定义
+
+```dart
+typedef TUploadValidatorEvent = void Function(TUploadValidatorError e);
+```
+
+
   
\ No newline at end of file

From 0d9e8e596b6998dff5e0f6c511cb6aae259e1d5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Tue, 26 May 2026 14:25:42 +0800
Subject: [PATCH 21/27] refactor(popup): enhance interaction semantics and
 improve button handling in TPopup components

---
 .../example/lib/page/t_popup_page.dart        |  96 +++++---
 .../src/components/popup/_popup_header.dart   |  42 +++-
 .../src/components/popup/_popup_route.dart    |  26 ++-
 .../lib/src/components/popup/t_popup.dart     |   7 +-
 .../src/components/popup/t_popup_handle.dart  |  10 +-
 .../src/components/popup/t_popup_options.dart |   4 +-
 .../src/components/popup/t_popup_types.dart   |   2 +
 .../test/t_popup_route_test.dart              | 190 +++++++++++-----
 tdesign-component/test/t_popup_test.dart      | 210 +++++++++++++++++-
 tdesign-site/src/popup/README.md              |  61 +++--
 10 files changed, 520 insertions(+), 128 deletions(-)

diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart
index c7efcfe8e..a7351a29e 100644
--- a/tdesign-component/example/lib/page/t_popup_page.dart
+++ b/tdesign-component/example/lib/page/t_popup_page.dart
@@ -103,15 +103,31 @@ class TPopupPage extends StatelessWidget {
                   options: TPopupOptions.bottom(
                       height: 280,
                       titleWidget: TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'),
-                      cancelBuilder: (_, __) => TText(
-                            '点这里确认!',
-                            textColor: TTheme.of(context).brandNormalColor,
-                            font: TTheme.of(context).fontBodyLarge,
+                      cancelBuilder: (_, close) => GestureDetector(
+                            behavior: HitTestBehavior.opaque,
+                            onTap: close,
+                            child: Padding(
+                              padding:
+                                  const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                              child: TText(
+                                '点这里确认!',
+                                textColor: TTheme.of(context).brandNormalColor,
+                                font: TTheme.of(context).fontBodyLarge,
+                              ),
+                            ),
                           ),
-                      confirmBuilder: (_, __) => TText(
-                            '关闭',
-                            textColor: TTheme.of(context).errorNormalColor,
-                            font: TTheme.of(context).fontBodyLarge,
+                      confirmBuilder: (_, close) => GestureDetector(
+                            behavior: HitTestBehavior.opaque,
+                            onTap: close,
+                            child: Padding(
+                              padding:
+                                  const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                              child: TText(
+                                '关闭',
+                                textColor: TTheme.of(context).errorNormalColor,
+                                font: TTheme.of(context).fontBodyLarge,
+                              ),
+                            ),
                           ),
                       child: Container(height: 200)),
                 );
@@ -182,15 +198,31 @@ class TPopupPage extends StatelessWidget {
                           height: 280,
                           radius: 6,
                           titleWidget: TText('标题文字标题文字标题文字标题文字标题文字标题文字标题文字'),
-                          cancelBuilder: (_, __) => TText(
-                                '点这里确认!',
-                                textColor: TTheme.of(context).brandNormalColor,
-                                font: TTheme.of(context).fontBodyLarge,
+                          cancelBuilder: (_, close) => GestureDetector(
+                                behavior: HitTestBehavior.opaque,
+                                onTap: close,
+                                child: Padding(
+                                  padding: const EdgeInsets.symmetric(
+                                      horizontal: 4, vertical: 8),
+                                  child: TText(
+                                    '点这里确认!',
+                                    textColor: TTheme.of(context).brandNormalColor,
+                                    font: TTheme.of(context).fontBodyLarge,
+                                  ),
+                                ),
                               ),
-                          confirmBuilder: (_, __) => TText(
-                                '关闭',
-                                textColor: TTheme.of(context).errorNormalColor,
-                                font: TTheme.of(context).fontBodyLarge,
+                          confirmBuilder: (_, close) => GestureDetector(
+                                behavior: HitTestBehavior.opaque,
+                                onTap: close,
+                                child: Padding(
+                                  padding: const EdgeInsets.symmetric(
+                                      horizontal: 4, vertical: 8),
+                                  child: TText(
+                                    '关闭',
+                                    textColor: TTheme.of(context).errorNormalColor,
+                                    font: TTheme.of(context).fontBodyLarge,
+                                  ),
+                                ),
                               ),
                           child: Container(height: 200)),
                     );
@@ -492,10 +524,17 @@ class TPopupPage extends StatelessWidget {
           context,
           options: TPopupOptions.bottom(
               height: 280,
-              cancelBuilder: (_, __) => TText(
-                    '关闭',
-                    textColor: TTheme.of(context).textColorSecondary,
-                    font: TTheme.of(context).fontBodyLarge,
+              cancelBuilder: (_, close) => GestureDetector(
+                    behavior: HitTestBehavior.opaque,
+                    onTap: close,
+                    child: Padding(
+                      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                      child: TText(
+                        '关闭',
+                        textColor: TTheme.of(context).textColorSecondary,
+                        font: TTheme.of(context).fontBodyLarge,
+                      ),
+                    ),
                   ),
               titleWidget: Row(
                 mainAxisSize: MainAxisSize.min,
@@ -510,11 +549,18 @@ class TPopupPage extends StatelessWidget {
                   ),
                 ],
               ),
-              confirmBuilder: (_, __) => TText(
-                    '完成',
-                    textColor: TTheme.of(context).brandNormalColor,
-                    font: TTheme.of(context).fontTitleMedium,
-                    fontWeight: FontWeight.w600,
+              confirmBuilder: (_, close) => GestureDetector(
+                    behavior: HitTestBehavior.opaque,
+                    onTap: close,
+                    child: Padding(
+                      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                      child: TText(
+                        '完成',
+                        textColor: TTheme.of(context).brandNormalColor,
+                        font: TTheme.of(context).fontTitleMedium,
+                        fontWeight: FontWeight.w600,
+                      ),
+                    ),
                   ),
               child: Container(height: 200)),
         );
diff --git a/tdesign-component/lib/src/components/popup/_popup_header.dart b/tdesign-component/lib/src/components/popup/_popup_header.dart
index ba5acb1c5..06ec0c0b0 100644
--- a/tdesign-component/lib/src/components/popup/_popup_header.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_header.dart
@@ -62,10 +62,8 @@ class _DefaultHeader extends StatelessWidget {
         if (showCancel)
           Padding(
             padding: EdgeInsets.only(left: theme.spacer8),
-            child: Semantics(
-              button: true,
-              label: _cancelSemanticsLabel(context, options),
-              excludeSemantics: true,
+            child: _wrapDefaultCancelSemantics(
+              context: context,
               child: _buildCancel(context, theme),
             ),
           )
@@ -79,10 +77,8 @@ class _DefaultHeader extends StatelessWidget {
         if (showConfirm)
           Padding(
             padding: EdgeInsets.only(right: theme.spacer8),
-            child: Semantics(
-              button: true,
-              label: _confirmSemanticsLabel(context, options),
-              excludeSemantics: true,
+            child: _wrapDefaultConfirmSemantics(
+              context: context,
               child: _buildConfirm(context, theme),
             ),
           )
@@ -92,6 +88,36 @@ class _DefaultHeader extends StatelessWidget {
     );
   }
 
+  Widget _wrapDefaultCancelSemantics({
+    required BuildContext context,
+    required Widget child,
+  }) {
+    if (!_isPopupDefaultCancel(options.cancelBuilder)) {
+      return child;
+    }
+    return Semantics(
+      button: true,
+      label: _cancelSemanticsLabel(context, options),
+      excludeSemantics: true,
+      child: child,
+    );
+  }
+
+  Widget _wrapDefaultConfirmSemantics({
+    required BuildContext context,
+    required Widget child,
+  }) {
+    if (!_isPopupDefaultConfirm(options.confirmBuilder)) {
+      return child;
+    }
+    return Semantics(
+      button: true,
+      label: _confirmSemanticsLabel(context, options),
+      excludeSemantics: true,
+      child: child,
+    );
+  }
+
   Widget _titleWrap(BuildContext context, TThemeData theme, Widget child) {
     // 标题内容由用户插槽决定样式,这里只做布局约束。
     return DefaultTextStyle.merge(
diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart
index 0f6d6dead..00350dac5 100644
--- a/tdesign-component/lib/src/components/popup/_popup_route.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_route.dart
@@ -2,6 +2,9 @@ part of 't_popup.dart';
 
 /// 库内 [PopupRoute];由 [TPopupHandle.open] push,勿在外部直接构造。
 class _PopupNavigatorRoute extends PopupRoute {
+  static const ValueKey transparentInteractionBarrierKey =
+      ValueKey('tpopup-transparent-interaction-barrier');
+
   _PopupNavigatorRoute({
     required this.options,
     required this.onCloseWithTrigger,
@@ -49,7 +52,7 @@ class _PopupNavigatorRoute extends PopupRoute {
       options.showOverlay ? _barrierSemanticsLabel : null;
 
   @override
-  Color get barrierColor => Colors.transparent;
+  Color? get barrierColor => null;
 
   /// 非 opaque,避免透明区域露出 Modal 默认底色。
   @override
@@ -58,6 +61,14 @@ class _PopupNavigatorRoute extends PopupRoute {
   @override
   bool get maintainState => !options.destroyOnClose;
 
+  @override
+  Widget buildModalBarrier() {
+    // Popup 自己在 buildTransitions 里管理遮罩与透明交互层,
+    // 这里返回空节点,避免 PopupRoute 默认的 ModalBarrier
+    // 在 showOverlay=false 时仍偷偷阻断底层交互。
+    return const SizedBox.shrink();
+  }
+
   /// 关闭开始前统一入口:触发 [TPopupOptions.onClose]、[onVisibleChange](false, …)。
   void fireCloseStart(TPopupTrigger trigger) {
     if (_closeStartFired) {
@@ -130,7 +141,7 @@ class _PopupNavigatorRoute extends PopupRoute {
       children: [
         if (options.showOverlay) barrier,
         if (!options.showOverlay && options.preventScrollThrough)
-          _scrollBlocker(child: const SizedBox.expand()),
+          _buildTransparentInteractionBarrier(),
         positioned,
       ],
     );
@@ -153,16 +164,13 @@ class _PopupNavigatorRoute extends PopupRoute {
         child: barrier,
       );
     }
-    if (options.preventScrollThrough) {
-      barrier = _scrollBlocker(child: barrier);
-    }
     return barrier;
   }
 
-  Widget _scrollBlocker({required Widget child}) {
-    return NotificationListener(
-      onNotification: (_) => true,
-      child: child,
+  Widget _buildTransparentInteractionBarrier() {
+    return const AbsorbPointer(
+      key: transparentInteractionBarrierKey,
+      child: SizedBox.expand(),
     );
   }
 
diff --git a/tdesign-component/lib/src/components/popup/t_popup.dart b/tdesign-component/lib/src/components/popup/t_popup.dart
index eeb80bc93..4d175352d 100644
--- a/tdesign-component/lib/src/components/popup/t_popup.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup.dart
@@ -74,17 +74,12 @@ final class TPopup {
     bool useRootNavigator = false,
   }) {
     final navContext = navigatorContext ?? context;
-    final navigator = Navigator.of(
-      navContext,
-      rootNavigator: useRootNavigator,
-    );
-
     final handle = TPopupHandle._(
       options: options,
       navigatorContext: navigatorContext,
       useRootNavigator: useRootNavigator,
     );
-    handle.open(context);
+    handle.open(navContext);
     return handle;
   }
 }
diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
index 884d3d351..654351016 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
@@ -88,7 +88,10 @@ class TPopupHandle {
 
     navigator.push(route).whenComplete(() {
       _PopupTracker.remove(navigator, this);
-      _detachRoute();
+      final completedRoute = route;
+      if (completedRoute != null) {
+        _detachRoute(completedRoute);
+      }
     });
   }
 
@@ -144,7 +147,10 @@ class TPopupHandle {
     navigator.removeRoute(route, result);
   }
 
-  void _detachRoute() {
+  void _detachRoute(_PopupNavigatorRoute route) {
+    if (!identical(_route, route)) {
+      return;
+    }
     _isClosed = true;
     _route = null;
   }
diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart
index 986142979..921bfaf5d 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_options.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart
@@ -29,7 +29,7 @@ const Object _unset = Object();
 /// |----------|------|
 /// | 省略(使用默认值) | 渲染内置 UI |
 /// | 显式 `null` | 隐藏该区域 |
-/// | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
+/// | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;需自行提供交互与语义,可调用 `close` 关闭浮层 |
 ///
 /// [titleWidget] 默认为 `null`,表示无标题内容。
 ///
@@ -341,7 +341,7 @@ class TPopupOptions {
   /// 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。
   final double? overlayOpacity;
 
-  /// 是否拦截底层滚动;无蒙层时用透明层吸收滚动。
+  /// 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。
   final bool preventScrollThrough;
 
   /// 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。
diff --git a/tdesign-component/lib/src/components/popup/t_popup_types.dart b/tdesign-component/lib/src/components/popup/t_popup_types.dart
index b9d1e909f..668ccf7cc 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_types.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_types.dart
@@ -34,6 +34,8 @@ typedef TPopupHeaderBuilder = Widget Function(
 ///
 /// * [context] 构建上下文
 /// * [close] 关闭浮层;触发源与槽位语义保持一致
+///
+/// 自定义 builder 需自行提供交互与无障碍语义;框架仅为内置默认控件补充默认语义。
 typedef TPopupSlotBuilder = Widget Function(
   BuildContext context,
   VoidCallback close,
diff --git a/tdesign-component/test/t_popup_route_test.dart b/tdesign-component/test/t_popup_route_test.dart
index 9bcd156ed..5d52eecb0 100644
--- a/tdesign-component/test/t_popup_route_test.dart
+++ b/tdesign-component/test/t_popup_route_test.dart
@@ -1,5 +1,4 @@
 import 'package:flutter/material.dart';
-import 'package:flutter/rendering.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:tdesign_flutter/tdesign_flutter.dart';
 
@@ -10,71 +9,144 @@ void main() {
   tearDown(resetPopupTestResource);
 
   group('Popup 路由层行为(通过 TPopup.show 验证)', () {
-    testWidgets('蒙层 ScrollNotification 被拦截', (tester) async {
-      await openPopup(
-        tester,
-        onPressed: () {
-          TPopup.show(
-            tester.element(find.text('open')),
-            options: TPopupOptions(
-                placement: TPopupPlacement.bottom,
-                height: 120,
-                preventScrollThrough: true,
-                child: const SizedBox(height: 60)),
-          );
-        },
+    testWidgets('无蒙层 + preventScrollThrough=true 时阻断底层点击与滚动',
+        (tester) async {
+      final controller = ScrollController();
+      var backgroundTapCount = 0;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: TTheme(
+            data: TThemeData.defaultData(),
+            child: Scaffold(
+              body: Builder(
+                builder: (context) {
+                  return Column(
+                    children: [
+                      ElevatedButton(
+                        onPressed: () => backgroundTapCount++,
+                        child: const Text('background button'),
+                      ),
+                      Expanded(
+                        child: ListView.builder(
+                          controller: controller,
+                          itemCount: 30,
+                          itemBuilder: (_, index) => SizedBox(
+                            height: 48,
+                            child: Text('item-$index'),
+                          ),
+                        ),
+                      ),
+                      ElevatedButton(
+                        onPressed: () {
+                          TPopup.show(
+                            context,
+                            options: TPopupOptions.bottom(
+                              height: 120,
+                              showOverlay: false,
+                              preventScrollThrough: true,
+                              cancelBuilder: null,
+                              confirmBuilder: null,
+                              child: const SizedBox(height: 60),
+                            ),
+                          );
+                        },
+                        child: const Text('open popup'),
+                      ),
+                    ],
+                  );
+                },
+              ),
+            ),
+          ),
+        ),
       );
       await tester.pumpAndSettle();
 
-      var intercepted = false;
-      for (final element in tester.elementList(
-        find.byType(NotificationListener),
-      )) {
-        final widget =
-            element.widget as NotificationListener;
-        if (widget.onNotification?.call(
-              ScrollStartNotification(
-                metrics: FixedScrollMetrics(
-                  minScrollExtent: 0,
-                  maxScrollExtent: 100,
-                  pixels: 0,
-                  viewportDimension: 100,
-                  axisDirection: AxisDirection.down,
-                  devicePixelRatio: 1,
-                ),
-                context: element,
-              ),
-            ) ==
-            true) {
-          intercepted = true;
-          break;
-        }
-      }
-      expect(intercepted, isTrue);
+      final backgroundButtonCenter =
+          tester.getCenter(find.text('background button'));
+      final listDragStart = tester.getCenter(find.text('item-3'));
+
+      await tester.tap(find.text('open popup'));
+      await tester.pumpAndSettle();
+
+      await tester.tapAt(backgroundButtonCenter);
+      await tester.pump();
+      expect(backgroundTapCount, 0);
+
+      await tester.dragFrom(listDragStart, const Offset(0, -200));
+      await tester.pump();
+      expect(controller.offset, 0);
     });
 
-    testWidgets('无蒙层时透明层拦截滚动', (tester) async {
-      await openPopup(
-        tester,
-        onPressed: () {
-          TPopup.show(
-            tester.element(find.text('open')),
-            options: TPopupOptions(
-                placement: TPopupPlacement.bottom,
-                height: 120,
-                showOverlay: false,
-                preventScrollThrough: true,
-                cancelBuilder: null,
-                confirmBuilder: null,
-                child: const SizedBox(height: 60)),
-          );
-        },
+    testWidgets('无蒙层 + preventScrollThrough=false 时允许底层点击与滚动',
+        (tester) async {
+      final controller = ScrollController();
+      var backgroundTapCount = 0;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: TTheme(
+            data: TThemeData.defaultData(),
+            child: Scaffold(
+              body: Builder(
+                builder: (context) {
+                  return Column(
+                    children: [
+                      ElevatedButton(
+                        onPressed: () => backgroundTapCount++,
+                        child: const Text('background button'),
+                      ),
+                      Expanded(
+                        child: ListView.builder(
+                          controller: controller,
+                          itemCount: 30,
+                          itemBuilder: (_, index) => SizedBox(
+                            height: 48,
+                            child: Text('item-$index'),
+                          ),
+                        ),
+                      ),
+                      ElevatedButton(
+                        onPressed: () {
+                          TPopup.show(
+                            context,
+                            options: TPopupOptions.bottom(
+                              height: 120,
+                              showOverlay: false,
+                              preventScrollThrough: false,
+                              cancelBuilder: null,
+                              confirmBuilder: null,
+                              child: const SizedBox(height: 60),
+                            ),
+                          );
+                        },
+                        child: const Text('open popup'),
+                      ),
+                    ],
+                  );
+                },
+              ),
+            ),
+          ),
+        ),
       );
       await tester.pumpAndSettle();
-      expect(
-        find.byType(NotificationListener),
-        findsWidgets,
-      );
+
+      final backgroundButtonCenter =
+          tester.getCenter(find.text('background button'));
+      final listDragStart = tester.getCenter(find.text('item-3'));
+
+      await tester.tap(find.text('open popup'));
+      await tester.pumpAndSettle();
+
+      await tester.tapAt(backgroundButtonCenter);
+      await tester.pump();
+      expect(backgroundTapCount, 1);
+
+      await tester.dragFrom(listDragStart, const Offset(0, -200));
+      await tester.pump();
+      expect(controller.offset, greaterThan(0));
     });
 
     testWidgets('fireCloseStart 仅触发一次 onClose', (tester) async {
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index 3af4e78f0..d4578eb98 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -5,6 +5,19 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 import 'helpers/popup_test_helpers.dart';
 import 'helpers/popup_test_resource.dart';
 
+class _RecordingNavigatorObserver extends NavigatorObserver {
+  final List> pushedRoutes = >[];
+
+  @override
+  void didPush(Route route, Route? previousRoute) {
+    pushedRoutes.add(route);
+    super.didPush(route, previousRoute);
+  }
+
+  int get popupPushCount =>
+      pushedRoutes.where((route) => route is PopupRoute).length;
+}
+
 void main() {
   tearDown(resetPopupTestResource);
 
@@ -182,6 +195,131 @@ void main() {
     });
   });
 
+  group('TPopup Navigator 选择', () {
+    testWidgets('navigatorContext 首次 show 挂载到指定 Navigator,且 handle.open 复用缓存',
+        (tester) async {
+      final rootObserver = _RecordingNavigatorObserver();
+      final nestedObserver = _RecordingNavigatorObserver();
+      late BuildContext rootContext;
+      late BuildContext nestedContext;
+      late TPopupHandle handle;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          navigatorObservers: [rootObserver],
+          home: TTheme(
+            data: TThemeData.defaultData(),
+            child: Builder(
+              builder: (rootCtx) {
+                rootContext = rootCtx;
+                return Navigator(
+                  observers: [nestedObserver],
+                  onGenerateRoute: (_) {
+                    return MaterialPageRoute(
+                      builder: (nestedCtx) {
+                        nestedContext = nestedCtx;
+                        return Scaffold(
+                          body: ElevatedButton(
+                            onPressed: () {
+                              handle = TPopup.show(
+                                nestedContext,
+                                navigatorContext: rootContext,
+                                options: TPopupOptions.bottom(
+                                  height: 120,
+                                  cancelBuilder: null,
+                                  confirmBuilder: null,
+                                  child: const SizedBox(
+                                    height: 60,
+                                    child: Text('root popup'),
+                                  ),
+                                ),
+                              );
+                            },
+                            child: const Text('open via root'),
+                          ),
+                        );
+                      },
+                    );
+                  },
+                );
+              },
+            ),
+          ),
+        ),
+      );
+      await tester.pumpAndSettle();
+
+      await tester.tap(find.text('open via root'));
+      await tester.pumpAndSettle();
+      expect(rootObserver.popupPushCount, 1);
+      expect(nestedObserver.popupPushCount, 0);
+      expect(find.text('root popup'), findsOneWidget);
+
+      handle.close();
+      await tester.pumpAndSettle();
+      expect(find.text('root popup'), findsNothing);
+
+      handle.open();
+      await tester.pumpAndSettle();
+      expect(rootObserver.popupPushCount, 2);
+      expect(nestedObserver.popupPushCount, 0);
+      expect(find.text('root popup'), findsOneWidget);
+    });
+
+    testWidgets('useRootNavigator=true 会挂载到根 Navigator', (tester) async {
+      final rootObserver = _RecordingNavigatorObserver();
+      final nestedObserver = _RecordingNavigatorObserver();
+      late BuildContext nestedContext;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          navigatorObservers: [rootObserver],
+          home: TTheme(
+            data: TThemeData.defaultData(),
+            child: Navigator(
+              observers: [nestedObserver],
+              onGenerateRoute: (_) {
+                return MaterialPageRoute(
+                  builder: (ctx) {
+                    nestedContext = ctx;
+                    return Scaffold(
+                      body: ElevatedButton(
+                        onPressed: () {
+                          TPopup.show(
+                            nestedContext,
+                            useRootNavigator: true,
+                            options: TPopupOptions.center(
+                              width: 120,
+                              height: 80,
+                              closeBuilder: null,
+                              child: const SizedBox(
+                                width: 120,
+                                height: 80,
+                                child: Text('root navigator popup'),
+                              ),
+                            ),
+                          );
+                        },
+                        child: const Text('open root navigator'),
+                      ),
+                    );
+                  },
+                );
+              },
+            ),
+          ),
+        ),
+      );
+      await tester.pumpAndSettle();
+
+      await tester.tap(find.text('open root navigator'));
+      await tester.pumpAndSettle();
+      expect(rootObserver.popupPushCount, 1);
+      expect(nestedObserver.popupPushCount, 0);
+      expect(find.text('root navigator popup'), findsOneWidget);
+    });
+  });
+
   group('TPopup placement', () {
     const placements = [
       TPopupPlacement.top,
@@ -504,7 +642,7 @@ void main() {
       );
       await tester.pumpAndSettle();
       expect(
-        find.byType(NotificationListener),
+        find.byType(AbsorbPointer),
         findsWidgets,
       );
     });
@@ -714,6 +852,41 @@ void main() {
       expect(find.text('panel'), findsOneWidget);
     });
 
+    testWidgets('关闭动画未结束时重新 open 不会被旧 route 回调误清理', (tester) async {
+      late BuildContext hostContext;
+      TPopupHandle? handle;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          hostContext = tester.element(find.text('open'));
+          handle = TPopup.show(
+            hostContext,
+            options: TPopupOptions.bottom(
+              height: 100,
+              cancelBuilder: null,
+              confirmBuilder: null,
+              animationDuration: const Duration(milliseconds: 300),
+              child: const SizedBox(height: 60, child: Text('race panel')),
+            ),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      expect(handle!.isShowing, isTrue);
+
+      handle!.close();
+      await tester.pump(const Duration(milliseconds: 100));
+
+      handle!.open(hostContext);
+      await tester.pump();
+      expect(handle!.isShowing, isTrue);
+
+      await tester.pumpAndSettle();
+      expect(handle!.isShowing, isTrue);
+      expect(find.text('race panel'), findsOneWidget);
+    });
+
     testWidgets('handle.open 在已展示时无副作用', (tester) async {
       late BuildContext hostContext;
       TPopupHandle? handle;
@@ -1009,5 +1182,40 @@ void main() {
       expect(find.text('自定义取消'), findsOneWidget);
       expect(find.text('自定义确认'), findsOneWidget);
     });
+
+    testWidgets('自定义 cancelBuilder 保留业务侧语义', (tester) async {
+      final semanticsHandle = tester.ensureSemantics();
+      try {
+        await openPopup(
+          tester,
+          onPressed: () {
+            TPopup.show(
+              tester.element(find.text('open')),
+              options: TPopupOptions(
+                placement: TPopupPlacement.bottom,
+                height: 200,
+                confirmBuilder: null,
+                cancelBuilder: (_, close) => Semantics(
+                  container: true,
+                  label: '自定义取消语义',
+                  button: true,
+                  child: GestureDetector(
+                    onTap: close,
+                    child: const Text('自定义取消'),
+                  ),
+                ),
+                child: const SizedBox(height: 60),
+              ),
+            );
+          },
+        );
+        await tester.pumpAndSettle();
+        final semanticsNode = tester.getSemantics(find.text('自定义取消'));
+        expect(semanticsNode.label, contains('自定义取消语义'));
+        expect(semanticsNode.label, isNot('取消'));
+      } finally {
+        semanticsHandle.dispose();
+      }
+    });
   });
 }
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 5188ae3b6..b45b5db2b 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -216,10 +216,17 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           context,
           options: TPopupOptions.bottom(
               height: 280,
-              cancelBuilder: (_, __) => TText(
-                    '关闭',
-                    textColor: TTheme.of(context).textColorSecondary,
-                    font: TTheme.of(context).fontBodyLarge,
+              cancelBuilder: (_, close) => GestureDetector(
+                    behavior: HitTestBehavior.opaque,
+                    onTap: close,
+                    child: Padding(
+                      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                      child: TText(
+                        '关闭',
+                        textColor: TTheme.of(context).textColorSecondary,
+                        font: TTheme.of(context).fontBodyLarge,
+                      ),
+                    ),
                   ),
               titleWidget: Row(
                 mainAxisSize: MainAxisSize.min,
@@ -234,11 +241,18 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
                   ),
                 ],
               ),
-              confirmBuilder: (_, __) => TText(
-                    '完成',
-                    textColor: TTheme.of(context).brandNormalColor,
-                    font: TTheme.of(context).fontTitleMedium,
-                    fontWeight: FontWeight.w600,
+              confirmBuilder: (_, close) => GestureDetector(
+                    behavior: HitTestBehavior.opaque,
+                    onTap: close,
+                    child: Padding(
+                      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                      child: TText(
+                        '完成',
+                        textColor: TTheme.of(context).brandNormalColor,
+                        font: TTheme.of(context).fontTitleMedium,
+                        fontWeight: FontWeight.w600,
+                      ),
+                    ),
                   ),
               child: Container(height: 200)),
         );
@@ -556,6 +570,10 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | --- | --- | --- | --- |
 | show | TPopupHandle | required BuildContext context, required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator | 打开浮层并压入独立 [PopupRoute]。 [context] 用于查找 [Navigator] 并展示浮层。 [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 [TPopupHandle.isShowing] 控制与查询。 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。 [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
 
+嵌套导航场景下,若触发控件位于内层 `Navigator` 中:
+- 传 `navigatorContext` 可显式指定浮层挂载到哪个 `Navigator`
+- 仅需挂载到根路由时可直接使用 `useRootNavigator: true`
+
 
 ### TPopupOptions
 #### 简介
@@ -585,10 +603,12 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  |----------|------|
  | 省略(使用默认值) | 渲染内置 UI |
  | 显式 `null` | 隐藏该区域 |
- | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
+| 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;需自行提供交互与语义,可调用 `close` 关闭浮层 |
 
  [titleWidget] 默认为 `null`,表示无标题内容。
 
+自定义 builder 只负责替换内容,框架不会自动补点击行为;如果需要点击关闭,请在 builder 内绑定 `close`。
+
  生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 #### 默认构造方法
 
@@ -614,7 +634,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
 | titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
@@ -645,7 +665,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -674,7 +694,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -702,7 +722,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -730,7 +750,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -758,7 +778,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -787,11 +807,20 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 属性 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| isShowing | bool | - | 浮层是否仍在展示(路由在栈中且未进入关闭流程)。 |
 | navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
 | options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
 | useRootNavigator | bool | - | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
 
 
+#### 实例方法
+
+| 名称 | 返回类型 | 参数 | 说明 |
+| --- | --- | --- | --- |
+| open | void | BuildContext? context | 打开或重新打开浮层。首次调用须能解析 [Navigator](传入 [context] 或依赖 [navigatorContext]);后续可省略以复用缓存的 [NavigatorState]。 |
+| close | void | Object? result | 关闭当前展示的浮层;[TPopupOptions.onVisibleChange] 的 [TPopupTrigger] 为 [TPopupTrigger.api]。 |
+
+
 #### 工厂构造方法
 
 ##### TPopupHandle._

From 3b5a28a42d9390a2af9dd6653aa042104b05566c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Tue, 26 May 2026 14:51:24 +0800
Subject: [PATCH 22/27] refactor(popup): introduce modal behavior and update
 overlay handling in TPopup options

---
 .../code/popup._buildApiShowOverlayFalse.txt  |  4 +-
 .../example/lib/page/t_popup_page.dart        |  4 +-
 .../src/components/popup/_popup_route.dart    | 62 ++++++++++---------
 .../src/components/popup/t_popup_options.dart | 49 +++++++++------
 .../test/t_popup_coverage_test.dart           |  7 ++-
 .../test/t_popup_options_test.dart            | 26 ++++++++
 .../test/t_popup_route_test.dart              | 10 +--
 tdesign-component/test/t_popup_test.dart      |  7 ++-
 tdesign-site/src/popup/README.md              | 26 +++++---
 9 files changed, 131 insertions(+), 64 deletions(-)

diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
index c7e2cf728..adc6d1a52 100644
--- a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
+++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
@@ -12,7 +12,9 @@
           options: TPopupOptions.bottom(
               height: 280,
               showOverlay: false,
-              // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口)
+              closeOnOverlayClick: false,
+              modal: true,
+              // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
               titleWidget: const TText('无蒙层'),
               child: Container(
                 height: 200,
diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart
index a7351a29e..ee7fcc4b1 100644
--- a/tdesign-component/example/lib/page/t_popup_page.dart
+++ b/tdesign-component/example/lib/page/t_popup_page.dart
@@ -670,7 +670,9 @@ class TPopupPage extends StatelessWidget {
           options: TPopupOptions.bottom(
               height: 280,
               showOverlay: false,
-              // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口)
+              closeOnOverlayClick: false,
+              modal: true,
+              // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
               titleWidget: const TText('无蒙层'),
               child: Container(
                 height: 200,
diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart
index 00350dac5..cde58d72f 100644
--- a/tdesign-component/lib/src/components/popup/_popup_route.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_route.dart
@@ -2,9 +2,6 @@ part of 't_popup.dart';
 
 /// 库内 [PopupRoute];由 [TPopupHandle.open] push,勿在外部直接构造。
 class _PopupNavigatorRoute extends PopupRoute {
-  static const ValueKey transparentInteractionBarrierKey =
-      ValueKey('tpopup-transparent-interaction-barrier');
-
   _PopupNavigatorRoute({
     required this.options,
     required this.onCloseWithTrigger,
@@ -26,6 +23,15 @@ class _PopupNavigatorRoute extends PopupRoute {
   bool _closeStartFired = false;
   String? _barrierSemanticsLabel;
 
+  _PopupBarrierMode get _barrierMode {
+    if (!options.modal) {
+      return _PopupBarrierMode.nonModal;
+    }
+    return options.showOverlay
+        ? _PopupBarrierMode.modalOverlay
+        : _PopupBarrierMode.modalTransparent;
+  }
+
   Color get _barrierColor {
     if (!options.showOverlay) {
       return Colors.transparent;
@@ -63,10 +69,24 @@ class _PopupNavigatorRoute extends PopupRoute {
 
   @override
   Widget buildModalBarrier() {
-    // Popup 自己在 buildTransitions 里管理遮罩与透明交互层,
-    // 这里返回空节点,避免 PopupRoute 默认的 ModalBarrier
-    // 在 showOverlay=false 时仍偷偷阻断底层交互。
-    return const SizedBox.shrink();
+    if (_barrierMode == _PopupBarrierMode.nonModal) {
+      return const SizedBox.shrink();
+    }
+    if (_barrierMode == _PopupBarrierMode.modalOverlay &&
+        options.closeOnOverlayClick) {
+      return ModalBarrier(
+        color: Colors.transparent,
+        dismissible: true,
+        onDismiss: _handleOverlayTap,
+        semanticsLabel: _resolveBarrierSemanticsLabel(navigator!.context),
+        barrierSemanticsDismissible: true,
+      );
+    }
+    return ModalBarrier(
+      color: Colors.transparent,
+      dismissible: false,
+      barrierSemanticsDismissible: false,
+    );
   }
 
   /// 关闭开始前统一入口:触发 [TPopupOptions.onClose]、[onVisibleChange](false, …)。
@@ -101,10 +121,6 @@ class _PopupNavigatorRoute extends PopupRoute {
       reverseCurve: Curves.easeOut,
     );
 
-    if (options.showOverlay) {
-      _barrierSemanticsLabel ??=
-          MaterialLocalizations.of(context).modalBarrierDismissLabel;
-    }
     _layout = PopupLayout(
       placement: options.placement,
       inset: options.inset,
@@ -139,16 +155,14 @@ class _PopupNavigatorRoute extends PopupRoute {
     return Stack(
       fit: StackFit.expand,
       children: [
-        if (options.showOverlay) barrier,
-        if (!options.showOverlay && options.preventScrollThrough)
-          _buildTransparentInteractionBarrier(),
+        if (_barrierMode == _PopupBarrierMode.modalOverlay) barrier,
         positioned,
       ],
     );
   }
 
   Widget _buildBarrier(BuildContext context, double t) {
-    Widget barrier = GestureDetector(
+    return GestureDetector(
       behavior: HitTestBehavior.opaque,
       onTap: _handleOverlayTap,
       child: Container(
@@ -157,21 +171,11 @@ class _PopupNavigatorRoute extends PopupRoute {
         ),
       ),
     );
-    if (options.showOverlay) {
-      barrier = Semantics(
-        label: _barrierSemanticsLabel!,
-        button: true,
-        child: barrier,
-      );
-    }
-    return barrier;
   }
 
-  Widget _buildTransparentInteractionBarrier() {
-    return const AbsorbPointer(
-      key: transparentInteractionBarrierKey,
-      child: SizedBox.expand(),
-    );
+  String _resolveBarrierSemanticsLabel(BuildContext context) {
+    return _barrierSemanticsLabel ??=
+        MaterialLocalizations.of(context).modalBarrierDismissLabel;
   }
 
   void _handleOverlayTap() {
@@ -234,3 +238,5 @@ class _PopupNavigatorRoute extends PopupRoute {
     super.dispose();
   }
 }
+
+enum _PopupBarrierMode { modalOverlay, modalTransparent, nonModal }
diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart
index 921bfaf5d..33b1f34a6 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_options.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart
@@ -50,7 +50,7 @@ class TPopupOptions {
     this.closeOnOverlayClick = true,
     this.overlayColor,
     this.overlayOpacity,
-    this.preventScrollThrough = true,
+    this.modal = true,
     this.destroyOnClose = false,
     this.animationDuration = const Duration(milliseconds: 240),
     this.headerBuilder = _kPopupDefaultHeader,
@@ -84,7 +84,7 @@ class TPopupOptions {
     bool closeOnOverlayClick = true,
     Color? overlayColor,
     double? overlayOpacity,
-    bool preventScrollThrough = true,
+    bool modal = true,
     bool destroyOnClose = false,
     Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
@@ -109,7 +109,7 @@ class TPopupOptions {
         closeOnOverlayClick: closeOnOverlayClick,
         overlayColor: overlayColor,
         overlayOpacity: overlayOpacity,
-        preventScrollThrough: preventScrollThrough,
+        modal: modal,
         destroyOnClose: destroyOnClose,
         animationDuration: animationDuration,
         onOpen: onOpen,
@@ -134,7 +134,7 @@ class TPopupOptions {
     bool closeOnOverlayClick = true,
     Color? overlayColor,
     double? overlayOpacity,
-    bool preventScrollThrough = true,
+    bool modal = true,
     bool destroyOnClose = false,
     Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
@@ -156,7 +156,7 @@ class TPopupOptions {
         closeOnOverlayClick: closeOnOverlayClick,
         overlayColor: overlayColor,
         overlayOpacity: overlayOpacity,
-        preventScrollThrough: preventScrollThrough,
+        modal: modal,
         destroyOnClose: destroyOnClose,
         animationDuration: animationDuration,
         onOpen: onOpen,
@@ -180,7 +180,7 @@ class TPopupOptions {
     bool closeOnOverlayClick = true,
     Color? overlayColor,
     double? overlayOpacity,
-    bool preventScrollThrough = true,
+    bool modal = true,
     bool destroyOnClose = false,
     Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
@@ -201,7 +201,7 @@ class TPopupOptions {
         closeOnOverlayClick: closeOnOverlayClick,
         overlayColor: overlayColor,
         overlayOpacity: overlayOpacity,
-        preventScrollThrough: preventScrollThrough,
+        modal: modal,
         destroyOnClose: destroyOnClose,
         animationDuration: animationDuration,
         onOpen: onOpen,
@@ -225,7 +225,7 @@ class TPopupOptions {
     bool closeOnOverlayClick = true,
     Color? overlayColor,
     double? overlayOpacity,
-    bool preventScrollThrough = true,
+    bool modal = true,
     bool destroyOnClose = false,
     Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
@@ -246,7 +246,7 @@ class TPopupOptions {
         closeOnOverlayClick: closeOnOverlayClick,
         overlayColor: overlayColor,
         overlayOpacity: overlayOpacity,
-        preventScrollThrough: preventScrollThrough,
+        modal: modal,
         destroyOnClose: destroyOnClose,
         animationDuration: animationDuration,
         onOpen: onOpen,
@@ -270,7 +270,7 @@ class TPopupOptions {
     bool closeOnOverlayClick = true,
     Color? overlayColor,
     double? overlayOpacity,
-    bool preventScrollThrough = true,
+    bool modal = true,
     bool destroyOnClose = false,
     Duration animationDuration = const Duration(milliseconds: 240),
     VoidCallback? onOpen,
@@ -291,7 +291,7 @@ class TPopupOptions {
         closeOnOverlayClick: closeOnOverlayClick,
         overlayColor: overlayColor,
         overlayOpacity: overlayOpacity,
-        preventScrollThrough: preventScrollThrough,
+        modal: modal,
         destroyOnClose: destroyOnClose,
         animationDuration: animationDuration,
         onOpen: onOpen,
@@ -329,10 +329,12 @@ class TPopupOptions {
   /// 内容区背景色,默认主题容器色。
   final Color? backgroundColor;
 
-  /// 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。
+  /// 是否绘制半透明蒙层。
+  ///
+  /// 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。
   final bool showOverlay;
 
-  /// 点击蒙层是否关闭(须 [showOverlay] 为 true)。
+  /// 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。
   final bool closeOnOverlayClick;
 
   /// 蒙层颜色,默认 black54。
@@ -341,8 +343,13 @@ class TPopupOptions {
   /// 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。
   final double? overlayOpacity;
 
-  /// 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。
-  final bool preventScrollThrough;
+  /// 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。
+  ///
+  /// 结合 [showOverlay] 可表达三种模式:
+  /// * `modal=true, showOverlay=true`:标准模态弹层
+  /// * `modal=true, showOverlay=false`:透明模态弹层
+  /// * `modal=false, showOverlay=false`:非模态浮层
+  final bool modal;
 
   /// 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。
   final bool destroyOnClose;
@@ -406,7 +413,7 @@ class TPopupOptions {
     bool? closeOnOverlayClick,
     Object? overlayColor = _unset,
     Object? overlayOpacity = _unset,
-    bool? preventScrollThrough,
+    bool? modal,
     bool? destroyOnClose,
     Duration? animationDuration,
     Object? headerBuilder = _unset,
@@ -444,7 +451,7 @@ class TPopupOptions {
       overlayOpacity: identical(overlayOpacity, _unset)
           ? this.overlayOpacity
           : (overlayOpacity as num?)?.toDouble(),
-      preventScrollThrough: preventScrollThrough ?? this.preventScrollThrough,
+      modal: modal ?? this.modal,
       destroyOnClose: destroyOnClose ?? this.destroyOnClose,
       animationDuration: animationDuration ?? this.animationDuration,
       headerBuilder: identical(headerBuilder, _unset)
@@ -497,7 +504,7 @@ class TPopupOptions {
       closeOnOverlayClick: closeOnOverlayClick,
       overlayColor: overlayColor,
       overlayOpacity: overlayOpacity,
-      preventScrollThrough: preventScrollThrough,
+      modal: modal,
       destroyOnClose: destroyOnClose,
       animationDuration: animationDuration,
       headerBuilder: isBottom ? headerBuilder : null,
@@ -626,6 +633,12 @@ class TPopupOptions {
       return 'closeBuilder only applies to placement=center '
           '(got placement=$placement).';
     }
+    if (showOverlay && !modal) {
+      return 'showOverlay=true requires modal=true.';
+    }
+    if (!showOverlay && closeOnOverlayClick) {
+      return 'closeOnOverlayClick requires showOverlay=true.';
+    }
     return null;
   }
 }
diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart
index 33b260668..9a7408906 100644
--- a/tdesign-component/test/t_popup_coverage_test.dart
+++ b/tdesign-component/test/t_popup_coverage_test.dart
@@ -569,7 +569,7 @@ void main() {
       await tester.pumpAndSettle();
     });
 
-    testWidgets('preventScrollThrough 为 false 仍可打开关闭', (tester) async {
+    testWidgets('modal 为 false 仍可打开关闭', (tester) async {
       TPopupHandle? handle;
       await openPopup(
         tester,
@@ -579,7 +579,9 @@ void main() {
             options: TPopupOptions(
                 placement: TPopupPlacement.bottom,
                 height: 120,
-                preventScrollThrough: false,
+                showOverlay: false,
+                closeOnOverlayClick: false,
+                modal: false,
                 child: const SizedBox(height: 60)),
           );
         },
@@ -782,6 +784,7 @@ void main() {
         child: const SizedBox(),
         animationDuration: const Duration(milliseconds: 500),
         showOverlay: false,
+        closeOnOverlayClick: false,
         overlayOpacity: 0.5,
         destroyOnClose: true,
         onVisibleChange: (_, __) => visibleChanges++,
diff --git a/tdesign-component/test/t_popup_options_test.dart b/tdesign-component/test/t_popup_options_test.dart
index 0a4d5032e..97a8867af 100644
--- a/tdesign-component/test/t_popup_options_test.dart
+++ b/tdesign-component/test/t_popup_options_test.dart
@@ -7,6 +7,7 @@ void main() {
     test('默认 placement 为 bottom,4 个 builder 默认 sentinel', () {
       final options = TPopupOptions(child: const SizedBox()).normalized();
       expect(options.placement, TPopupPlacement.bottom);
+      expect(options.modal, isTrue);
       expect(options.usesDefaultHeader, isTrue);
       expect(options.usesDefaultCancel, isTrue);
       expect(options.usesDefaultConfirm, isTrue);
@@ -166,6 +167,22 @@ void main() {
         ).assertPlacementParams(),
         throwsA(isA()),
       );
+      expect(
+        () => TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: true,
+          modal: false,
+        ).assertPlacementParams(),
+        throwsA(isA()),
+      );
+      expect(
+        () => TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          closeOnOverlayClick: true,
+        ).assertPlacementParams(),
+        throwsA(isA()),
+      );
     });
 
     test('assertPlacementParams 各 placement 的 inset 类型错位也抛错', () {
@@ -218,6 +235,15 @@ void main() {
         ).assertPlacementParams(),
         returnsNormally,
       );
+      expect(
+        () => TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          closeOnOverlayClick: false,
+          modal: false,
+        ).assertPlacementParams(),
+        returnsNormally,
+      );
     });
   });
 }
diff --git a/tdesign-component/test/t_popup_route_test.dart b/tdesign-component/test/t_popup_route_test.dart
index 5d52eecb0..2aeaf9ab0 100644
--- a/tdesign-component/test/t_popup_route_test.dart
+++ b/tdesign-component/test/t_popup_route_test.dart
@@ -9,7 +9,7 @@ void main() {
   tearDown(resetPopupTestResource);
 
   group('Popup 路由层行为(通过 TPopup.show 验证)', () {
-    testWidgets('无蒙层 + preventScrollThrough=true 时阻断底层点击与滚动',
+    testWidgets('无蒙层 + modal=true 时阻断底层点击与滚动',
         (tester) async {
       final controller = ScrollController();
       var backgroundTapCount = 0;
@@ -44,7 +44,8 @@ void main() {
                             options: TPopupOptions.bottom(
                               height: 120,
                               showOverlay: false,
-                              preventScrollThrough: true,
+                              closeOnOverlayClick: false,
+                              modal: true,
                               cancelBuilder: null,
                               confirmBuilder: null,
                               child: const SizedBox(height: 60),
@@ -79,7 +80,7 @@ void main() {
       expect(controller.offset, 0);
     });
 
-    testWidgets('无蒙层 + preventScrollThrough=false 时允许底层点击与滚动',
+    testWidgets('无蒙层 + modal=false 时允许底层点击与滚动',
         (tester) async {
       final controller = ScrollController();
       var backgroundTapCount = 0;
@@ -114,7 +115,8 @@ void main() {
                             options: TPopupOptions.bottom(
                               height: 120,
                               showOverlay: false,
-                              preventScrollThrough: false,
+                              closeOnOverlayClick: false,
+                              modal: false,
                               cancelBuilder: null,
                               confirmBuilder: null,
                               child: const SizedBox(height: 60),
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index d4578eb98..9fe245922 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -625,7 +625,7 @@ void main() {
       await tester.pumpAndSettle();
     });
 
-    testWidgets('showOverlay false 且 preventScrollThrough', (tester) async {
+    testWidgets('showOverlay false 且 modal=true', (tester) async {
       await openPopup(
         tester,
         onPressed: () {
@@ -635,14 +635,15 @@ void main() {
                 placement: TPopupPlacement.bottom,
                 height: 100,
                 showOverlay: false,
-                preventScrollThrough: true,
+                closeOnOverlayClick: false,
+                modal: true,
                 child: const SizedBox(height: 60)),
           );
         },
       );
       await tester.pumpAndSettle();
       expect(
-        find.byType(AbsorbPointer),
+        find.byType(ModalBarrier),
         findsWidgets,
       );
     });
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index b45b5db2b..5d93e24b4 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -461,7 +461,9 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           options: TPopupOptions.bottom(
               height: 280,
               showOverlay: false,
-              // 无蒙层时无法点遮罩关闭,须保留操作栏取消(或其它关闭入口)
+              closeOnOverlayClick: false,
+              modal: true,
+              // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
               titleWidget: const TText('无蒙层'),
               child: Container(
                 height: 200,
@@ -610,6 +612,16 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 自定义 builder 只负责替换内容,框架不会自动补点击行为;如果需要点击关闭,请在 builder 内绑定 `close`。
 
  生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
+
+## 模态与蒙层
+
+| 参数组合 | 效果 |
+|----------|------|
+| `modal: true, showOverlay: true` | 标准模态弹层:显示遮罩,阻断背景交互与底层语义/焦点 |
+| `modal: true, showOverlay: false` | 透明模态弹层:不显示遮罩,但仍阻断背景交互与底层语义/焦点 |
+| `modal: false, showOverlay: false` | 非模态浮层:不显示遮罩,允许背景继续交互 |
+
+`showOverlay: true, modal: false` 不属于支持组合;`showOverlay: false` 时需同时设置 `closeOnOverlayClick: false`。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -634,7 +646,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 |
-| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
 | titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
@@ -665,7 +677,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -694,7 +706,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -722,7 +734,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -750,7 +762,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -778,7 +790,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否阻断底层交互;无蒙层时用透明交互层拦截点击、拖拽与滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |

From a786408f73ca82e8b45cc9da7c2ee8bf7118528f Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Tue, 26 May 2026 07:01:01 +0000
Subject: [PATCH 23/27] [autofix.ci] apply automated fixes

---
 .../example/assets/api/action-sheet_api.md    |  73 +++++++-
 .../example/assets/api/image-viewer_api.md    |  36 +++-
 .../example/assets/api/message_api.md         |  20 +-
 .../example/assets/api/popover_api.md         |  23 ++-
 .../example/assets/api/popup_api.md           |  63 ++++---
 .../example/assets/api/swipe-cell_api.md      |  25 ++-
 .../example/assets/api/theme_api.md           | 104 ++++++++++-
 .../example/assets/api/toast_api.md           | 175 ++++++++++++++++--
 ...p._buildPopFromBottomWithCloseAndTitle.txt |  32 +++-
 tdesign-site/src/action-sheet/README.md       |  73 +++++++-
 tdesign-site/src/image-viewer/README.md       |  36 +++-
 tdesign-site/src/message/README.md            |  20 +-
 tdesign-site/src/popover/README.md            |  23 ++-
 tdesign-site/src/popup/README.md              |  88 +++++----
 tdesign-site/src/swipe-cell/README.md         |  25 ++-
 tdesign-site/src/toast/README.md              | 173 +++++++++++++++--
 16 files changed, 854 insertions(+), 135 deletions(-)

diff --git a/tdesign-component/example/assets/api/action-sheet_api.md b/tdesign-component/example/assets/api/action-sheet_api.md
index 664f3bfc3..64bc4b9dd 100644
--- a/tdesign-component/example/assets/api/action-sheet_api.md
+++ b/tdesign-component/example/assets/api/action-sheet_api.md
@@ -47,11 +47,76 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TActionSheet.showGridActionSheet
+
+显示宫格类型面板
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
+| items | List | - | ActionSheet的项目列表 |
+| align | TActionSheetAlign | TActionSheetAlign.center | 对齐方式 |
+| cancelText | String? | - | 取消按钮的文本 |
+| showCancel | bool | true | 是否显示取消按钮 |
+| onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
+| showOverlay | bool | true | 是否显示遮罩层 |
+| closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
+| count | int | 8 | 每页显示的项目数 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为true时有效 |
+| rows | int | 2 | 显示的行数 当[theme]等于[TActionSheetTheme.grid]时有效 |
+| itemHeight | double | 96.0 | 项目的行高 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.group]时有效 |
+| itemMinWidth | double | 80.0 | 项目的最小宽度 当[theme]等于[TActionSheetTheme.grid]且[scrollable]为true时有效 或当[theme]等于[TActionSheetTheme.group]时有效 |
+| scrollable | bool | false | 是否可以横向滚动 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为false时有效 |
+| showPagination | bool | false | 是否显示分页 当[theme]等于[TActionSheetTheme.grid]时有效 |
+| onCancel | VoidCallback? | - | 取消按钮的回调函数 |
+| description | String? | - | 描述文本 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.list]时有效 |
+| onClose | VoidCallback? | - | 关闭时的回调函数 |
+| useSafeArea | bool | true | 使用安全区域 |
+
+
+##### TActionSheet.showGroupActionSheet
+
+显示分组类型面板
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showGridActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, int count, int rows, double itemHeight, double itemMinWidth, bool scrollable, bool showPagination, VoidCallback? onCancel, String? description, VoidCallback? onClose, bool useSafeArea | 显示宫格类型面板 |
-| showGroupActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, double itemHeight, double itemMinWidth, VoidCallback? onCancel, VoidCallback? onClose, bool useSafeArea | 显示分组类型面板 |
-| showListActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, VoidCallback? onCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, VoidCallback? onClose, bool useSafeArea | 显示列表类型面板 |
+| context | BuildContext | - | 上下文 |
+| items | List | - | ActionSheet的项目列表 |
+| align | TActionSheetAlign | TActionSheetAlign.left | 对齐方式 |
+| cancelText | String? | - | 取消按钮的文本 |
+| showCancel | bool | true | 是否显示取消按钮 |
+| onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
+| showOverlay | bool | true | 是否显示遮罩层 |
+| closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
+| itemHeight | double | 96.0 | 项目的行高 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.group]时有效 |
+| itemMinWidth | double | 80.0 | 项目的最小宽度 当[theme]等于[TActionSheetTheme.grid]且[scrollable]为true时有效 或当[theme]等于[TActionSheetTheme.group]时有效 |
+| onCancel | VoidCallback? | - | 取消按钮的回调函数 |
+| onClose | VoidCallback? | - | 关闭时的回调函数 |
+| useSafeArea | bool | true | 使用安全区域 |
+
+
+##### TActionSheet.showListActionSheet
+
+显示列表类型面板
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
+| items | List | - | ActionSheet的项目列表 |
+| align | TActionSheetAlign | TActionSheetAlign.center | 对齐方式 |
+| cancelText | String? | - | 取消按钮的文本 |
+| showCancel | bool | true | 是否显示取消按钮 |
+| onCancel | VoidCallback? | - | 取消按钮的回调函数 |
+| onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
+| showOverlay | bool | true | 是否显示遮罩层 |
+| closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
+| onClose | VoidCallback? | - | 关闭时的回调函数 |
+| useSafeArea | bool | true | 使用安全区域 |
 
 
 ### TActionSheetTheme
diff --git a/tdesign-component/example/assets/api/image-viewer_api.md b/tdesign-component/example/assets/api/image-viewer_api.md
index 89df5e4e7..1cc94ffd9 100644
--- a/tdesign-component/example/assets/api/image-viewer_api.md
+++ b/tdesign-component/example/assets/api/image-viewer_api.md
@@ -3,9 +3,41 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TImageViewer.showImageViewer
+
+显示图片预览
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showImageViewer | void | required BuildContext context, required List images, List? labels, bool? closeBtn, bool? deleteBtn, bool? showIndex, bool? loop, bool? autoplay, int? duration, Color? bgColor, Color? navBarBgColor, Color? iconColor, TextStyle? labelStyle, TextStyle? indexStyle, Color? modalBarrierColor, bool? barrierDismissible, int? defaultIndex, double? width, double? height, OnIndexChange? onIndexChange, OnClose? onClose, OnDelete? onDelete, bool? ignoreDeleteError, OnImageTap? onTap, OnLongPress? onLongPress, LeftItemBuilder? leftItemBuilder, RightItemBuilder? rightItemBuilder | 显示图片预览 |
+| context | BuildContext | - | - |
+| images | List | - | 图片数组 |
+| labels | List? | - | 图片描述 |
+| closeBtn | bool? | true | 是否展示关闭按钮 |
+| deleteBtn | bool? | false | 是否显示删除操作 |
+| showIndex | bool? | false | 是否显示页码 |
+| loop | bool? | false | 图片是否循环 |
+| autoplay | bool? | false | 图片轮播是否自动播放 |
+| duration | int? | - | 自动播放间隔 |
+| bgColor | Color? | - | 背景色 |
+| navBarBgColor | Color? | - | 导航栏背景色 |
+| iconColor | Color? | - | 图标颜色 |
+| labelStyle | TextStyle? | - | label文字样式 |
+| indexStyle | TextStyle? | - | 页码样式 |
+| modalBarrierColor | Color? | - | - |
+| barrierDismissible | bool? | - | - |
+| defaultIndex | int? | - | 默认预览图片所在的下标 |
+| width | double? | - | 图片宽度 |
+| height | double? | - | 图片高度 |
+| onIndexChange | OnIndexChange? | - | 预览图片切换回调 |
+| onClose | OnClose? | - | 关闭点击 |
+| onDelete | OnDelete? | - | 删除点击 |
+| ignoreDeleteError | bool? | - | 是否忽略单张图片删除错误提示 |
+| onTap | OnImageTap? | - | 点击图片 |
+| onLongPress | OnLongPress? | - | 长按图片 |
+| leftItemBuilder | LeftItemBuilder? | - | 左侧自定义操作 |
+| rightItemBuilder | RightItemBuilder? | - | 右侧自定义操作 |
 
 
 ### TImageViewerWidget
diff --git a/tdesign-component/example/assets/api/message_api.md b/tdesign-component/example/assets/api/message_api.md
index 9c3abd1ab..c1cfed62a 100644
--- a/tdesign-component/example/assets/api/message_api.md
+++ b/tdesign-component/example/assets/api/message_api.md
@@ -21,9 +21,25 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TMessage.showMessage
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showMessage | void | required BuildContext context, String? content, bool? visible, int? duration, dynamic closeBtn, dynamic icon, dynamic link, MessageMarquee? marquee, List? offset, MessageTheme? theme, VoidCallback? onCloseBtnClick, VoidCallback? onDurationEnd, VoidCallback? onLinkClick | - |
+| context | BuildContext | - | - |
+| content | String? | - | 通知内容 |
+| visible | bool? | - | 是否显示 |
+| duration | int? | - | 消息内置计时器 |
+| closeBtn | dynamic | - | 关闭按钮 |
+| icon | dynamic | - | 自定义消息前面的图标 |
+| link | dynamic | - | 链接名称 |
+| marquee | MessageMarquee? | - | 跑马灯效果 |
+| offset | List? | - | 相对于 placement 的偏移量 |
+| theme | MessageTheme? | - | 消息组件风格 info/success/warning/error |
+| onCloseBtnClick | VoidCallback? | - | 点击关闭按钮触发 |
+| onDurationEnd | VoidCallback? | - | 计时结束后触发 |
+| onLinkClick | VoidCallback? | - | 点击链接文本时触发 |
 
 
 ### MessageMarquee
diff --git a/tdesign-component/example/assets/api/popover_api.md b/tdesign-component/example/assets/api/popover_api.md
index 480124a64..bcf1ff4a9 100644
--- a/tdesign-component/example/assets/api/popover_api.md
+++ b/tdesign-component/example/assets/api/popover_api.md
@@ -3,9 +3,28 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TPopover.showPopover
+
+返回类型:`Future`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showPopover | Future | required BuildContext context, String? content, Widget? contentWidget, double offset, TPopoverTheme? theme, bool closeOnClickOutside, TPopoverPlacement? placement, bool? showArrow, double arrowSize, EdgeInsetsGeometry? padding, double? width, double? height, Color? overlayColor, OnTap? onTap, OnLongTap? onLongTap, BorderRadius? radius | - |
+| context | BuildContext | - | 上下文 |
+| content | String? | - | 显示内容 |
+| contentWidget | Widget? | - | 自定义内容 |
+| offset | double | 4 | 偏移 |
+| theme | TPopoverTheme? | - | 弹出气泡主题 |
+| closeOnClickOutside | bool | true | - |
+| placement | TPopoverPlacement? | - | 浮层出现位置 |
+| showArrow | bool? | true | 是否显示浮层箭头 |
+| arrowSize | double | 8 | 箭头大小 |
+| padding | EdgeInsetsGeometry? | - | 内容内边距 |
+| width | double? | - | 内容宽度(包含padding,实际高度:height - paddingLeft - paddingRight) |
+| height | double? | - | 内容高度(包含padding,实际高度:height - paddingTop - paddingBottom) |
+| overlayColor | Color? | Colors.transparent | - |
+| onTap | OnTap? | - | 点击事件 |
+| onLongTap | OnLongTap? | - | 长按事件 |
+| radius | BorderRadius? | - | 圆角 |
 
 
 ### TPopoverWidget
diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md
index ef3784508..d3d7559b3 100644
--- a/tdesign-component/example/assets/api/popup_api.md
+++ b/tdesign-component/example/assets/api/popup_api.md
@@ -28,9 +28,30 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TPopup.show
+
+打开浮层并压入独立 [PopupRoute]。
+
+ [context] 用于查找 [Navigator] 并展示浮层。
+
+ [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。
+
+ 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、
+ [TPopupHandle.isShowing] 控制与查询。
+ 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。
+
+ [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。
+
+ [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。
+
+返回类型:`TPopupHandle`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| show | TPopupHandle | required BuildContext context, required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator | 打开浮层并压入独立 [PopupRoute]。 [context] 用于查找 [Navigator] 并展示浮层。 [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 [TPopupHandle.isShowing] 控制与查询。 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。 [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
+| context | BuildContext | - | - |
+| options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
+| navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
+| useRootNavigator | bool | false | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
 
 
 ### TPopupOptions
@@ -61,7 +82,7 @@
  |----------|------|
  | 省略(使用默认值) | 渲染内置 UI |
  | 显式 `null` | 隐藏该区域 |
- | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;可调用 `close` 关闭浮层 |
+ | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;需自行提供交互与语义,可调用 `close` 关闭浮层 |
 
  [titleWidget] 默认为 `null`,表示无标题内容。
 
@@ -75,12 +96,13 @@
 | cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
 | child | Widget | - | 浮层主体内容(必填)。 |
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
 | height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
 | inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
 | onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -90,9 +112,8 @@
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
 | titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
 | width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
 
@@ -117,11 +138,11 @@
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -146,11 +167,11 @@
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -174,11 +195,11 @@
 | inset | TPopupLeftInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -202,11 +223,11 @@
 | inset | TPopupRightInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -230,11 +251,11 @@
 | inset | TPopupTopInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| preventScrollThrough | bool | true | 是否拦截底层滚动;无蒙层时用透明层吸收滚动。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
diff --git a/tdesign-component/example/assets/api/swipe-cell_api.md b/tdesign-component/example/assets/api/swipe-cell_api.md
index be9049b40..5f6f0c9ca 100644
--- a/tdesign-component/example/assets/api/swipe-cell_api.md
+++ b/tdesign-component/example/assets/api/swipe-cell_api.md
@@ -25,10 +25,29 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TSwipeCell.close
+
+根据[groupTag]关闭[TSwipeCell]
+
+ current:保留当前不关闭
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| tag | Object? | - | - |
+| current | SlidableController? | - | - |
+
+
+##### TSwipeCell.of
+
+获取上下文最近的[controller]
+
+返回类型:`SlidableController?`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| close | void | required Object? tag, SlidableController? current | 根据[groupTag]关闭[TSwipeCell] current:保留当前不关闭 |
-| of | SlidableController? | required BuildContext context | 获取上下文最近的[controller] |
+| context | BuildContext | - | - |
 
 
 ### TSwipeDirection
diff --git a/tdesign-component/example/assets/api/theme_api.md b/tdesign-component/example/assets/api/theme_api.md
index f4e32529d..922b3d8be 100644
--- a/tdesign-component/example/assets/api/theme_api.md
+++ b/tdesign-component/example/assets/api/theme_api.md
@@ -12,13 +12,59 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TTheme.defaultData
+
+获取默认主题数据,全局唯一
+
+返回类型:`TThemeData`
+
+##### TTheme.needMultiTheme
+
+开启多套主题功能
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| value | bool | true | - |
+
+
+##### TTheme.of
+
+获取主题数据,如果未传context则获取全局唯一的默认数据,
+ 传了context,则获取最近的主题,取不到则会获取全局唯一默认数据
+
+返回类型:`TThemeData`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext? | - | - |
+
+
+##### TTheme.ofNullable
+
+获取主题数据,取不到则可空
+ 传了context,则获取最近的主题,取不到或未传context则返回null,
+
+返回类型:`TThemeData?`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext? | - | - |
+
+
+##### TTheme.setResourceBuilder
+
+设置资源代理,
+ needAlwaysBuild=true:每次都会走build方法;如果全局有多个Delegate,需要区分情况去获取,则可以设置needAlwaysBuild为true,业务自己判断返回哪个delegate
+ needAlwaysBuild=false:返回delegate为null,则每次都会走build方法,返回了
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| defaultData | TThemeData | - | 获取默认主题数据,全局唯一 |
-| needMultiTheme | void | bool value | 开启多套主题功能 |
-| of | TThemeData | BuildContext? context | 获取主题数据,如果未传context则获取全局唯一的默认数据, 传了context,则获取最近的主题,取不到则会获取全局唯一默认数据 |
-| ofNullable | TThemeData? | BuildContext? context | 获取主题数据,取不到则可空 传了context,则获取最近的主题,取不到或未传context则返回null, |
-| setResourceBuilder | void | required TResourceBuilder delegate, bool needAlwaysBuild | 设置资源代理, needAlwaysBuild=true:每次都会走build方法;如果全局有多个Delegate,需要区分情况去获取,则可以设置needAlwaysBuild为true,业务自己判断返回哪个delegate needAlwaysBuild=false:返回delegate为null,则每次都会走build方法,返回了 |
+| delegate | TResourceBuilder | - | - |
+| needAlwaysBuild | bool | false | - |
 
 
 ### TThemeData
@@ -46,11 +92,49 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TThemeData.defaultData
+
+获取默认Data,一个App里只有一个,用于没有context的地方
+
+返回类型:`TThemeData`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| defaultData | TThemeData | TExtraThemeData? extraThemeData | 获取默认Data,一个App里只有一个,用于没有context的地方 |
-| fromJson | TThemeData? | required String name, required String themeJson, String? darkName, recoverDefault, TExtraThemeData? extraThemeData | 解析配置的json文件为主题数据 [name] 主题名称,目前只支持一级键 [themeJson] 主题json字符串,要求json配置必须正确 [recoverDefault] 是否恢复为默认主题数据 [extraThemeData] 额外扩展的主题数据 |
-| parseThemeData | TThemeData | required String name, required themeConfig, required TExtraThemeData? extraThemeData | - |
+| extraThemeData | TExtraThemeData? | - | 额外定义的结构 |
+
+
+##### TThemeData.fromJson
+
+解析配置的json文件为主题数据
+
+ [name] 主题名称,目前只支持一级键
+
+ [themeJson] 主题json字符串,要求json配置必须正确
+
+ [recoverDefault] 是否恢复为默认主题数据
+
+ [extraThemeData] 额外扩展的主题数据
+
+返回类型:`TThemeData?`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| name | String | - | 名称 |
+| themeJson | String | - | - |
+| darkName | String? | - | - |
+| recoverDefault | - | false | - |
+| extraThemeData | TExtraThemeData? | - | 额外定义的结构 |
+
+
+##### TThemeData.parseThemeData
+
+返回类型:`TThemeData`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| name | String | - | 名称 |
+| themeConfig | - | - | - |
+| extraThemeData | TExtraThemeData? | - | 额外定义的结构 |
 
 
 ### DefaultMapFactory
diff --git a/tdesign-component/example/assets/api/toast_api.md b/tdesign-component/example/assets/api/toast_api.md
index 3dd94b63b..f0699ef24 100644
--- a/tdesign-component/example/assets/api/toast_api.md
+++ b/tdesign-component/example/assets/api/toast_api.md
@@ -3,18 +3,169 @@
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
-| --- | --- | --- | --- |
-| dismissAll | void | - | 关闭所有Toast |
-| dismissLoading | void | - | 关闭加载Toast(向后兼容) |
-| dismissToast | void | required String toastId | 关闭指定的Toast |
-| showFail | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 失败提示Toast |
-| showIconText | String | required String? text, IconData? icon, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带图标的Toast |
-| showLoading | String | required BuildContext context, String? text, Duration duration, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带文案的加载Toast |
-| showLoadingWithoutText | String | required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, double? iconSize, Color? iconColor, String? toastId | 不带文案的加载Toast |
-| showSuccess | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 成功提示Toast |
-| showText | String | required String? text, required BuildContext context, Duration duration, int? maxLines, BoxConstraints? constraints, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, String? toastId | 普通文本Toast |
-| showWarning | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 警告Toast |
+##### TToast.dismissAll
+
+关闭所有Toast
+
+返回类型:`void`
+
+##### TToast.dismissLoading
+
+关闭加载Toast(向后兼容)
+
+返回类型:`void`
+
+##### TToast.dismissToast
+
+关闭指定的Toast
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| toastId | String | - | - |
+
+
+##### TToast.showFail
+
+失败提示Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showIconText
+
+带图标的Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| icon | IconData? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showLoading
+
+带文案的加载Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| text | String? | - | - |
+| duration | Duration | const Duration(seconds: 99999999) | - |
+| preventTap | bool? | - | - |
+| customWidget | Widget? | - | - |
+| backgroundColor | Color? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showLoadingWithoutText
+
+不带文案的加载Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(seconds: 99999999) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showSuccess
+
+成功提示Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showText
+
+普通文本Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| maxLines | int? | - | - |
+| constraints | BoxConstraints? | - | - |
+| preventTap | bool? | - | - |
+| customWidget | Widget? | - | - |
+| backgroundColor | Color? | - | - |
+| textStyle | TextStyle? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showWarning
+
+警告Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
 
 
 ### IconTextDirection
diff --git a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
index e5df1335a..83ddf96b2 100644
--- a/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
+++ b/tdesign-component/example/assets/code/popup._buildPopFromBottomWithCloseAndTitle.txt
@@ -11,10 +11,17 @@
           context,
           options: TPopupOptions.bottom(
               height: 280,
-              cancelBuilder: (_, __) => TText(
-                    '关闭',
-                    textColor: TTheme.of(context).textColorSecondary,
-                    font: TTheme.of(context).fontBodyLarge,
+              cancelBuilder: (_, close) => GestureDetector(
+                    behavior: HitTestBehavior.opaque,
+                    onTap: close,
+                    child: Padding(
+                      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                      child: TText(
+                        '关闭',
+                        textColor: TTheme.of(context).textColorSecondary,
+                        font: TTheme.of(context).fontBodyLarge,
+                      ),
+                    ),
                   ),
               titleWidget: Row(
                 mainAxisSize: MainAxisSize.min,
@@ -29,11 +36,18 @@
                   ),
                 ],
               ),
-              confirmBuilder: (_, __) => TText(
-                    '完成',
-                    textColor: TTheme.of(context).brandNormalColor,
-                    font: TTheme.of(context).fontTitleMedium,
-                    fontWeight: FontWeight.w600,
+              confirmBuilder: (_, close) => GestureDetector(
+                    behavior: HitTestBehavior.opaque,
+                    onTap: close,
+                    child: Padding(
+                      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
+                      child: TText(
+                        '完成',
+                        textColor: TTheme.of(context).brandNormalColor,
+                        font: TTheme.of(context).fontTitleMedium,
+                        fontWeight: FontWeight.w600,
+                      ),
+                    ),
                   ),
               child: Container(height: 200)),
         );
diff --git a/tdesign-site/src/action-sheet/README.md b/tdesign-site/src/action-sheet/README.md
index b2c7bf0b0..2c8614a1b 100644
--- a/tdesign-site/src/action-sheet/README.md
+++ b/tdesign-site/src/action-sheet/README.md
@@ -935,11 +935,76 @@ Widget _buildIconListLeftActionSheet(BuildContext context) {
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TActionSheet.showGridActionSheet
+
+显示宫格类型面板
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
+| items | List | - | ActionSheet的项目列表 |
+| align | TActionSheetAlign | TActionSheetAlign.center | 对齐方式 |
+| cancelText | String? | - | 取消按钮的文本 |
+| showCancel | bool | true | 是否显示取消按钮 |
+| onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
+| showOverlay | bool | true | 是否显示遮罩层 |
+| closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
+| count | int | 8 | 每页显示的项目数 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为true时有效 |
+| rows | int | 2 | 显示的行数 当[theme]等于[TActionSheetTheme.grid]时有效 |
+| itemHeight | double | 96.0 | 项目的行高 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.group]时有效 |
+| itemMinWidth | double | 80.0 | 项目的最小宽度 当[theme]等于[TActionSheetTheme.grid]且[scrollable]为true时有效 或当[theme]等于[TActionSheetTheme.group]时有效 |
+| scrollable | bool | false | 是否可以横向滚动 当[theme]等于[TActionSheetTheme.grid]且[showPagination]为false时有效 |
+| showPagination | bool | false | 是否显示分页 当[theme]等于[TActionSheetTheme.grid]时有效 |
+| onCancel | VoidCallback? | - | 取消按钮的回调函数 |
+| description | String? | - | 描述文本 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.list]时有效 |
+| onClose | VoidCallback? | - | 关闭时的回调函数 |
+| useSafeArea | bool | true | 使用安全区域 |
+
+
+##### TActionSheet.showGroupActionSheet
+
+显示分组类型面板
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showGridActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, int count, int rows, double itemHeight, double itemMinWidth, bool scrollable, bool showPagination, VoidCallback? onCancel, String? description, VoidCallback? onClose, bool useSafeArea | 显示宫格类型面板 |
-| showGroupActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, double itemHeight, double itemMinWidth, VoidCallback? onCancel, VoidCallback? onClose, bool useSafeArea | 显示分组类型面板 |
-| showListActionSheet | void | required BuildContext context, required List items, TActionSheetAlign align, String? cancelText, bool showCancel, VoidCallback? onCancel, TActionSheetItemCallback? onSelected, bool showOverlay, bool closeOnOverlayClick, VoidCallback? onClose, bool useSafeArea | 显示列表类型面板 |
+| context | BuildContext | - | 上下文 |
+| items | List | - | ActionSheet的项目列表 |
+| align | TActionSheetAlign | TActionSheetAlign.left | 对齐方式 |
+| cancelText | String? | - | 取消按钮的文本 |
+| showCancel | bool | true | 是否显示取消按钮 |
+| onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
+| showOverlay | bool | true | 是否显示遮罩层 |
+| closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
+| itemHeight | double | 96.0 | 项目的行高 当[theme]等于[TActionSheetTheme.grid]或[theme]等于[TActionSheetTheme.group]时有效 |
+| itemMinWidth | double | 80.0 | 项目的最小宽度 当[theme]等于[TActionSheetTheme.grid]且[scrollable]为true时有效 或当[theme]等于[TActionSheetTheme.group]时有效 |
+| onCancel | VoidCallback? | - | 取消按钮的回调函数 |
+| onClose | VoidCallback? | - | 关闭时的回调函数 |
+| useSafeArea | bool | true | 使用安全区域 |
+
+
+##### TActionSheet.showListActionSheet
+
+显示列表类型面板
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | 上下文 |
+| items | List | - | ActionSheet的项目列表 |
+| align | TActionSheetAlign | TActionSheetAlign.center | 对齐方式 |
+| cancelText | String? | - | 取消按钮的文本 |
+| showCancel | bool | true | 是否显示取消按钮 |
+| onCancel | VoidCallback? | - | 取消按钮的回调函数 |
+| onSelected | TActionSheetItemCallback? | - | 选择项目时的回调函数 |
+| showOverlay | bool | true | 是否显示遮罩层 |
+| closeOnOverlayClick | bool | true | 点击蒙层时是否关闭 |
+| onClose | VoidCallback? | - | 关闭时的回调函数 |
+| useSafeArea | bool | true | 使用安全区域 |
 
 
 ### TActionSheetTheme
diff --git a/tdesign-site/src/image-viewer/README.md b/tdesign-site/src/image-viewer/README.md
index 6652845fc..f48679c44 100644
--- a/tdesign-site/src/image-viewer/README.md
+++ b/tdesign-site/src/image-viewer/README.md
@@ -73,9 +73,41 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TImageViewer.showImageViewer
+
+显示图片预览
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showImageViewer | void | required BuildContext context, required List images, List? labels, bool? closeBtn, bool? deleteBtn, bool? showIndex, bool? loop, bool? autoplay, int? duration, Color? bgColor, Color? navBarBgColor, Color? iconColor, TextStyle? labelStyle, TextStyle? indexStyle, Color? modalBarrierColor, bool? barrierDismissible, int? defaultIndex, double? width, double? height, OnIndexChange? onIndexChange, OnClose? onClose, OnDelete? onDelete, bool? ignoreDeleteError, OnImageTap? onTap, OnLongPress? onLongPress, LeftItemBuilder? leftItemBuilder, RightItemBuilder? rightItemBuilder | 显示图片预览 |
+| context | BuildContext | - | - |
+| images | List | - | 图片数组 |
+| labels | List? | - | 图片描述 |
+| closeBtn | bool? | true | 是否展示关闭按钮 |
+| deleteBtn | bool? | false | 是否显示删除操作 |
+| showIndex | bool? | false | 是否显示页码 |
+| loop | bool? | false | 图片是否循环 |
+| autoplay | bool? | false | 图片轮播是否自动播放 |
+| duration | int? | - | 自动播放间隔 |
+| bgColor | Color? | - | 背景色 |
+| navBarBgColor | Color? | - | 导航栏背景色 |
+| iconColor | Color? | - | 图标颜色 |
+| labelStyle | TextStyle? | - | label文字样式 |
+| indexStyle | TextStyle? | - | 页码样式 |
+| modalBarrierColor | Color? | - | - |
+| barrierDismissible | bool? | - | - |
+| defaultIndex | int? | - | 默认预览图片所在的下标 |
+| width | double? | - | 图片宽度 |
+| height | double? | - | 图片高度 |
+| onIndexChange | OnIndexChange? | - | 预览图片切换回调 |
+| onClose | OnClose? | - | 关闭点击 |
+| onDelete | OnDelete? | - | 删除点击 |
+| ignoreDeleteError | bool? | - | 是否忽略单张图片删除错误提示 |
+| onTap | OnImageTap? | - | 点击图片 |
+| onLongPress | OnLongPress? | - | 长按图片 |
+| leftItemBuilder | LeftItemBuilder? | - | 左侧自定义操作 |
+| rightItemBuilder | RightItemBuilder? | - | 右侧自定义操作 |
 
 
 ### TImageViewerWidget
diff --git a/tdesign-site/src/message/README.md b/tdesign-site/src/message/README.md
index e573959b5..5ed4b412f 100644
--- a/tdesign-site/src/message/README.md
+++ b/tdesign-site/src/message/README.md
@@ -308,9 +308,25 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TMessage.showMessage
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showMessage | void | required BuildContext context, String? content, bool? visible, int? duration, dynamic closeBtn, dynamic icon, dynamic link, MessageMarquee? marquee, List? offset, MessageTheme? theme, VoidCallback? onCloseBtnClick, VoidCallback? onDurationEnd, VoidCallback? onLinkClick | - |
+| context | BuildContext | - | - |
+| content | String? | - | 通知内容 |
+| visible | bool? | - | 是否显示 |
+| duration | int? | - | 消息内置计时器 |
+| closeBtn | dynamic | - | 关闭按钮 |
+| icon | dynamic | - | 自定义消息前面的图标 |
+| link | dynamic | - | 链接名称 |
+| marquee | MessageMarquee? | - | 跑马灯效果 |
+| offset | List? | - | 相对于 placement 的偏移量 |
+| theme | MessageTheme? | - | 消息组件风格 info/success/warning/error |
+| onCloseBtnClick | VoidCallback? | - | 点击关闭按钮触发 |
+| onDurationEnd | VoidCallback? | - | 计时结束后触发 |
+| onLinkClick | VoidCallback? | - | 点击链接文本时触发 |
 
 
 ### MessageMarquee
diff --git a/tdesign-site/src/popover/README.md b/tdesign-site/src/popover/README.md
index 7e41ef0b9..aae1a64a0 100644
--- a/tdesign-site/src/popover/README.md
+++ b/tdesign-site/src/popover/README.md
@@ -1279,9 +1279,28 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TPopover.showPopover
+
+返回类型:`Future`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| showPopover | Future | required BuildContext context, String? content, Widget? contentWidget, double offset, TPopoverTheme? theme, bool closeOnClickOutside, TPopoverPlacement? placement, bool? showArrow, double arrowSize, EdgeInsetsGeometry? padding, double? width, double? height, Color? overlayColor, OnTap? onTap, OnLongTap? onLongTap, BorderRadius? radius | - |
+| context | BuildContext | - | 上下文 |
+| content | String? | - | 显示内容 |
+| contentWidget | Widget? | - | 自定义内容 |
+| offset | double | 4 | 偏移 |
+| theme | TPopoverTheme? | - | 弹出气泡主题 |
+| closeOnClickOutside | bool | true | - |
+| placement | TPopoverPlacement? | - | 浮层出现位置 |
+| showArrow | bool? | true | 是否显示浮层箭头 |
+| arrowSize | double | 8 | 箭头大小 |
+| padding | EdgeInsetsGeometry? | - | 内容内边距 |
+| width | double? | - | 内容宽度(包含padding,实际高度:height - paddingLeft - paddingRight) |
+| height | double? | - | 内容高度(包含padding,实际高度:height - paddingTop - paddingBottom) |
+| overlayColor | Color? | Colors.transparent | - |
+| onTap | OnTap? | - | 点击事件 |
+| onLongTap | OnLongTap? | - | 长按事件 |
+| radius | BorderRadius? | - | 圆角 |
 
 
 ### TPopoverWidget
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 5d93e24b4..8a706ea82 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -568,13 +568,30 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
-| --- | --- | --- | --- |
-| show | TPopupHandle | required BuildContext context, required TPopupOptions options, BuildContext? navigatorContext, bool useRootNavigator | 打开浮层并压入独立 [PopupRoute]。 [context] 用于查找 [Navigator] 并展示浮层。 [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、 [TPopupHandle.isShowing] 控制与查询。 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。 [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。 [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。 |
+##### TPopup.show
+
+打开浮层并压入独立 [PopupRoute]。
+
+ [context] 用于查找 [Navigator] 并展示浮层。
+
+ [options] 浮层配置;方向固定时推荐 [TPopupOptions.bottom] 等命名工厂。
+
+ 返回 [TPopupHandle],可用 [TPopupHandle.close]、[TPopupHandle.open]、
+ [TPopupHandle.isShowing] 控制与查询。
+ 重复调用会继续 push 新的浮层;若需互斥请在业务层管理。
+
+ [navigatorContext] 可选,指定承载浮层的 [Navigator] 的 context,默认 [context]。
 
-嵌套导航场景下,若触发控件位于内层 `Navigator` 中:
-- 传 `navigatorContext` 可显式指定浮层挂载到哪个 `Navigator`
-- 仅需挂载到根路由时可直接使用 `useRootNavigator: true`
+ [useRootNavigator] 为 true 时使用根 [Navigator](嵌套导航场景)。
+
+返回类型:`TPopupHandle`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
+| navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
+| useRootNavigator | bool | false | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
 
 
 ### TPopupOptions
@@ -605,23 +622,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  |----------|------|
  | 省略(使用默认值) | 渲染内置 UI |
  | 显式 `null` | 隐藏该区域 |
-| 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;需自行提供交互与语义,可调用 `close` 关闭浮层 |
+ | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;需自行提供交互与语义,可调用 `close` 关闭浮层 |
 
  [titleWidget] 默认为 `null`,表示无标题内容。
 
-自定义 builder 只负责替换内容,框架不会自动补点击行为;如果需要点击关闭,请在 builder 内绑定 `close`。
-
  生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
-
-## 模态与蒙层
-
-| 参数组合 | 效果 |
-|----------|------|
-| `modal: true, showOverlay: true` | 标准模态弹层:显示遮罩,阻断背景交互与底层语义/焦点 |
-| `modal: true, showOverlay: false` | 透明模态弹层:不显示遮罩,但仍阻断背景交互与底层语义/焦点 |
-| `modal: false, showOverlay: false` | 非模态浮层:不显示遮罩,允许背景继续交互 |
-
-`showOverlay: true, modal: false` 不属于支持组合;`showOverlay: false` 时需同时设置 `closeOnOverlayClick: false`。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -631,12 +636,13 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
 | child | Widget | - | 浮层主体内容(必填)。 |
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
 | height | double? | - | 高度;[TPopupPlacement.top]、[TPopupPlacement.bottom] 生效;[TPopupPlacement.center] 约束面板尺寸。 |
 | inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
 | onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -646,9 +652,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | placement | TPopupPlacement | TPopupPlacement.bottom | 出现位置,默认 [TPopupPlacement.bottom]。 |
-| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
 | titleWidget | Widget? | - | bottom 标题插槽;仅 [headerBuilder] 为内置默认时生效。`null` 表示无标题。 |
 | width | double? | - | 宽度;[TPopupPlacement.left]、[TPopupPlacement.right]、[TPopupPlacement.center] 生效。 |
 
@@ -673,11 +678,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -702,11 +707,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -730,11 +735,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupLeftInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -758,11 +763,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupRightInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -786,11 +791,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupTopInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool | true | 点击蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
-| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 |
+| modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | animationDuration | Duration | const Duration(milliseconds: 240) | 打开/关闭动画时长。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
@@ -819,20 +824,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 属性 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| isShowing | bool | - | 浮层是否仍在展示(路由在栈中且未进入关闭流程)。 |
 | navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
 | options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
 | useRootNavigator | bool | - | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
 
 
-#### 实例方法
-
-| 名称 | 返回类型 | 参数 | 说明 |
-| --- | --- | --- | --- |
-| open | void | BuildContext? context | 打开或重新打开浮层。首次调用须能解析 [Navigator](传入 [context] 或依赖 [navigatorContext]);后续可省略以复用缓存的 [NavigatorState]。 |
-| close | void | Object? result | 关闭当前展示的浮层;[TPopupOptions.onVisibleChange] 的 [TPopupTrigger] 为 [TPopupTrigger.api]。 |
-
-
 #### 工厂构造方法
 
 ##### TPopupHandle._
diff --git a/tdesign-site/src/swipe-cell/README.md b/tdesign-site/src/swipe-cell/README.md
index 27316a636..0947a8d2e 100644
--- a/tdesign-site/src/swipe-cell/README.md
+++ b/tdesign-site/src/swipe-cell/README.md
@@ -363,10 +363,29 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TSwipeCell.close
+
+根据[groupTag]关闭[TSwipeCell]
+
+ current:保留当前不关闭
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| tag | Object? | - | - |
+| current | SlidableController? | - | - |
+
+
+##### TSwipeCell.of
+
+获取上下文最近的[controller]
+
+返回类型:`SlidableController?`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| close | void | required Object? tag, SlidableController? current | 根据[groupTag]关闭[TSwipeCell] current:保留当前不关闭 |
-| of | SlidableController? | required BuildContext context | 获取上下文最近的[controller] |
+| context | BuildContext | - | - |
 
 
 ### TSwipeDirection
diff --git a/tdesign-site/src/toast/README.md b/tdesign-site/src/toast/README.md
index 51f750262..9b836d4a5 100644
--- a/tdesign-site/src/toast/README.md
+++ b/tdesign-site/src/toast/README.md
@@ -386,18 +386,169 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 #### 静态方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
+##### TToast.dismissAll
+
+关闭所有Toast
+
+返回类型:`void`
+
+##### TToast.dismissLoading
+
+关闭加载Toast(向后兼容)
+
+返回类型:`void`
+
+##### TToast.dismissToast
+
+关闭指定的Toast
+
+返回类型:`void`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| toastId | String | - | - |
+
+
+##### TToast.showFail
+
+失败提示Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showIconText
+
+带图标的Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| icon | IconData? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showLoading
+
+带文案的加载Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| text | String? | - | - |
+| duration | Duration | const Duration(seconds: 99999999) | - |
+| preventTap | bool? | - | - |
+| customWidget | Widget? | - | - |
+| backgroundColor | Color? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showLoadingWithoutText
+
+不带文案的加载Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(seconds: 99999999) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showSuccess
+
+成功提示Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showText
+
+普通文本Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
+| --- | --- | --- | --- |
+| text | String? | - | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| maxLines | int? | - | - |
+| constraints | BoxConstraints? | - | - |
+| preventTap | bool? | - | - |
+| customWidget | Widget? | - | - |
+| backgroundColor | Color? | - | - |
+| textStyle | TextStyle? | - | - |
+| toastId | String? | - | - |
+
+
+##### TToast.showWarning
+
+警告Toast
+
+返回类型:`String`
+
+| 参数 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| dismissAll | void | - | 关闭所有Toast |
-| dismissLoading | void | - | 关闭加载Toast(向后兼容) |
-| dismissToast | void | required String toastId | 关闭指定的Toast |
-| showFail | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 失败提示Toast |
-| showIconText | String | required String? text, IconData? icon, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带图标的Toast |
-| showLoading | String | required BuildContext context, String? text, Duration duration, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 带文案的加载Toast |
-| showLoadingWithoutText | String | required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, double? iconSize, Color? iconColor, String? toastId | 不带文案的加载Toast |
-| showSuccess | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 成功提示Toast |
-| showText | String | required String? text, required BuildContext context, Duration duration, int? maxLines, BoxConstraints? constraints, bool? preventTap, Widget? customWidget, Color? backgroundColor, TextStyle? textStyle, String? toastId | 普通文本Toast |
-| showWarning | String | required String? text, IconTextDirection direction, required BuildContext context, Duration duration, bool? preventTap, Color? backgroundColor, int? maxLines, TextStyle? textStyle, double? iconSize, Color? iconColor, String? toastId | 警告Toast |
+| text | String? | - | - |
+| direction | IconTextDirection | IconTextDirection.horizontal | - |
+| context | BuildContext | - | - |
+| duration | Duration | const Duration(milliseconds: 3000) | - |
+| preventTap | bool? | - | - |
+| backgroundColor | Color? | - | - |
+| maxLines | int? | - | - |
+| textStyle | TextStyle? | - | - |
+| iconSize | double? | - | - |
+| iconColor | Color? | - | - |
+| toastId | String? | - | - |
 
 
 ### IconTextDirection

From 159ef4669658562044e5f2ad53657715f14639dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Tue, 26 May 2026 15:30:36 +0800
Subject: [PATCH 24/27] refactor(popup): simplify overlay closing rules and
 tighten validation

---
 .../code/popup._buildApiShowOverlayFalse.txt  |   1 -
 .../example/lib/page/t_popup_page.dart        |   1 -
 .../src/components/popup/_popup_route.dart    |   3 +-
 .../src/components/popup/t_popup_handle.dart  |  20 +--
 .../src/components/popup/t_popup_options.dart |  42 ++++--
 .../test/t_popup_coverage_test.dart           |   4 +-
 .../test/t_popup_options_test.dart            | 142 +++++++++++++++++-
 .../test/t_popup_route_test.dart              |   2 -
 tdesign-component/test/t_popup_test.dart      | 139 ++++++++++++++++-
 tdesign-site/src/popup/README.md              |  44 ++++--
 10 files changed, 347 insertions(+), 51 deletions(-)

diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
index adc6d1a52..ba67d4671 100644
--- a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
+++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
@@ -12,7 +12,6 @@
           options: TPopupOptions.bottom(
               height: 280,
               showOverlay: false,
-              closeOnOverlayClick: false,
               modal: true,
               // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
               titleWidget: const TText('无蒙层'),
diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart
index ee7fcc4b1..47e489e66 100644
--- a/tdesign-component/example/lib/page/t_popup_page.dart
+++ b/tdesign-component/example/lib/page/t_popup_page.dart
@@ -670,7 +670,6 @@ class TPopupPage extends StatelessWidget {
           options: TPopupOptions.bottom(
               height: 280,
               showOverlay: false,
-              closeOnOverlayClick: false,
               modal: true,
               // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
               titleWidget: const TText('无蒙层'),
diff --git a/tdesign-component/lib/src/components/popup/_popup_route.dart b/tdesign-component/lib/src/components/popup/_popup_route.dart
index cde58d72f..1efb0b507 100644
--- a/tdesign-component/lib/src/components/popup/_popup_route.dart
+++ b/tdesign-component/lib/src/components/popup/_popup_route.dart
@@ -13,8 +13,7 @@ class _PopupNavigatorRoute extends PopupRoute {
         );
 
   final TPopupOptions options;
-  final void Function(TPopupTrigger trigger, [Object? result])
-      onCloseWithTrigger;
+  final void Function(TPopupTrigger trigger) onCloseWithTrigger;
 
   late PopupLayout _layout;
   bool _animationListenerAttached = false;
diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
index 654351016..31b7044df 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
@@ -42,6 +42,8 @@ class TPopupHandle {
   ///
   /// 已展示时调用无副作用。Navigator 已销毁且未提供新 [context] 时,debug 下 assert,
   /// release 下静默返回。
+  ///
+  /// 配置非法时会直接抛出 [FlutterError],debug / release 行为一致。
   void open([BuildContext? context]) {
     if (isShowing) {
       return;
@@ -57,7 +59,10 @@ class TPopupHandle {
       return;
     }
 
-    options.assertPlacementParams();
+    final validationError = options._validatePlacementParams();
+    if (validationError != null) {
+      _throwPopupOptionsValidationError(validationError);
+    }
     final normalized = options.normalized();
 
     _isClosed = false;
@@ -65,7 +70,7 @@ class TPopupHandle {
 
     _PopupNavigatorRoute? route;
 
-    void closeWithTrigger(TPopupTrigger trigger, [Object? result]) {
+    void closeWithTrigger(TPopupTrigger trigger) {
       final currentRoute = route;
       if (!isShowing || currentRoute == null) {
         return;
@@ -74,7 +79,6 @@ class TPopupHandle {
         navigator: navigator,
         route: currentRoute,
         trigger: trigger,
-        result: result,
       );
     }
 
@@ -98,11 +102,9 @@ class TPopupHandle {
   /// 关闭当前展示的浮层;[TPopupOptions.onVisibleChange] 的 [TPopupTrigger] 为
   /// [TPopupTrigger.api]。
   ///
-  /// [result] 可选,作为 [Navigator.pop] 的返回值。
-  ///
   /// 已关闭或未展示时调用无副作用。
   /// 嵌套浮层场景下会关闭当前 handle 对应的那一层,而不会误关栈顶其它浮层。
-  void close([Object? result]) {
+  void close() {
     final route = _route;
     final navigator = route?.navigator ?? _lastNavigator;
     if (!isShowing || route == null || navigator == null) {
@@ -112,7 +114,6 @@ class TPopupHandle {
       navigator: navigator,
       route: route,
       trigger: TPopupTrigger.api,
-      result: result,
     );
   }
 
@@ -136,15 +137,14 @@ class TPopupHandle {
     required NavigatorState navigator,
     required _PopupNavigatorRoute route,
     required TPopupTrigger trigger,
-    Object? result,
   }) {
     _markClosing();
     route.fireCloseStart(trigger);
     if (identical(_PopupTracker.top(navigator), this)) {
-      navigator.pop(result);
+      navigator.pop();
       return;
     }
-    navigator.removeRoute(route, result);
+    navigator.removeRoute(route);
   }
 
   void _detachRoute(_PopupNavigatorRoute route) {
diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart
index 33b1f34a6..29efe7ac9 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_options.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart
@@ -3,6 +3,10 @@ part of 't_popup.dart';
 /// 用于 [TPopupOptions.copyWith] 区分"不传"与"显式 null"。
 const Object _unset = Object();
 
+Never _throwPopupOptionsValidationError(String error) {
+  throw FlutterError('TPopupOptions: $error');
+}
+
 /// [TPopup.show] 的配置对象。
 ///
 /// ## 如何创建
@@ -10,7 +14,7 @@ const Object _unset = Object();
 /// | 场景 | 推荐用法 |
 /// |------|----------|
 /// | 弹出方向已知 | [TPopupOptions.bottom]、[TPopupOptions.center]、[TPopupOptions.top]、[TPopupOptions.left]、[TPopupOptions.right] |
-/// | 方向由变量决定 | 默认构造并设置 [placement];Debug 下传错字段会抛 [FlutterError] |
+/// | 方向由变量决定 | 默认构造并设置 [placement];传错字段会在 [TPopup.show] / [TPopupHandle.open] 时抛 [FlutterError] |
 ///
 /// 命名工厂只暴露当前方向生效的字段(例如 [TPopupOptions.bottom] 无 [width] 参数)。
 ///
@@ -47,7 +51,7 @@ class TPopupOptions {
     this.radius,
     this.backgroundColor,
     this.showOverlay = true,
-    this.closeOnOverlayClick = true,
+    bool? closeOnOverlayClick,
     this.overlayColor,
     this.overlayOpacity,
     this.modal = true,
@@ -64,7 +68,7 @@ class TPopupOptions {
     this.onClosed,
     this.onVisibleChange,
     this.onOverlayClick,
-  });
+  }) : _closeOnOverlayClick = closeOnOverlayClick;
 
   /// 创建 [TPopupPlacement.bottom] 配置。
   ///
@@ -81,7 +85,7 @@ class TPopupOptions {
     double? radius,
     Color? backgroundColor,
     bool showOverlay = true,
-    bool closeOnOverlayClick = true,
+    bool? closeOnOverlayClick,
     Color? overlayColor,
     double? overlayOpacity,
     bool modal = true,
@@ -131,7 +135,7 @@ class TPopupOptions {
     double? radius,
     Color? backgroundColor,
     bool showOverlay = true,
-    bool closeOnOverlayClick = true,
+    bool? closeOnOverlayClick,
     Color? overlayColor,
     double? overlayOpacity,
     bool modal = true,
@@ -177,7 +181,7 @@ class TPopupOptions {
     double? radius,
     Color? backgroundColor,
     bool showOverlay = true,
-    bool closeOnOverlayClick = true,
+    bool? closeOnOverlayClick,
     Color? overlayColor,
     double? overlayOpacity,
     bool modal = true,
@@ -222,7 +226,7 @@ class TPopupOptions {
     double? radius,
     Color? backgroundColor,
     bool showOverlay = true,
-    bool closeOnOverlayClick = true,
+    bool? closeOnOverlayClick,
     Color? overlayColor,
     double? overlayOpacity,
     bool modal = true,
@@ -267,7 +271,7 @@ class TPopupOptions {
     double? radius,
     Color? backgroundColor,
     bool showOverlay = true,
-    bool closeOnOverlayClick = true,
+    bool? closeOnOverlayClick,
     Color? overlayColor,
     double? overlayOpacity,
     bool modal = true,
@@ -334,8 +338,12 @@ class TPopupOptions {
   /// 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。
   final bool showOverlay;
 
-  /// 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。
-  final bool closeOnOverlayClick;
+  final bool? _closeOnOverlayClick;
+
+  /// 点击可见蒙层是否关闭。
+  ///
+  /// 省略时默认跟随 [showOverlay]:显示蒙层时为 true,否则为 false。
+  bool get closeOnOverlayClick => _closeOnOverlayClick ?? showOverlay;
 
   /// 蒙层颜色,默认 black54。
   final Color? overlayColor;
@@ -410,7 +418,7 @@ class TPopupOptions {
     Object? radius = _unset,
     Object? backgroundColor = _unset,
     bool? showOverlay,
-    bool? closeOnOverlayClick,
+    Object? closeOnOverlayClick = _unset,
     Object? overlayColor = _unset,
     Object? overlayOpacity = _unset,
     bool? modal,
@@ -444,7 +452,9 @@ class TPopupOptions {
           ? this.backgroundColor
           : backgroundColor as Color?,
       showOverlay: showOverlay ?? this.showOverlay,
-      closeOnOverlayClick: closeOnOverlayClick ?? this.closeOnOverlayClick,
+      closeOnOverlayClick: identical(closeOnOverlayClick, _unset)
+          ? _closeOnOverlayClick
+          : closeOnOverlayClick as bool?,
       overlayColor: identical(overlayColor, _unset)
           ? this.overlayColor
           : overlayColor as Color?,
@@ -501,7 +511,7 @@ class TPopupOptions {
       radius: radius,
       backgroundColor: backgroundColor,
       showOverlay: showOverlay,
-      closeOnOverlayClick: closeOnOverlayClick,
+      closeOnOverlayClick: _closeOnOverlayClick,
       overlayColor: overlayColor,
       overlayOpacity: overlayOpacity,
       modal: modal,
@@ -574,7 +584,7 @@ class TPopupOptions {
     assert(() {
       final err = _validatePlacementParams();
       if (err != null) {
-        throw FlutterError('TPopupOptions: $err');
+        _throwPopupOptionsValidationError(err);
       }
       return true;
     }());
@@ -636,8 +646,8 @@ class TPopupOptions {
     if (showOverlay && !modal) {
       return 'showOverlay=true requires modal=true.';
     }
-    if (!showOverlay && closeOnOverlayClick) {
-      return 'closeOnOverlayClick requires showOverlay=true.';
+    if (!showOverlay && _closeOnOverlayClick == true) {
+      return 'closeOnOverlayClick=true requires showOverlay=true.';
     }
     return null;
   }
diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart
index 9a7408906..ee90023ab 100644
--- a/tdesign-component/test/t_popup_coverage_test.dart
+++ b/tdesign-component/test/t_popup_coverage_test.dart
@@ -580,7 +580,6 @@ void main() {
                 placement: TPopupPlacement.bottom,
                 height: 120,
                 showOverlay: false,
-                closeOnOverlayClick: false,
                 modal: false,
                 child: const SizedBox(height: 60)),
           );
@@ -628,7 +627,6 @@ void main() {
                 height: 100,
                 inset: const TPopupBottomInset(left: 16, right: 16),
                 showOverlay: false,
-                closeOnOverlayClick: false,
                 cancelBuilder: null,
                 confirmBuilder: null,
                 child: const SizedBox(height: 60)),
@@ -784,13 +782,13 @@ void main() {
         child: const SizedBox(),
         animationDuration: const Duration(milliseconds: 500),
         showOverlay: false,
-        closeOnOverlayClick: false,
         overlayOpacity: 0.5,
         destroyOnClose: true,
         onVisibleChange: (_, __) => visibleChanges++,
       );
       expect(opts.animationDuration, const Duration(milliseconds: 500));
       expect(opts.showOverlay, isFalse);
+      expect(opts.closeOnOverlayClick, isFalse);
       expect(opts.overlayOpacity, 0.5);
       expect(opts.destroyOnClose, isTrue);
       opts.onVisibleChange?.call(false, TPopupTrigger.api);
diff --git a/tdesign-component/test/t_popup_options_test.dart b/tdesign-component/test/t_popup_options_test.dart
index 97a8867af..55c7d4982 100644
--- a/tdesign-component/test/t_popup_options_test.dart
+++ b/tdesign-component/test/t_popup_options_test.dart
@@ -8,12 +8,98 @@ void main() {
       final options = TPopupOptions(child: const SizedBox()).normalized();
       expect(options.placement, TPopupPlacement.bottom);
       expect(options.modal, isTrue);
+      expect(options.closeOnOverlayClick, isTrue);
       expect(options.usesDefaultHeader, isTrue);
       expect(options.usesDefaultCancel, isTrue);
       expect(options.usesDefaultConfirm, isTrue);
       expect(options.titleWidget, isNull);
     });
 
+    test('showOverlay=false 且省略 closeOnOverlayClick 时默认按 false 解析', () {
+      final options = TPopupOptions(
+        child: const SizedBox(),
+        showOverlay: false,
+      ).normalized();
+      expect(options.closeOnOverlayClick, isFalse);
+    });
+
+    test('模态与蒙层合法组合矩阵可通过校验并解析默认关闭行为', () {
+      void expectValid(
+        String reason,
+        TPopupOptions options, {
+        required bool expectedCloseOnOverlayClick,
+      }) {
+        expect(
+          () => options.assertPlacementParams(),
+          returnsNormally,
+          reason: reason,
+        );
+        expect(
+          options.closeOnOverlayClick,
+          expectedCloseOnOverlayClick,
+          reason: reason,
+        );
+      }
+
+      expectValid(
+        '标准模态 + 默认蒙层关闭',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: true,
+          modal: true,
+        ),
+        expectedCloseOnOverlayClick: true,
+      );
+      expectValid(
+        '标准模态 + 显式禁止蒙层关闭',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: true,
+          modal: true,
+          closeOnOverlayClick: false,
+        ),
+        expectedCloseOnOverlayClick: false,
+      );
+      expectValid(
+        '透明模态 + 默认不允许蒙层关闭',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          modal: true,
+        ),
+        expectedCloseOnOverlayClick: false,
+      );
+      expectValid(
+        '透明模态 + 显式 false 仍合法',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          modal: true,
+          closeOnOverlayClick: false,
+        ),
+        expectedCloseOnOverlayClick: false,
+      );
+      expectValid(
+        '非模态浮层 + 默认不允许蒙层关闭',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          modal: false,
+        ),
+        expectedCloseOnOverlayClick: false,
+      );
+      expectValid(
+        '非模态浮层 + 显式 false 仍合法',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          modal: false,
+          closeOnOverlayClick: false,
+        ),
+        expectedCloseOnOverlayClick: false,
+      );
+    });
+
     test('bottom 默认走内置三段式(useDefaultHeader)', () {
       final options = TPopupOptions(
         child: const SizedBox(),
@@ -185,6 +271,52 @@ void main() {
       );
     });
 
+    test('模态与蒙层非法组合矩阵全部抛 FlutterError', () {
+      void expectInvalid(String reason, TPopupOptions options) {
+        expect(
+          () => options.assertPlacementParams(),
+          throwsA(isA()),
+          reason: reason,
+        );
+      }
+
+      expectInvalid(
+        '有蒙层但非模态(默认蒙层关闭值)',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: true,
+          modal: false,
+        ),
+      );
+      expectInvalid(
+        '有蒙层但非模态(显式 false)',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: true,
+          modal: false,
+          closeOnOverlayClick: false,
+        ),
+      );
+      expectInvalid(
+        '透明模态下显式要求蒙层关闭',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          modal: true,
+          closeOnOverlayClick: true,
+        ),
+      );
+      expectInvalid(
+        '非模态浮层下显式要求蒙层关闭',
+        TPopupOptions(
+          child: const SizedBox(),
+          showOverlay: false,
+          modal: false,
+          closeOnOverlayClick: true,
+        ),
+      );
+    });
+
     test('assertPlacementParams 各 placement 的 inset 类型错位也抛错', () {
       expect(
         () => TPopupOptions(
@@ -239,11 +371,19 @@ void main() {
         () => TPopupOptions(
           child: const SizedBox(),
           showOverlay: false,
-          closeOnOverlayClick: false,
           modal: false,
         ).assertPlacementParams(),
         returnsNormally,
       );
     });
+
+    test('copyWith(closeOnOverlayClick: null) 可恢复为跟随 showOverlay 的默认值', () {
+      final options = TPopupOptions(
+        child: const SizedBox(),
+        showOverlay: false,
+        closeOnOverlayClick: false,
+      ).copyWith(closeOnOverlayClick: null);
+      expect(options.closeOnOverlayClick, isFalse);
+    });
   });
 }
diff --git a/tdesign-component/test/t_popup_route_test.dart b/tdesign-component/test/t_popup_route_test.dart
index 2aeaf9ab0..93253f90b 100644
--- a/tdesign-component/test/t_popup_route_test.dart
+++ b/tdesign-component/test/t_popup_route_test.dart
@@ -44,7 +44,6 @@ void main() {
                             options: TPopupOptions.bottom(
                               height: 120,
                               showOverlay: false,
-                              closeOnOverlayClick: false,
                               modal: true,
                               cancelBuilder: null,
                               confirmBuilder: null,
@@ -115,7 +114,6 @@ void main() {
                             options: TPopupOptions.bottom(
                               height: 120,
                               showOverlay: false,
-                              closeOnOverlayClick: false,
                               modal: false,
                               cancelBuilder: null,
                               confirmBuilder: null,
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index 9fe245922..526ea7d6c 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -635,7 +635,6 @@ void main() {
                 placement: TPopupPlacement.bottom,
                 height: 100,
                 showOverlay: false,
-                closeOnOverlayClick: false,
                 modal: true,
                 child: const SizedBox(height: 60)),
           );
@@ -648,6 +647,85 @@ void main() {
       );
     });
 
+    testWidgets('模态与蒙层合法组合矩阵都可正常 show / close', (tester) async {
+      late BuildContext hostContext;
+
+      await tester.pumpWidget(
+        wrapPopupTest(
+          Builder(
+            builder: (context) {
+              hostContext = context;
+              return const SizedBox.shrink();
+            },
+          ),
+        ),
+      );
+
+      Future expectShowAndClose(
+        String label,
+        TPopupOptions options,
+      ) async {
+        late TPopupHandle handle;
+        expect(
+          () => handle = TPopup.show(hostContext, options: options),
+          returnsNormally,
+          reason: label,
+        );
+        await tester.pumpAndSettle();
+        expect(find.text(label), findsOneWidget, reason: label);
+
+        handle.close();
+        await tester.pumpAndSettle();
+        expect(find.text(label), findsNothing, reason: label);
+      }
+
+      await expectShowAndClose(
+        '标准模态默认关闭',
+        TPopupOptions.bottom(
+          height: 100,
+          showOverlay: true,
+          modal: true,
+          cancelBuilder: null,
+          confirmBuilder: null,
+          child: const SizedBox(height: 60, child: Text('标准模态默认关闭')),
+        ),
+      );
+      await expectShowAndClose(
+        '标准模态显式禁止蒙层关闭',
+        TPopupOptions.bottom(
+          height: 100,
+          showOverlay: true,
+          modal: true,
+          closeOnOverlayClick: false,
+          cancelBuilder: null,
+          confirmBuilder: null,
+          child: const SizedBox(height: 60, child: Text('标准模态显式禁止蒙层关闭')),
+        ),
+      );
+      await expectShowAndClose(
+        '透明模态默认关闭策略',
+        TPopupOptions.bottom(
+          height: 100,
+          showOverlay: false,
+          modal: true,
+          cancelBuilder: null,
+          confirmBuilder: null,
+          child: const SizedBox(height: 60, child: Text('透明模态默认关闭策略')),
+        ),
+      );
+      await expectShowAndClose(
+        '非模态浮层默认关闭策略',
+        TPopupOptions.bottom(
+          height: 100,
+          showOverlay: false,
+          modal: false,
+          cancelBuilder: null,
+          confirmBuilder: null,
+          child: const SizedBox(height: 60, child: Text('非模态浮层默认关闭策略')),
+        ),
+      );
+    });
+
     testWidgets('overlayOpacity 与自定义颜色', (tester) async {
       await openPopup(
         tester,
@@ -914,6 +992,65 @@ void main() {
       await tester.pumpAndSettle();
       expect(handle!.isShowing, isTrue);
     });
+
+    testWidgets('非法参数组合矩阵在 show 时直接抛 FlutterError', (tester) async {
+      late BuildContext hostContext;
+
+      await tester.pumpWidget(
+        wrapPopupTest(
+          Builder(
+            builder: (context) {
+              hostContext = context;
+              return const SizedBox.shrink();
+            },
+          ),
+        ),
+      );
+
+      void expectInvalidShow(String reason, TPopupOptions options) {
+        expect(
+          () => TPopup.show(hostContext, options: options),
+          throwsA(isA()),
+          reason: reason,
+        );
+      }
+
+      expectInvalidShow(
+        '有蒙层但非模态(默认关闭策略)',
+        TPopupOptions.bottom(
+          child: const SizedBox(height: 40),
+          showOverlay: true,
+          modal: false,
+        ),
+      );
+      expectInvalidShow(
+        '有蒙层但非模态(显式 false)',
+        TPopupOptions.bottom(
+          child: const SizedBox(height: 40),
+          showOverlay: true,
+          modal: false,
+          closeOnOverlayClick: false,
+        ),
+      );
+      expectInvalidShow(
+        '透明模态显式要求蒙层关闭',
+        TPopupOptions.bottom(
+          child: const SizedBox(height: 40),
+          showOverlay: false,
+          modal: true,
+          closeOnOverlayClick: true,
+        ),
+      );
+      expectInvalidShow(
+        '非模态浮层显式要求蒙层关闭',
+        TPopupOptions.bottom(
+          child: const SizedBox(height: 40),
+          showOverlay: false,
+          modal: false,
+          closeOnOverlayClick: true,
+        ),
+      );
+    });
   });
 
   group('TPopup 扩展场景', () {
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 8a706ea82..ec0a431ec 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -461,7 +461,6 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
           options: TPopupOptions.bottom(
               height: 280,
               showOverlay: false,
-              closeOnOverlayClick: false,
               modal: true,
               // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
               titleWidget: const TText('无蒙层'),
@@ -603,7 +602,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  | 场景 | 推荐用法 |
  |------|----------|
  | 弹出方向已知 | [TPopupOptions.bottom]、[TPopupOptions.center]、[TPopupOptions.top]、[TPopupOptions.left]、[TPopupOptions.right] |
- | 方向由变量决定 | 默认构造并设置 [placement];Debug 下传错字段会抛 [FlutterError] |
+| 方向由变量决定 | 默认构造并设置 [placement];传错字段会在 [TPopup.show] / [TPopupHandle.open] 时抛 [FlutterError] |
 
  命名工厂只暴露当前方向生效的字段(例如 [TPopupOptions.bottom] 无 [width] 参数)。
 
@@ -626,7 +625,18 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
  [titleWidget] 默认为 `null`,表示无标题内容。
 
- 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
+生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
+
+## 模态与蒙层
+
+| 参数组合 | 效果 |
+|----------|------|
+| `modal: true, showOverlay: true` | 标准模态弹层:显示遮罩,阻断背景交互与底层语义/焦点 |
+| `modal: true, showOverlay: false` | 透明模态弹层:不显示遮罩,但仍阻断背景交互与底层语义/焦点 |
+| `modal: false, showOverlay: false` | 非模态浮层:不显示遮罩,允许背景继续交互 |
+
+`showOverlay: true, modal: false` 不属于支持组合;`showOverlay: false` 时 `closeOnOverlayClick` 默认按 `false` 处理,若显式传 `true` 则属于非法组合。
+非法组合不会被静默纠正,`show()` / `handle.open()` 时会直接抛出 `FlutterError`。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -636,7 +646,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
 | child | Widget | - | 浮层主体内容(必填)。 |
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
@@ -678,8 +688,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -707,8 +717,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -735,8 +745,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupLeftInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -763,8 +773,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupRightInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -791,8 +801,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupTopInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
+| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -824,11 +834,17 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 属性 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
+| isShowing | bool | - | 浮层是否仍在展示(路由在栈中且未进入关闭流程)。 |
 | navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
 | options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
 | useRootNavigator | bool | - | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
 
+#### 实例方法
 
+| 名称 | 返回类型 | 参数 | 说明 |
+| --- | --- | --- | --- |
+| open | void | BuildContext? context | 打开或重新打开浮层。首次调用须能解析 [Navigator](传入 [context] 或依赖 [navigatorContext]);后续可省略以复用缓存的 [NavigatorState]。 |
+| close | void | - | 关闭当前展示的浮层;[TPopupOptions.onVisibleChange] 的 [TPopupTrigger] 为 [TPopupTrigger.api]。 |
 #### 工厂构造方法
 
 ##### TPopupHandle._

From 68fa06ef0aadb5f0b6bd74e8dc0c103a30ccdaa4 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Tue, 26 May 2026 07:39:43 +0000
Subject: [PATCH 25/27] [autofix.ci] apply automated fixes

---
 .../example/assets/api/popup_api.md           | 14 +++---
 tdesign-site/src/popup/README.md              | 43 ++++++-------------
 2 files changed, 20 insertions(+), 37 deletions(-)

diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md
index d3d7559b3..bc7ef13c8 100644
--- a/tdesign-component/example/assets/api/popup_api.md
+++ b/tdesign-component/example/assets/api/popup_api.md
@@ -63,7 +63,7 @@
  | 场景 | 推荐用法 |
  |------|----------|
  | 弹出方向已知 | [TPopupOptions.bottom]、[TPopupOptions.center]、[TPopupOptions.top]、[TPopupOptions.left]、[TPopupOptions.right] |
- | 方向由变量决定 | 默认构造并设置 [placement];Debug 下传错字段会抛 [FlutterError] |
+ | 方向由变量决定 | 默认构造并设置 [placement];传错字段会在 [TPopup.show] / [TPopupHandle.open] 时抛 [FlutterError] |
 
  命名工厂只暴露当前方向生效的字段(例如 [TPopupOptions.bottom] 无 [width] 参数)。
 
@@ -96,7 +96,7 @@
 | cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
 | child | Widget | - | 浮层主体内容(必填)。 |
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool? | - | - |
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
@@ -139,7 +139,7 @@
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -168,7 +168,7 @@
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -196,7 +196,7 @@
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -224,7 +224,7 @@
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -252,7 +252,7 @@
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
 | showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
-| closeOnOverlayClick | bool | true | 点击可见蒙层是否关闭(须 [showOverlay] 为 true)。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index ec0a431ec..62fd185b1 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -602,7 +602,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  | 场景 | 推荐用法 |
  |------|----------|
  | 弹出方向已知 | [TPopupOptions.bottom]、[TPopupOptions.center]、[TPopupOptions.top]、[TPopupOptions.left]、[TPopupOptions.right] |
-| 方向由变量决定 | 默认构造并设置 [placement];传错字段会在 [TPopup.show] / [TPopupHandle.open] 时抛 [FlutterError] |
+ | 方向由变量决定 | 默认构造并设置 [placement];传错字段会在 [TPopup.show] / [TPopupHandle.open] 时抛 [FlutterError] |
 
  命名工厂只暴露当前方向生效的字段(例如 [TPopupOptions.bottom] 无 [width] 参数)。
 
@@ -625,18 +625,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
  [titleWidget] 默认为 `null`,表示无标题内容。
 
-生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
-
-## 模态与蒙层
-
-| 参数组合 | 效果 |
-|----------|------|
-| `modal: true, showOverlay: true` | 标准模态弹层:显示遮罩,阻断背景交互与底层语义/焦点 |
-| `modal: true, showOverlay: false` | 透明模态弹层:不显示遮罩,但仍阻断背景交互与底层语义/焦点 |
-| `modal: false, showOverlay: false` | 非模态浮层:不显示遮罩,允许背景继续交互 |
-
-`showOverlay: true, modal: false` 不属于支持组合;`showOverlay: false` 时 `closeOnOverlayClick` 默认按 `false` 处理,若显式传 `true` 则属于非法组合。
-非法组合不会被静默纠正,`show()` / `handle.open()` 时会直接抛出 `FlutterError`。
+ 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -646,7 +635,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | cancelBuilder | TPopupSlotBuilder? | _kPopupDefaultCancel | bottom 左侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「取消」,点击触发 [TPopupTrigger.cancel]。 |
 | child | Widget | - | 浮层主体内容(必填)。 |
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
-| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
+| closeOnOverlayClick | bool? | - | - |
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | destroyOnClose | bool | false | 为 true 时路由 `maintainState` 为 false,关闭后不保留路由内 State。 |
 | headerBuilder | TPopupHeaderBuilder? | _kPopupDefaultHeader | bottom 头部;仅 [TPopupPlacement.bottom] 生效。三态见类文档「Builder 三态」。 自定义时忽略 [titleWidget]、[cancelBuilder]、[confirmBuilder]。 |
@@ -688,8 +677,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | confirmBuilder | TPopupSlotBuilder? | _kPopupDefaultConfirm | bottom 右侧操作槽;仅 [headerBuilder] 为内置默认时生效。 内置默认为「确定」,点击触发 [TPopupTrigger.confirm]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -717,8 +706,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | closeBuilder | TPopupSlotBuilder? | _kPopupDefaultClose | center 面板外下方关闭区;仅 [TPopupPlacement.center] 生效。三态见类文档「Builder 三态」。 内置默认点击触发 [TPopupTrigger.close]。 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -745,8 +734,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupLeftInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -773,8 +762,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupRightInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -801,8 +790,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupTopInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | radius | double? | - | 内容区圆角,默认主题大圆角。 |
 | backgroundColor | Color? | - | 内容区背景色,默认主题容器色。 |
-| showOverlay | bool | true | 是否绘制半透明蒙层;为 false 时须保留其它关闭入口。 |
-| closeOnOverlayClick | bool? | 跟随 showOverlay | 点击可见蒙层是否关闭;省略时显示蒙层为 true,否则为 false。 |
+| showOverlay | bool | true | 是否绘制半透明蒙层。 当 [modal] 为 true 且此值为 false 时,为“透明模态弹层”。 |
+| closeOnOverlayClick | bool? | - | - |
 | overlayColor | Color? | - | 蒙层颜色,默认 black54。 |
 | overlayOpacity | double? | - | 蒙层透明度系数(0–1),与 [overlayColor] 的 alpha 相乘后用于绘制。 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
@@ -834,17 +823,11 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 
 | 属性 | 类型 | 默认值 | 说明 |
 | --- | --- | --- | --- |
-| isShowing | bool | - | 浮层是否仍在展示(路由在栈中且未进入关闭流程)。 |
 | navigatorContext | BuildContext? | - | 与 [TPopup.show] 的 [navigatorContext] 相同。 |
 | options | TPopupOptions | - | 创建时传入的配置;每次 [open] 会按 [TPopupOptions.placement] 裁剪无效字段后使用。 |
 | useRootNavigator | bool | - | 与 [TPopup.show] 的 [useRootNavigator] 相同。 |
 
-#### 实例方法
 
-| 名称 | 返回类型 | 参数 | 说明 |
-| --- | --- | --- | --- |
-| open | void | BuildContext? context | 打开或重新打开浮层。首次调用须能解析 [Navigator](传入 [context] 或依赖 [navigatorContext]);后续可省略以复用缓存的 [NavigatorState]。 |
-| close | void | - | 关闭当前展示的浮层;[TPopupOptions.onVisibleChange] 的 [TPopupTrigger] 为 [TPopupTrigger.api]。 |
 #### 工厂构造方法
 
 ##### TPopupHandle._

From 62fbe8761e602b94798253abeb354a7bb57a905f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=98=BF=E8=8F=9C=20Cai?= 
Date: Tue, 26 May 2026 16:04:36 +0800
Subject: [PATCH 26/27] refactor(popup): update text and comments for clarity
 in overlay demo and documentation

---
 .../code/popup._buildApiShowOverlayFalse.txt  |   6 +-
 .../example/lib/page/t_popup_page.dart        |   6 +-
 .../src/components/popup/t_popup_handle.dart  |  28 +++-
 .../src/components/popup/t_popup_options.dart |   4 +-
 .../test/t_popup_coverage_test.dart           |   6 +-
 tdesign-component/test/t_popup_test.dart      | 150 +++++++++++++++++-
 tdesign-site/src/popup/README.md              |  22 +--
 7 files changed, 194 insertions(+), 28 deletions(-)

diff --git a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
index ba67d4671..58776fda0 100644
--- a/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
+++ b/tdesign-component/example/assets/code/popup._buildApiShowOverlayFalse.txt
@@ -1,7 +1,7 @@
 
   Widget _buildApiShowOverlayFalse(BuildContext context) {
     return TButton(
-      text: 'showOverlay: false(无蒙层)',
+      text: 'showOverlay: false(透明模态)',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
@@ -13,8 +13,8 @@
               height: 280,
               showOverlay: false,
               modal: true,
-              // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
-              titleWidget: const TText('无蒙层'),
+              // 不显示可见蒙层,但仍阻断背景交互;须保留其它关闭入口。
+              titleWidget: const TText('透明模态'),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,
diff --git a/tdesign-component/example/lib/page/t_popup_page.dart b/tdesign-component/example/lib/page/t_popup_page.dart
index 47e489e66..898800b37 100644
--- a/tdesign-component/example/lib/page/t_popup_page.dart
+++ b/tdesign-component/example/lib/page/t_popup_page.dart
@@ -659,7 +659,7 @@ class TPopupPage extends StatelessWidget {
   @Demo(group: 'popup')
   Widget _buildApiShowOverlayFalse(BuildContext context) {
     return TButton(
-      text: 'showOverlay: false(无蒙层)',
+      text: 'showOverlay: false(透明模态)',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
@@ -671,8 +671,8 @@ class TPopupPage extends StatelessWidget {
               height: 280,
               showOverlay: false,
               modal: true,
-              // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
-              titleWidget: const TText('无蒙层'),
+              // 不显示可见蒙层,但仍阻断背景交互;须保留其它关闭入口。
+              titleWidget: const TText('透明模态'),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,
diff --git a/tdesign-component/lib/src/components/popup/t_popup_handle.dart b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
index 31b7044df..f64a9bace 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_handle.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_handle.dart
@@ -31,6 +31,7 @@ class TPopupHandle {
   _PopupNavigatorRoute? _route;
   NavigatorState? _lastNavigator;
   bool _isClosed = false;
+  int _openEpoch = 0;
 
   /// 浮层是否仍在展示(路由在栈中且未进入关闭流程)。
   bool get isShowing => _route != null && !_isClosed;
@@ -38,7 +39,7 @@ class TPopupHandle {
   /// 打开或重新打开浮层。
   ///
   /// [context] 可选。首次调用须能解析 [Navigator](传入 [context] 或依赖
-  /// [navigatorContext]);后续可省略以复用缓存的 [NavigatorState]。
+  /// [navigatorContext]);后续可省略,优先复用缓存的 [NavigatorState]。
   ///
   /// 已展示时调用无副作用。Navigator 已销毁且未提供新 [context] 时,debug 下 assert,
   /// release 下静默返回。
@@ -64,6 +65,8 @@ class TPopupHandle {
       _throwPopupOptionsValidationError(validationError);
     }
     final normalized = options.normalized();
+    final onClosed = normalized.onClosed;
+    final openEpoch = ++_openEpoch;
 
     _isClosed = false;
     _lastNavigator = navigator;
@@ -83,7 +86,13 @@ class TPopupHandle {
     }
 
     route = _PopupNavigatorRoute(
-      options: normalized,
+      options: normalized.copyWith(
+        onClosed: () {
+          if (_openEpoch == openEpoch) {
+            onClosed?.call();
+          }
+        },
+      ),
       onCloseWithTrigger: closeWithTrigger,
     );
     _route = route;
@@ -118,15 +127,22 @@ class TPopupHandle {
   }
 
   NavigatorState? _resolveNavigator(BuildContext? context) {
-    final ctx = context ?? navigatorContext;
-    if (ctx != null) {
-      return Navigator.maybeOf(ctx, rootNavigator: useRootNavigator);
+    final explicitNavigator = _navigatorFromContext(context);
+    if (explicitNavigator != null) {
+      return explicitNavigator;
     }
     final cached = _lastNavigator;
     if (cached != null && cached.mounted) {
       return cached;
     }
-    return null;
+    return _navigatorFromContext(navigatorContext);
+  }
+
+  NavigatorState? _navigatorFromContext(BuildContext? context) {
+    if (context == null || !context.mounted) {
+      return null;
+    }
+    return Navigator.maybeOf(context, rootNavigator: useRootNavigator);
   }
 
   void _markClosing() {
diff --git a/tdesign-component/lib/src/components/popup/t_popup_options.dart b/tdesign-component/lib/src/components/popup/t_popup_options.dart
index 29efe7ac9..c64623ef0 100644
--- a/tdesign-component/lib/src/components/popup/t_popup_options.dart
+++ b/tdesign-component/lib/src/components/popup/t_popup_options.dart
@@ -397,7 +397,9 @@ class TPopupOptions {
   /// 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。
   final VoidCallback? onClose;
 
-  /// 路由 pop 且关闭动画结束。
+  /// 当前展示周期真正结束。
+  ///
+  /// 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。
   final VoidCallback? onClosed;
 
   /// 显隐变化;第二个参数为 [TPopupTrigger]。
diff --git a/tdesign-component/test/t_popup_coverage_test.dart b/tdesign-component/test/t_popup_coverage_test.dart
index ee90023ab..295d6cb84 100644
--- a/tdesign-component/test/t_popup_coverage_test.dart
+++ b/tdesign-component/test/t_popup_coverage_test.dart
@@ -33,7 +33,7 @@ void main() {
       expect(find.text('确定'), findsNothing);
     });
 
-    testWidgets('bottom 仅标题且 titleAlignLeft', (tester) async {
+    testWidgets('bottom 仅标题时可正常渲染标题内容', (tester) async {
       await openPopup(
         tester,
         onPressed: () {
@@ -366,7 +366,7 @@ void main() {
   });
 
   group('TPopup 覆盖率深化', () {
-    testWidgets('headerBuilder 透传 titleWidget 与 cancelBuilder 槽位',
+    testWidgets('headerBuilder 自定义内容可正常渲染',
         (tester) async {
       await openPopup(
         tester,
@@ -397,7 +397,7 @@ void main() {
       expect(find.text('builder右'), findsOneWidget);
     });
 
-    testWidgets('headerBuilder 使用自定义 cancel/confirm Widget 槽位', (tester) async {
+    testWidgets('headerBuilder 自定义行内内容可正常渲染', (tester) async {
       await openPopup(
         tester,
         onPressed: () {
diff --git a/tdesign-component/test/t_popup_test.dart b/tdesign-component/test/t_popup_test.dart
index 526ea7d6c..1191a3c03 100644
--- a/tdesign-component/test/t_popup_test.dart
+++ b/tdesign-component/test/t_popup_test.dart
@@ -874,8 +874,9 @@ void main() {
       expect(find.text('inner'), findsNothing);
     });
 
-    testWidgets('系统返回键关闭', (tester) async {
+    testWidgets('系统返回键关闭并上报 systemBack trigger', (tester) async {
       var closedCount = 0;
+      TPopupTrigger? hideTrigger;
       late BuildContext hostContext;
 
       await openPopup(
@@ -888,6 +889,11 @@ void main() {
                 placement: TPopupPlacement.bottom,
                 height: 100,
                 onClosed: () => closedCount++,
+                onVisibleChange: (visible, trigger) {
+                  if (!visible) {
+                    hideTrigger = trigger;
+                  }
+                },
                 child: const SizedBox(height: 60)),
           );
         },
@@ -896,6 +902,7 @@ void main() {
       await tester.binding.handlePopRoute();
       await tester.pumpAndSettle();
       expect(closedCount, 1);
+      expect(hideTrigger, TPopupTrigger.systemBack);
     });
 
     testWidgets('handle.close 后 handle.open 可再次展示', (tester) async {
@@ -934,6 +941,8 @@ void main() {
     testWidgets('关闭动画未结束时重新 open 不会被旧 route 回调误清理', (tester) async {
       late BuildContext hostContext;
       TPopupHandle? handle;
+      var openCount = 0;
+      var closedCount = 0;
 
       await openPopup(
         tester,
@@ -946,6 +955,8 @@ void main() {
               cancelBuilder: null,
               confirmBuilder: null,
               animationDuration: const Duration(milliseconds: 300),
+              onOpen: () => openCount++,
+              onClosed: () => closedCount++,
               child: const SizedBox(height: 60, child: Text('race panel')),
             ),
           );
@@ -953,6 +964,8 @@ void main() {
       );
       await tester.pumpAndSettle();
       expect(handle!.isShowing, isTrue);
+      expect(openCount, 1);
+      expect(closedCount, 0);
 
       handle!.close();
       await tester.pump(const Duration(milliseconds: 100));
@@ -960,10 +973,145 @@ void main() {
       handle!.open(hostContext);
       await tester.pump();
       expect(handle!.isShowing, isTrue);
+      expect(openCount, 2);
 
       await tester.pumpAndSettle();
       expect(handle!.isShowing, isTrue);
       expect(find.text('race panel'), findsOneWidget);
+      expect(closedCount, 0);
+
+      handle!.close();
+      await tester.pumpAndSettle();
+      expect(closedCount, 1);
+    });
+
+    testWidgets('navigatorContext 失效后 handle.open 仍优先复用缓存 navigator',
+        (tester) async {
+      TPopupHandle? handle;
+      var showLauncher = true;
+
+      await tester.pumpWidget(
+        MaterialApp(
+          home: TTheme(
+            data: TThemeData.defaultData(),
+            child: StatefulBuilder(
+              builder: (context, setState) {
+                return Scaffold(
+                  body: Column(
+                    children: [
+                      if (showLauncher)
+                        Builder(
+                          builder: (launcherContext) {
+                            return ElevatedButton(
+                              onPressed: () {
+                                handle = TPopup.show(
+                                  launcherContext,
+                                  navigatorContext: launcherContext,
+                                  options: TPopupOptions.bottom(
+                                    height: 100,
+                                    cancelBuilder: null,
+                                    confirmBuilder: null,
+                                    child: const SizedBox(
+                                      height: 60,
+                                      child: Text('cached navigator popup'),
+                                    ),
+                                  ),
+                                );
+                              },
+                              child: const Text('open popup'),
+                            );
+                          },
+                        ),
+                      ElevatedButton(
+                        onPressed: () {
+                          setState(() => showLauncher = false);
+                        },
+                        child: const Text('dispose launcher'),
+                      ),
+                    ],
+                  ),
+                );
+              },
+            ),
+          ),
+        ),
+      );
+      await tester.tap(find.text('open popup'));
+      await tester.pumpAndSettle();
+      expect(find.text('cached navigator popup'), findsOneWidget);
+
+      handle!.close();
+      await tester.pumpAndSettle();
+      expect(find.text('cached navigator popup'), findsNothing);
+
+      await tester.tap(find.text('dispose launcher'));
+      await tester.pumpAndSettle();
+
+      expect(() => handle!.open(), returnsNormally);
+      await tester.pumpAndSettle();
+      expect(handle!.isShowing, isTrue);
+      expect(find.text('cached navigator popup'), findsOneWidget);
+    });
+
+    testWidgets('非栈顶 handle.close 会立即移除 route 并触发 onClosed', (tester) async {
+      TPopupHandle? outerHandle;
+      TPopupHandle? innerHandle;
+      var outerClosedCount = 0;
+
+      await openPopup(
+        tester,
+        onPressed: () {
+          outerHandle = TPopup.show(
+            tester.element(find.text('open')),
+            options: TPopupOptions.bottom(
+              height: 160,
+              animationDuration: const Duration(milliseconds: 300),
+              cancelBuilder: null,
+              confirmBuilder: null,
+              onClosed: () => outerClosedCount++,
+              child: Builder(
+                builder: (ctx) {
+                  return Column(
+                    children: [
+                      const Text('outer immediate remove'),
+                      ElevatedButton(
+                        onPressed: () {
+                          innerHandle = TPopup.show(
+                            ctx,
+                            options: TPopupOptions.center(
+                              width: 120,
+                              height: 80,
+                              closeBuilder: null,
+                              child: const SizedBox(
+                                width: 120,
+                                height: 80,
+                                child: Text('inner immediate remove'),
+                              ),
+                            ),
+                          );
+                        },
+                        child: const Text('open inner immediate'),
+                      ),
+                    ],
+                  );
+                },
+              ),
+            ),
+          );
+        },
+      );
+      await tester.pumpAndSettle();
+      await tester.tap(find.text('open inner immediate'));
+      await tester.pumpAndSettle();
+
+      outerHandle!.close();
+      await tester.pump();
+      expect(find.text('outer immediate remove'), findsNothing);
+      expect(find.text('inner immediate remove'), findsOneWidget);
+      expect(outerClosedCount, 1);
+
+      innerHandle!.close();
+      await tester.pumpAndSettle();
     });
 
     testWidgets('handle.open 在已展示时无副作用', (tester) async {
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index 62fd185b1..fa11a0e4a 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -450,7 +450,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
   
   Widget _buildApiShowOverlayFalse(BuildContext context) {
     return TButton(
-      text: 'showOverlay: false(无蒙层)',
+      text: 'showOverlay: false(透明模态)',
       isBlock: true,
       theme: TButtonTheme.primary,
       type: TButtonType.outline,
@@ -462,8 +462,8 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
               height: 280,
               showOverlay: false,
               modal: true,
-              // 无蒙层但仍保持模态;须保留操作栏取消(或其它关闭入口)
-              titleWidget: const TText('无蒙层'),
+              // 不显示可见蒙层,但仍阻断背景交互;须保留其它关闭入口。
+              titleWidget: const TText('透明模态'),
               child: Container(
                 height: 200,
                 color: TTheme.of(context).bgColorContainer,
@@ -623,9 +623,9 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  | 显式 `null` | 隐藏该区域 |
  | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;需自行提供交互与语义,可调用 `close` 关闭浮层 |
 
- [titleWidget] 默认为 `null`,表示无标题内容。
+[titleWidget] 默认为 `null`,表示无标题内容。
 
- 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
+生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。其中 [onClosed] 表示当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -643,7 +643,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
@@ -687,7 +687,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -716,7 +716,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -744,7 +744,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -772,7 +772,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -800,7 +800,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 

From 476e9d6426227d1108cccc6d9a13fb284979cba0 Mon Sep 17 00:00:00 2001
From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com>
Date: Tue, 26 May 2026 09:22:44 +0000
Subject: [PATCH 27/27] [autofix.ci] apply automated fixes

---
 .../example/assets/api/popup_api.md              | 12 ++++++------
 tdesign-site/src/popup/README.md                 | 16 ++++++++--------
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/tdesign-component/example/assets/api/popup_api.md b/tdesign-component/example/assets/api/popup_api.md
index bc7ef13c8..9ff884bdf 100644
--- a/tdesign-component/example/assets/api/popup_api.md
+++ b/tdesign-component/example/assets/api/popup_api.md
@@ -104,7 +104,7 @@
 | inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
@@ -148,7 +148,7 @@
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -177,7 +177,7 @@
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -205,7 +205,7 @@
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -233,7 +233,7 @@
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -261,7 +261,7 @@
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 路由 pop 且关闭动画结束。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
diff --git a/tdesign-site/src/popup/README.md b/tdesign-site/src/popup/README.md
index fa11a0e4a..b9588e4f2 100644
--- a/tdesign-site/src/popup/README.md
+++ b/tdesign-site/src/popup/README.md
@@ -623,9 +623,9 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
  | 显式 `null` | 隐藏该区域 |
  | 自定义 [TPopupHeaderBuilder] / [TPopupSlotBuilder] | 完全替换;需自行提供交互与语义,可调用 `close` 关闭浮层 |
 
-[titleWidget] 默认为 `null`,表示无标题内容。
+ [titleWidget] 默认为 `null`,表示无标题内容。
 
-生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。其中 [onClosed] 表示当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。
+ 生命周期回调见 [onOpen]、[onOpened]、[onClose]、[onClosed]、[onVisibleChange]、[onOverlayClick]。
 #### 默认构造方法
 
 | 参数 | 类型 | 默认值 | 说明 |
@@ -643,7 +643,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | inset | TPopupInset? | - | 交叉轴边缘留白;具体类型由 [placement] 决定。 * [TPopupPlacement.bottom] 使用 [TPopupBottomInset] * [TPopupPlacement.top] 使用 [TPopupTopInset] * [TPopupPlacement.left] 使用 [TPopupLeftInset] * [TPopupPlacement.right] 使用 [TPopupRightInset] * [TPopupPlacement.center] 不支持 |
 | modal | bool | true | 是否以模态方式展示;为 true 时阻断背景交互与底层语义/焦点。 结合 [showOverlay] 可表达三种模式: * `modal=true, showOverlay=true`:标准模态弹层 * `modal=true, showOverlay=false`:透明模态弹层 * `modal=false, showOverlay=false`:非模态浮层 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
@@ -687,7 +687,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -716,7 +716,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -744,7 +744,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -772,7 +772,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |
 
@@ -800,7 +800,7 @@ import 'package:tdesign_flutter/tdesign_flutter.dart';
 | onOpen | VoidCallback? | - | 路由 push 时(打开动画开始前)。 |
 | onOpened | VoidCallback? | - | 打开动画结束。 |
 | onClose | VoidCallback? | - | 开始关闭(与 [onVisibleChange] 的 `visible: false` 同期)。 |
-| onClosed | VoidCallback? | - | 当前展示周期真正结束;非栈顶路由被直接移除时不保证存在关闭动画。 |
+| onClosed | VoidCallback? | - | 当前展示周期真正结束。 大多数场景下会在关闭动画结束后触发;非栈顶路由被直接移除时不保证存在关闭动画。 |
 | onVisibleChange | TPopupVisibleChangeCallback? | - | 显隐变化;第二个参数为 [TPopupTrigger]。 |
 | onOverlayClick | VoidCallback? | - | 蒙层点击;是否关闭取决于 [closeOnOverlayClick]。 |