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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- **BREAKING** feat: `TreeViewItem.children` is now unmodifiable. Use `TreeViewController` methods (`addItem()`, `addItems()`, `removeItem()`, `moveItem()`) to modify tree structure.
- feat: `TitleBar` now supports double-click callback to maximize or restore the window ([#1298](https://github.com/bdlukaa/fluent_ui/issues/1298))
- fix: Correctly apply `TitleBar`'s `isBackButtonEnabled` ([#1298](https://github.com/bdlukaa/fluent_ui/issues/1298))
- fix: `NavigationView` compact pane `PaneItemExpander` flyout is now correctly placed in right-to-left directionality; Flyout `leftTop` and `rightTop` placement modes now correctly align with the top of the target element when the flyout is taller than the available space above the target ([#1289](https://github.com/bdlukaa/fluent_ui/issues/1289))

## 4.14.0

Expand Down
43 changes: 20 additions & 23 deletions example/lib/screens/navigation/tree_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -329,30 +329,27 @@ TreeView(
await Future<void>.delayed(const Duration(seconds: 2));

// ...and add the fetched nodes via the controller.
_lazyController.addItems(
[
TreeViewItem(
content: const Text('Lazy item 1'),
value: 'lazy_1',
),
TreeViewItem(
content: const Text('Lazy item 2'),
value: 'lazy_2',
),
TreeViewItem(
content: const Text('Lazy item 3'),
value: 'lazy_3',
),
TreeViewItem(
content: const Text(
'Lazy item 4 (this text should not overflow)',
overflow: TextOverflow.ellipsis,
),
value: 'lazy_4',
_lazyController.addItems([
TreeViewItem(
content: const Text('Lazy item 1'),
value: 'lazy_1',
),
TreeViewItem(
content: const Text('Lazy item 2'),
value: 'lazy_2',
),
TreeViewItem(
content: const Text('Lazy item 3'),
value: 'lazy_3',
),
TreeViewItem(
content: const Text(
'Lazy item 4 (this text should not overflow)',
overflow: TextOverflow.ellipsis,
),
],
parent: item,
);
value: 'lazy_4',
),
], parent: item);
},
onItemInvoked: (final item, final reason) async =>
debugPrint('onItemInvoked(reason=$reason): $item'),
Expand Down
2 changes: 1 addition & 1 deletion lib/src/controls/flyouts/flyout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ class _FlyoutPositionDelegate extends SingleChildLayoutDelegate {

final bottomY = clampVertical(targetOffset.dy);

final horizontalTopY = clampVertical(topY + flyoutSize.height);
final horizontalTopY = clampVertical(targetOffset.dy - targetSize.height);
final horizontalY = clampVertical(
targetOffset.dy - targetSize.height / 2 - flyoutSize.height / 2,
);
Expand Down
5 changes: 4 additions & 1 deletion lib/src/controls/flyouts/menu_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,10 @@ class MenuBarState extends State<MenuBar> {
onTapOutside: (_) => closeFlyout(),
child: Container(
constraints: BoxConstraints(
minHeight: (40 + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity),
minHeight: (40 + theme.visualDensity.baseSizeAdjustment.dy).clamp(
0.0,
double.infinity,
),
),
padding: EdgeInsetsDirectional.only(
top: barMargin.top,
Expand Down
5 changes: 4 additions & 1 deletion lib/src/controls/flyouts/tooltip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,10 @@ class TooltipThemeData with Diagnosticable {
/// Creates the standard [TooltipThemeData] based on the given [theme].
factory TooltipThemeData.standard(FluentThemeData theme) {
return TooltipThemeData(
height: (32 + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity),
height: (32 + theme.visualDensity.baseSizeAdjustment.dy).clamp(
0.0,
double.infinity,
),
verticalOffset: 24,
preferBelow: false,
margin: EdgeInsetsDirectional.zero,
Expand Down
10 changes: 8 additions & 2 deletions lib/src/controls/form/combo_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -789,11 +789,17 @@ class _ComboBoxItemContainer extends StatelessWidget {
: theme.resources.textFillColorPrimary;

final densityAdjustment = theme.visualDensity.baseSizeAdjustment.dy;
final adjustedItemHeight = (kComboBoxItemHeight + densityAdjustment).clamp(0.0, double.infinity);
final adjustedItemHeight = (kComboBoxItemHeight + densityAdjustment).clamp(
0.0,
double.infinity,
);
return Container(
height: hasPadding
? adjustedItemHeight
: (adjustedItemHeight - _kMenuItemBottomPadding).clamp(0.0, double.infinity),
: (adjustedItemHeight - _kMenuItemBottomPadding).clamp(
0.0,
double.infinity,
),
alignment: AlignmentDirectional.centerStart,
child: DefaultTextStyle.merge(
style: TextStyle(color: foregroundColor),
Expand Down
9 changes: 7 additions & 2 deletions lib/src/controls/form/number_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,10 @@ class NumberBoxState<T extends num> extends State<NumberBox<T>> {
if (boxContext == null) return const SizedBox.shrink();
final box = boxContext.findRenderObject()! as RenderBox;
final isRtl = Directionality.of(context) == TextDirection.rtl;
final overlayHeight = (kNumberBoxOverlayHeight + FluentTheme.of(context).visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity);
final overlayHeight =
(kNumberBoxOverlayHeight +
FluentTheme.of(context).visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity);

final Widget child = PositionedDirectional(
width: kNumberBoxOverlayWidth,
Expand Down Expand Up @@ -907,7 +910,9 @@ class _NumberBoxCompactOverlay extends StatelessWidget {
Widget build(BuildContext context) {
assert(debugCheckHasFluentTheme(context));
final theme = FluentTheme.of(context);
final overlayHeight = (kNumberBoxOverlayHeight + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity);
final overlayHeight =
(kNumberBoxOverlayHeight + theme.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity);

return Padding(
padding: const EdgeInsetsDirectional.only(start: 10),
Expand Down
4 changes: 3 additions & 1 deletion lib/src/controls/form/text_box.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1609,7 +1609,9 @@ class _TextBoxState extends State<TextBox>
child: Container(
foregroundDecoration: foregroundDecoration,
constraints: BoxConstraints(
minHeight: (32 + themeData.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity),
minHeight:
(32 + themeData.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity),
),
child: _selectionGestureDetectorBuilder
.buildGestureDetector(
Expand Down
5 changes: 4 additions & 1 deletion lib/src/controls/navigation/navigation_view/indicators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,10 @@ class _StickyNavigationIndicatorState

final fluentTheme = FluentTheme.of(context);
final densityAdjustment = fluentTheme.visualDensity.baseSizeAdjustment.dy;
final paneItemMinHeight = (kPaneItemMinHeight + densityAdjustment).clamp(0.0, double.infinity);
final paneItemMinHeight = (kPaneItemMinHeight + densityAdjustment).clamp(
0.0,
double.infinity,
);

return IgnorePointer(
child: Align(
Expand Down
16 changes: 8 additions & 8 deletions lib/src/controls/navigation/navigation_view/pane_items.dart
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,10 @@ class PaneItem extends NavigationPaneItem {
final theme = NavigationPaneTheme.of(context);
final fluentTheme = FluentTheme.of(context);
final densityAdjustment = fluentTheme.visualDensity.baseSizeAdjustment.dy;
final paneItemMinHeight = (kPaneItemMinHeight + densityAdjustment).clamp(0.0, double.infinity);
final paneItemMinHeight = (kPaneItemMinHeight + densityAdjustment).clamp(
0.0,
double.infinity,
);

final titleText = title?._getProperty<String>() ?? '';
final baseStyle = title?._getProperty<TextStyle>() ?? const TextStyle();
Expand Down Expand Up @@ -273,9 +276,7 @@ class PaneItem extends NavigationPaneItem {
case PaneDisplayMode.compact:
return Container(
key: key,
constraints: BoxConstraints(
minHeight: paneItemMinHeight,
),
constraints: BoxConstraints(minHeight: paneItemMinHeight),
alignment: AlignmentDirectional.centerStart,
padding: theme.iconPadding ?? EdgeInsetsDirectional.zero,
child: infoBadge != null
Expand All @@ -299,9 +300,7 @@ class PaneItem extends NavigationPaneItem {

return ConstrainedBox(
key: key,
constraints: BoxConstraints(
minHeight: paneItemMinHeight,
),
constraints: BoxConstraints(minHeight: paneItemMinHeight),
child: ClipRect(
child: Row(
children: [
Expand Down Expand Up @@ -899,9 +898,10 @@ class __PaneItemExpanderState extends State<_PaneItemExpander>
final displayMode = body.displayMode;
final navigationTheme = NavigationPaneTheme.of(context);

final textDirection = Directionality.of(context);
flyoutController.showFlyout<void>(
placementMode: displayMode == PaneDisplayMode.compact
? FlyoutPlacementMode.rightTop
? FlyoutPlacementMode.rightTop.resolve(textDirection)
: FlyoutPlacementMode.bottomCenter,
forceAvailableSpace: true,
barrierColor: Colors.transparent,
Expand Down
10 changes: 7 additions & 3 deletions lib/src/controls/navigation/navigation_view/title_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class TitleBar extends StatelessWidget {

/// The callback that is called when the title bar is double-tapped.
final VoidCallback? onDoubleTap;

@override
Widget build(BuildContext context) {
assert(debugCheckHasFluentTheme(context));
Expand Down Expand Up @@ -580,7 +580,9 @@ class PaneToggleButton extends StatelessWidget {
Widget build(BuildContext context) {
final view = NavigationView.dataOf(context);
final fluentTheme = FluentTheme.of(context);
final paneItemHeight = (kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity);
final paneItemHeight =
(kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity);

final width = view.pane?.size?.compactWidth ?? kCompactNavigationPaneWidth;
return Container(
Expand Down Expand Up @@ -637,7 +639,9 @@ class PaneBackButton extends StatelessWidget {
final viewData = NavigationView.dataOf(context);
final canPop = viewData.canPop;
final fluentTheme = FluentTheme.of(context);
final paneItemHeight = (kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity);
final paneItemHeight =
(kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity);

final width =
viewData.pane?.size?.compactWidth ?? kCompactNavigationPaneWidth;
Expand Down
9 changes: 6 additions & 3 deletions lib/src/controls/navigation/tree_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1417,9 +1417,12 @@ class _TreeViewItem extends StatelessWidget {
// Indentation and selection indicator for single selection mode.
Container(
constraints: BoxConstraints(
minHeight: ((selectionMode == TreeViewSelectionMode.multiple
? 28.0
: 26.0) + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity),
minHeight:
((selectionMode == TreeViewSelectionMode.multiple
? 28.0
: 26.0) +
theme.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity),
),
padding: EdgeInsetsDirectional.only(
start: selectionMode == TreeViewSelectionMode.multiple
Expand Down
4 changes: 3 additions & 1 deletion lib/src/controls/pickers/date_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,9 @@ class DatePickerState extends State<DatePicker> {
duration: theme.fastAnimationDuration,
curve: theme.animationCurve,
constraints: BoxConstraints(
minHeight: (kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity),
minHeight:
(kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity),
),
decoration: kPickerDecorationBuilder(context, states),
child: DefaultTextStyle.merge(
Expand Down
4 changes: 3 additions & 1 deletion lib/src/controls/pickers/time_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,9 @@ class TimePickerState extends State<TimePicker>
duration: theme.fastAnimationDuration,
curve: theme.animationCurve,
constraints: BoxConstraints(
minHeight: (kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity),
minHeight:
(kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity),
),
decoration: kPickerDecorationBuilder(context, states),
child: DefaultTextStyle.merge(
Expand Down
7 changes: 6 additions & 1 deletion lib/src/controls/surfaces/expander.dart
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,12 @@ class ExpanderState extends State<Expander>
hitTestBehavior: HitTestBehavior.deferToChild,
builder: (context, states) {
return Container(
constraints: BoxConstraints(minHeight: (42 + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity)),
constraints: BoxConstraints(
minHeight: (42 + theme.visualDensity.baseSizeAdjustment.dy).clamp(
0.0,
double.infinity,
),
),
decoration: ShapeDecoration(
color:
widget.headerBackgroundColor?.resolve(states) ??
Expand Down
7 changes: 6 additions & 1 deletion lib/src/controls/surfaces/info_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,12 @@ class InfoBar extends StatelessWidget {
);
}();
return Container(
constraints: BoxConstraints(minHeight: (48 + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity)),
constraints: BoxConstraints(
minHeight: (48 + theme.visualDensity.baseSizeAdjustment.dy).clamp(
0.0,
double.infinity,
),
),
decoration: style.decoration?.call(severity),
padding: style.padding ?? const EdgeInsetsDirectional.all(10),
child: Row(
Expand Down
5 changes: 4 additions & 1 deletion lib/src/controls/surfaces/list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,10 @@ class ListTile extends StatelessWidget {
child: Container(
decoration: ShapeDecoration(shape: shape, color: tileColor),
constraints: BoxConstraints(
minHeight: (kOneLineTileHeight + theme.visualDensity.baseSizeAdjustment.dy).clamp(0.0, double.infinity),
minHeight:
(kOneLineTileHeight +
theme.visualDensity.baseSizeAdjustment.dy)
.clamp(0.0, double.infinity),
minWidth: 88,
),
margin: margin,
Expand Down
4 changes: 1 addition & 3 deletions test/flyout_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,7 @@ void main() {

// Hover over the sub-menu item to trigger its display
final subMenuFinder = find.text('Sub Menu');
final gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: Offset.zero);
await gesture.moveTo(tester.getCenter(subMenuFinder));
await tester.pumpAndSettle(const Duration(milliseconds: 500));
Expand Down
4 changes: 1 addition & 3 deletions test/icons_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import 'app_test.dart';

void main() {
testWidgets('Icons specify FluentIcons font', (tester) async {
await tester.pumpWidget(
wrapApp(child: const Icon(FluentIcons.clear)),
);
await tester.pumpWidget(wrapApp(child: const Icon(FluentIcons.clear)));

expect(FluentIcons.clear.fontFamily, 'FluentIcons');
expect(FluentIcons.search.fontFamily, 'FluentIcons');
Expand Down
53 changes: 53 additions & 0 deletions test/navigation_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -969,4 +969,57 @@ void main() {
},
);
});

// Regression test for https://github.com/bdlukaa/fluent_ui/issues/XXX -
// NavigationView compact pane flyout too small in RTL directionality
group('Issue - NavigationView compact pane flyout in RTL directionality', () {
testWidgets('PaneItemExpander flyout opens correctly in RTL compact mode', (
tester,
) async {
await tester.pumpWidget(
FluentApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: SizedBox(
width: 1200,
height: 800,
child: NavigationView(
pane: NavigationPane(
selected: 0,
displayMode: PaneDisplayMode.compact,
items: [
PaneItemExpander(
icon: const Icon(FluentIcons.folder),
title: const Text('Files'),
body: const Center(child: Text('Files Page')),
items: [
PaneItem(
icon: const Icon(FluentIcons.document),
title: const Text('Documents'),
body: const Center(child: Text('Documents Page')),
),
],
),
],
),
),
),
),
),
);

await tester.pumpAndSettle();

// Tap the PaneItemExpander icon to open the flyout
await tester.tap(find.byIcon(FluentIcons.folder));
await tester.pumpAndSettle();

// The flyout should be open and contain the child item
expect(find.byType(MenuFlyout), findsOneWidget);

// The flyout should have enough width (more than just the compact pane width)
final flyoutBox = tester.renderObject<RenderBox>(find.byType(MenuFlyout));
expect(flyoutBox.size.width, greaterThan(kCompactNavigationPaneWidth));
});
});
}
Loading
Loading