-
Notifications
You must be signed in to change notification settings - Fork 28.6k
Tri-state Checkbox #14611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tri-state Checkbox #14611
Conversation
381f1b9
to
689f831
Compare
@@ -21,6 +21,10 @@ import 'toggleable.dart'; | |||
/// rebuild the checkbox with a new [value] to update the visual appearance of | |||
/// the checkbox. | |||
/// | |||
/// The checkbox can optionally display three values - true, false, and null - | |||
/// if [triState] is true. When [value] is null a dash is displayed. By default | |||
/// triState is false and the checkbox's value must be true or false. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[triState] and [value] on the last line
/// * [value], which determines whether the checkbox is checked, and must not | ||
/// be null. | ||
/// * [value], which determines whether the checkbox is checked. The value can | ||
/// only be be null if triState is true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[tryState]
const Checkbox({ | ||
Key key, | ||
@required this.value, | ||
this.triState: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably spell this "tristate", since "tri" isn't a word, but a prefix.
/// and will not respond to input gestures. | ||
/// | ||
/// When the checkbox is tapped, if [triState] is false (the default) then | ||
/// the onChanged callback will be applied to `!value`. If [triState] is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[onChanged]
/// | ||
/// When the checkbox is tapped, if [triState] is false (the default) then | ||
/// the onChanged callback will be applied to `!value`. If [triState] is | ||
/// true this callback will be applied to false if the current value is true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[value]
/// and will not respond to input gestures. | ||
/// | ||
/// When the checkbox is tapped, if [triState] is false (the default) then | ||
/// the onChanged callback will be applied to `!value`. If [triState] is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what you mean by "applied to" in this paragraph
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok. I think that phrasing would be confusing to our less mathematically-inclined users. I'd probably just say "will be called with the value" or some such.
/// Checkbox displays a dash when its value is null. | ||
/// | ||
/// When a tri-state checkbox is tapped its [onChanged] callback will be | ||
/// applied to true if the current value is null or false, false otherwise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what "applied to" means here either
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inactiveColor: inactiveColor, | ||
onChanged: onChanged, | ||
size: const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius), | ||
vsync: vsync); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: missing newline after last argument
/// The [value], [activeColor], and [inactiveColor] arguments must not be | ||
/// null. | ||
/// The [activeColor], and [inactiveColor] arguments must not be | ||
/// null. The [value] can only be null if triState is true. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[triState]
/// the value 0.0. When the control is active, the value is true and this | ||
/// animation has the value 1.0. When the control is changing from inactive | ||
/// the value 0.0. When the control is active, the value either true or triState | ||
/// is true and the value is null. When the control is active the animation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[triState], [value]
/// False if this control is "inactive" (not checked, off, or unselected). | ||
/// | ||
/// If value is true then the control "active" (checked, on, or selected). If | ||
/// triState is true and value is null, then the control is considered to be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[triState], [value]
@@ -290,7 +318,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { | |||
config.isEnabled = isInteractive; | |||
if (isInteractive) | |||
config.onTap = _handleTap; | |||
config.isChecked = _value; | |||
config.isChecked = _value != false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to convey the null state to the accessibility system
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to defer that to a separate PR. I've discussed it with @goderbauer; support for tristates isn't trivial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you leave a TODO here and/or file a bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general we should have the rule that all new widgets support accessibility, text scaling, localization, and bidi out of the box, but if there's a reason to make an exception here then ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a TODO and filed #14677
@@ -241,7 +269,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { | |||
|
|||
void _handleTap() { | |||
if (isInteractive) | |||
onChanged(!_value); | |||
onChanged(value == false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if tristate is true, this doesn't seem to give the user a way to get back to the indeterminate state. It seems like if tristate is true, this should go in the loop false -> true -> indeterminate -> false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was intentional however I see that HTML tristates do cycle this way. So we can too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main reason to do it this way is that if someone doesn't want this behaviour, they can easily just set tristate to false once the value isn't null, but if they do want this behaviour, there's nothing they can do if we don't implement it by default.
await tester.tap(find.byType(Checkbox)); | ||
await tester.pumpAndSettle(); | ||
expect(checkBoxValue, true); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should verify that animations correctly handle:
true -> false
true -> indeterminate
false -> true
false -> indeterminate
indeterminate -> true
indeterminate -> false
689f831
to
52a19e1
Compare
Added a TODO and filed #14674 for smooth segues for cases where the checkbox value changes in the middle its animation cycle |
Added optional support for tri-state checkboxes, i.e. a checkbox whose value can be true, false, or null.
The checkboxes on the right were created with
triState: true
,value: null
.