Skip to content

Commit 55dbe4f

Browse files
authored
Improve controllers (#630)
* Tighten FAccordionController behavior * Add fields to FAccordionItem * Improve assert error messages * Fix `FTabs` throwing an assertion error if `FTabController` is provided with a `initialIndex` > 0 * Fix typo in assertion error * Tweak controller's value * Clean up debug messages * Commit from GitHub Actions (Forui Hooks Presubmit) * Prepare samples for review * Prepare Forui for review * Fix failing tests --------- Co-authored-by: Pante <[email protected]>
1 parent 83a446f commit 55dbe4f

File tree

93 files changed

+536
-441
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+536
-441
lines changed

docs/app/docs/data/accordion/page.mdx

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ A vertically stacked set of interactive headings that reveal associated content
1313

1414
<Tabs items={['Preview', 'Code']}>
1515
<Tabs.Tab>
16-
<Widget name='accordion' query={{controller: 'default-max'}} height={500}/>
16+
<Widget name='accordion' query={{controller: 'default'}} height={500}/>
1717
</Tabs.Tab>
1818
<Tabs.Tab>
1919
```dart copy
2020
Column(
2121
mainAxisAlignment: MainAxisAlignment.center,
2222
children: [
2323
FAccordion(
24-
controller: FAccordionController(max: 2),
24+
controller: FAccordionController(),
2525
children: [
2626
FAccordionItem(
2727
title: const Text('Is it accessible?'),
@@ -58,7 +58,7 @@ dart run forui style create accordion
5858

5959
```dart copy
6060
FAccordion(
61-
controller: FAccordionController(min: 1, max: 2), // or FAccordionController.radio()
61+
controller: FAccordionController(min: 1, max: 2),
6262
style: FAccordionStyle(...),
6363
children: [
6464
FAccordionItem(
@@ -69,48 +69,11 @@ FAccordion(
6969
);
7070
```
7171

72-
## Examples
73-
74-
### With Radio Behaviour
75-
76-
<Tabs items={['Preview', 'Code']}>
77-
<Tabs.Tab>
78-
<Widget name='accordion' query={{controller: 'radio'}} height={500}/>
79-
</Tabs.Tab>
80-
<Tabs.Tab>
81-
```dart {5} copy
82-
Column(
83-
mainAxisAlignment: MainAxisAlignment.center,
84-
children: [
85-
FAccordion(
86-
controller: FAccordionController.radio(),
87-
children: [
88-
FAccordionItem(
89-
title: const Text('Is it accessible?'),
90-
child: const Text('Yes. It follows WAI-ARIA design patterns.'),
91-
),
92-
FAccordionItem(
93-
initiallyExpanded: true,
94-
title: const Text('Is it Styled?'),
95-
child: const Text('Yes. It includes default styles matching other components.'),
96-
),
97-
FAccordionItem(
98-
title: const Text('Is it Animated?'),
99-
child: const Text('Yes. Animations are enabled by default but can be disabled.'),
100-
),
101-
],
102-
),
103-
],
104-
);
105-
```
106-
</Tabs.Tab>
107-
</Tabs>
108-
10972
### With Multi-select Behaviour
11073

11174
<Tabs items={['Preview', 'Code']}>
11275
<Tabs.Tab>
113-
<Widget name='accordion' query={{controller: 'default'}} height={500}/>
76+
<Widget name='accordion' query={{controller: 'max'}} height={500}/>
11477
</Tabs.Tab>
11578
<Tabs.Tab>
11679
```dart {5} copy

docs/app/docs/tile/tile-group/page.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ several sections.
213213
],
214214
),
215215
FSelectTileGroup<String>(
216-
selectController: FSelectTileGroupController.radio(value: 'List'),
216+
selectController: FSelectTileGroupController.radio('List'),
217217
children: [
218218
FSelectTile(title: const Text('List View'), value: 'List'),
219219
FSelectTile(title: const Text('Grid View'), value: 'Grid'),

forui/CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
## 0.15.0
22

3+
### Consistent Controllers
4+
We've done a pass over the controllers in Forui to make them more consistent and easier to use.
5+
6+
* **Breaking** Change `FMultiValueNotifier({Set<T> value})` to `FMultiValueNotifier({Set<T> values})`.
7+
* **Breaking** Change `FMultiValueNotifier.radio({T? value})` to `FMultiValueNotifier.radio([T? value])`.
8+
9+
10+
### `FAccordion`
11+
* Add `FAccordionItem.onHoverChange`
12+
* Add `FAccordionItem.onStateChange`
13+
14+
* **Breaking** Make `FAccordionController.controllers` private.
15+
* **Breaking** Remove `FAccordionController.radio(...)` - use `FAccrdionController(max: 1)` instead.
16+
* **Breaking** Remove `FAccordionController.validate(...)`.
17+
18+
319
### `FMultiSelect` (new)
420
* Add `FMultiSelect`.
521
* Add `FMultiSelectController`.
@@ -10,6 +26,10 @@
1026
### Others
1127
* Rename `FSelect.divider` to `FSelect.contentDivider`.
1228
* Change `FMultiValueNotifier` to be non-abstract.
29+
* Change `FTappableStyle.mouseCursor` to `MouseCursor.defer`. See https://ux.stackexchange.com/questions/105024/why-dont-button-html-elements-have-a-css-cursor-pointer-by-default
30+
for our rationale behind this change.
31+
32+
* Fix `FTabs` throwing an assertion error if `FTabController` is provided with a `initialIndex` > 0.
1333

1434

1535
### 0.14.1

forui/analysis_options.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ include: package:flint/analysis_options.flutter.yaml
22
analyzer:
33
errors:
44
implicit_call_tearoffs: ignore
5+
prefer_asserts_with_message: ignore
56
unused_result: ignore
67

78
linter:

forui/example/lib/sandbox.dart

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,45 @@ class Sandbox extends StatefulWidget {
1111
enum Notification { all, direct, nothing, limitedTime, timeSensitive, selectedApps }
1212

1313
class _SandboxState extends State<Sandbox> with SingleTickerProviderStateMixin {
14-
late final FPopoverController controller;
14+
late final FTabController c;
1515

1616
@override
1717
void initState() {
1818
super.initState();
19-
controller = FPopoverController(vsync: this);
19+
c = FTabController(vsync: this, length: 2, initialIndex: 1);
2020
}
2121

2222
@override
2323
void dispose() {
24-
controller.dispose();
24+
c.dispose();
2525
super.dispose();
2626
}
2727

2828
@override
2929
Widget build(BuildContext context) {
30-
return Center(child: FMultiSelect<String>.fromMap(const {'1': '1', '2': '2', '3': '3', '4': '4'}));
30+
return Center(
31+
child: FTabs(
32+
controller: c,
33+
initialIndex: 0,
34+
children: [
35+
FTabEntry(
36+
label: const Text('Account'),
37+
child: FCard(
38+
title: const Text('Account'),
39+
subtitle: const Text('Make changes to your account here. Click save when you are done.'),
40+
child: Container(color: Colors.blue, height: 100),
41+
),
42+
),
43+
FTabEntry(
44+
label: const Text('Password'),
45+
child: FCard(
46+
title: const Text('Password'),
47+
subtitle: const Text('Change your password here. After saving, you will be logged out.'),
48+
child: Container(color: Colors.red, height: 100),
49+
),
50+
),
51+
],
52+
),
53+
);
3154
}
3255
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import 'package:flutter/cupertino.dart';
2+
import 'package:flutter/foundation.dart';
3+
4+
import 'package:meta/meta.dart';
5+
6+
@internal
7+
bool debugCheckHasAncestor<T extends InheritedWidget>(String ancestor, BuildContext context, {bool generic = false}) {
8+
assert(() {
9+
if (context.dependOnInheritedWidgetOfExactType<T>() == null) {
10+
throw FlutterError.fromParts([
11+
ErrorSummary('No $ancestor ancestor found.'),
12+
ErrorDescription('The ${context.widget.runtimeType} widget requires an $ancestor widget ancestor.'),
13+
context.describeWidget('The specific widget that could not find a $ancestor ancestor was'),
14+
context.describeOwnershipChain('The ownership chain for the affected widget is'),
15+
if (generic)
16+
ErrorHint(
17+
"This is likely because $ancestor's type parameter could not be inferred. To fix this, wrap "
18+
'${context.widget.runtimeType} in a $ancestor widget and explicitly specify the type parameter.',
19+
)
20+
else
21+
ErrorHint('To fix this, wrap ${context.widget.runtimeType} in a $ancestor widget.'),
22+
]);
23+
}
24+
return true;
25+
}());
26+
27+
return true;
28+
}
29+
30+
@internal
31+
bool debugCheckInclusiveRange<T>(int min, int? max) {
32+
assert(() {
33+
if (min < 0 && (max != null && max < 0)) {
34+
throw FlutterError.fromParts([
35+
ErrorSummary("$T's min < 0 and max < 0."),
36+
IntProperty('The offending min value is', min, style: DiagnosticsTreeStyle.errorProperty),
37+
IntProperty('The offending max value is', max, style: DiagnosticsTreeStyle.errorProperty),
38+
ErrorHint('To fix this, ensure that both min and max are non-negative.'),
39+
]);
40+
}
41+
42+
if (min < 0) {
43+
throw FlutterError.fromParts([
44+
ErrorSummary("$T's min < 0."),
45+
IntProperty('The offending min value is', min, style: DiagnosticsTreeStyle.errorProperty),
46+
ErrorHint('To fix this, ensure that min is non-negative.'),
47+
]);
48+
}
49+
50+
if (max != null && max < 0) {
51+
throw FlutterError.fromParts([
52+
ErrorSummary("$T's max < 0."),
53+
IntProperty('The offending max value is', max, style: DiagnosticsTreeStyle.errorProperty),
54+
ErrorHint('To fix this, ensure that max is non-negative.'),
55+
]);
56+
}
57+
58+
if (max != null && max < min) {
59+
throw FlutterError.fromParts([
60+
ErrorSummary("$T's max < min."),
61+
IntProperty('The offending min value is', min, style: DiagnosticsTreeStyle.errorProperty),
62+
IntProperty('The offending max value is', max, style: DiagnosticsTreeStyle.errorProperty),
63+
ErrorHint('To fix this, ensure that min <= max.'),
64+
]);
65+
}
66+
67+
return true;
68+
}());
69+
70+
return true;
71+
}

forui/lib/src/foundation/focused_outline.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class FFocusedOutlineStyle with Diagnosticable, _$FFocusedOutlineStyleFunctions
131131
/// The outline's width. Defaults to 1.
132132
///
133133
/// ## Contract
134-
/// Throws [AssertionError] if the width is not positive.
134+
/// Must be > 0.
135135
@override
136136
final double width;
137137

@@ -141,5 +141,5 @@ class FFocusedOutlineStyle with Diagnosticable, _$FFocusedOutlineStyleFunctions
141141

142142
/// Creates a [FFocusedOutlineStyle].
143143
const FFocusedOutlineStyle({required this.color, required this.borderRadius, this.width = 1, this.spacing = 3})
144-
: assert(0 < width, 'The width must be greater than 0.');
144+
: assert(0 < width, 'width ($width) must be > 0.');
145145
}

forui/lib/src/foundation/input/input.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ abstract class InputState<T extends Input<U>, U> extends State<T> {
144144
statesController: controller.statesController,
145145
builder: widget.builder,
146146
autocorrect: false,
147-
// We cannot use TextInputType.number as it does not contain a done button.
147+
// We cannot use TextInputType.number as it does not contain a done button on iOS.
148148
keyboardType: const TextInputType.numberWithOptions(signed: true),
149149
minLines: 1,
150150
label: widget.label,

forui/lib/src/foundation/input/input_controller.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ abstract class InputController extends TextEditingController {
2828
}
2929

3030
final TextSelection(:baseOffset, :extentOffset) = value.selection;
31-
// Selected everything without doing anything else.
31+
// Selected the entire text without doing anything else.
3232
if (baseOffset == 0 && extentOffset == value.text.length && text == value.text) {
3333
super.value = value;
3434
return;

forui/lib/src/foundation/input/parser.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ abstract class Parser {
1515

1616
/// Updates the [current] input based on the [current] and [previous] input.
1717
(List<String>, Changes) update(List<String> previous, List<String> current) {
18-
assert(previous.length == pattern.length, 'previous must have ${pattern.length} parts');
19-
assert(current.length == pattern.length, 'current must have ${pattern.length} parts');
18+
assert(previous.length == pattern.length, 'previous must have ${pattern.length} parts.');
19+
assert(current.length == pattern.length, 'current must have ${pattern.length} parts.');
2020

2121
Changes changes = const None();
2222
for (int i = 0; i < pattern.length; i++) {

0 commit comments

Comments
 (0)