Skip to content

Commit 68abf65

Browse files
feat: improve focus on fields with list widgets
1 parent 7cef78e commit 68abf65

File tree

6 files changed

+134
-131
lines changed

6 files changed

+134
-131
lines changed

lib/src/fields/form_builder_checkbox_group.dart

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -69,38 +69,43 @@ class FormBuilderCheckboxGroup<T> extends FormBuilderFieldDecoration<List<T>> {
6969
builder: (FormFieldState<List<T>?> field) {
7070
final state = field as _FormBuilderCheckboxGroupState<T>;
7171

72-
return InputDecorator(
73-
decoration: state.decoration,
74-
isFocused: state.effectiveFocusNode.hasFocus,
75-
child: GroupedCheckbox<T>(
76-
focusNode: state.effectiveFocusNode,
77-
orientation: orientation,
78-
value: state.value,
79-
options: options,
80-
onChanged: (val) {
81-
field.didChange(val);
82-
},
83-
disabled: state.enabled
84-
? disabled
85-
: options.map((e) => e.value).toList(),
86-
activeColor: activeColor,
87-
visualDensity: visualDensity,
88-
focusColor: focusColor,
89-
checkColor: checkColor,
90-
materialTapTargetSize: materialTapTargetSize,
91-
hoverColor: hoverColor,
92-
tristate: tristate,
93-
wrapAlignment: wrapAlignment,
94-
wrapCrossAxisAlignment: wrapCrossAxisAlignment,
95-
wrapDirection: wrapDirection,
96-
wrapRunAlignment: wrapRunAlignment,
97-
wrapRunSpacing: wrapRunSpacing,
98-
wrapSpacing: wrapSpacing,
99-
wrapTextDirection: wrapTextDirection,
100-
wrapVerticalDirection: wrapVerticalDirection,
101-
separator: separator,
102-
controlAffinity: controlAffinity,
103-
itemDecoration: itemDecoration,
72+
return Focus(
73+
focusNode: state.effectiveFocusNode,
74+
skipTraversal: true,
75+
canRequestFocus: state.enabled,
76+
debugLabel: 'FormBuilderCheckboxGroup-$name',
77+
child: InputDecorator(
78+
decoration: state.decoration,
79+
isFocused: state.effectiveFocusNode.hasFocus,
80+
child: GroupedCheckbox<T>(
81+
orientation: orientation,
82+
value: state.value,
83+
options: options,
84+
onChanged: (val) {
85+
field.didChange(val);
86+
},
87+
disabled: state.enabled
88+
? disabled
89+
: options.map((e) => e.value).toList(),
90+
activeColor: activeColor,
91+
visualDensity: visualDensity,
92+
focusColor: focusColor,
93+
checkColor: checkColor,
94+
materialTapTargetSize: materialTapTargetSize,
95+
hoverColor: hoverColor,
96+
tristate: tristate,
97+
wrapAlignment: wrapAlignment,
98+
wrapCrossAxisAlignment: wrapCrossAxisAlignment,
99+
wrapDirection: wrapDirection,
100+
wrapRunAlignment: wrapRunAlignment,
101+
wrapRunSpacing: wrapRunSpacing,
102+
wrapSpacing: wrapSpacing,
103+
wrapTextDirection: wrapTextDirection,
104+
wrapVerticalDirection: wrapVerticalDirection,
105+
separator: separator,
106+
controlAffinity: controlAffinity,
107+
itemDecoration: itemDecoration,
108+
),
104109
),
105110
);
106111
},

lib/src/fields/form_builder_choice_chips.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ class FormBuilderChoiceChip<T> extends FormBuilderFieldDecoration<T> {
397397

398398
return Focus(
399399
focusNode: state.effectiveFocusNode,
400+
skipTraversal: true,
401+
canRequestFocus: state.enabled,
402+
debugLabel: 'FormBuilderChoiceChip-$name',
400403
child: InputDecorator(
401404
decoration: state.decoration,
402405
isFocused: state.effectiveFocusNode.hasFocus,

lib/src/fields/form_builder_filter_chips.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,11 @@ class FormBuilderFilterChip<T> extends FormBuilderFieldDecoration<List<T>> {
8181
builder: (FormFieldState<List<T>?> field) {
8282
final state = field as _FormBuilderFilterChipState<T>;
8383
final fieldValue = field.value ?? [];
84-
8584
return Focus(
8685
focusNode: state.effectiveFocusNode,
86+
skipTraversal: true,
87+
canRequestFocus: state.enabled,
88+
debugLabel: 'FormBuilderFilterChip-$name',
8789
child: InputDecorator(
8890
decoration: state.decoration,
8991
isFocused: state.effectiveFocusNode.hasFocus,

lib/src/fields/form_builder_radio_group.dart

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -63,35 +63,40 @@ class FormBuilderRadioGroup<T> extends FormBuilderFieldDecoration<T> {
6363
builder: (FormFieldState<T?> field) {
6464
final state = field as _FormBuilderRadioGroupState<T>;
6565

66-
return InputDecorator(
67-
decoration: state.decoration,
68-
isFocused: state.effectiveFocusNode.hasFocus,
69-
child: GroupedRadio<T>(
70-
focusNode: state.effectiveFocusNode,
71-
activeColor: activeColor,
72-
controlAffinity: controlAffinity,
73-
disabled: state.enabled
74-
? disabled
75-
: options.map((option) => option.value).toList(),
76-
focusColor: focusColor,
77-
hoverColor: hoverColor,
78-
materialTapTargetSize: materialTapTargetSize,
79-
onChanged: (value) {
80-
state.didChange(value);
81-
},
82-
options: options,
83-
orientation: orientation,
84-
separator: separator,
85-
value: state.value,
86-
wrapAlignment: wrapAlignment,
87-
wrapCrossAxisAlignment: wrapCrossAxisAlignment,
88-
wrapDirection: wrapDirection,
89-
wrapRunAlignment: wrapRunAlignment,
90-
wrapRunSpacing: wrapRunSpacing,
91-
wrapSpacing: wrapSpacing,
92-
wrapTextDirection: wrapTextDirection,
93-
wrapVerticalDirection: wrapVerticalDirection,
94-
itemDecoration: itemDecoration,
66+
return Focus(
67+
focusNode: state.effectiveFocusNode,
68+
skipTraversal: true,
69+
canRequestFocus: state.enabled,
70+
debugLabel: 'FormBuilderRadioGroup-$name',
71+
child: InputDecorator(
72+
decoration: state.decoration,
73+
isFocused: state.effectiveFocusNode.hasFocus,
74+
child: GroupedRadio<T>(
75+
activeColor: activeColor,
76+
controlAffinity: controlAffinity,
77+
disabled: state.enabled
78+
? disabled
79+
: options.map((option) => option.value).toList(),
80+
focusColor: focusColor,
81+
hoverColor: hoverColor,
82+
materialTapTargetSize: materialTapTargetSize,
83+
onChanged: (value) {
84+
state.didChange(value);
85+
},
86+
options: options,
87+
orientation: orientation,
88+
separator: separator,
89+
value: state.value,
90+
wrapAlignment: wrapAlignment,
91+
wrapCrossAxisAlignment: wrapCrossAxisAlignment,
92+
wrapDirection: wrapDirection,
93+
wrapRunAlignment: wrapRunAlignment,
94+
wrapRunSpacing: wrapRunSpacing,
95+
wrapSpacing: wrapSpacing,
96+
wrapTextDirection: wrapTextDirection,
97+
wrapVerticalDirection: wrapVerticalDirection,
98+
itemDecoration: itemDecoration,
99+
),
95100
),
96101
);
97102
},

lib/src/widgets/grouped_checkbox.dart

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,11 @@ class GroupedCheckbox<T> extends StatelessWidget {
190190
/// [wrapSpacing] is used as inter-item right margin
191191
final BoxDecoration? itemDecoration;
192192

193-
final FocusNode focusNode;
194-
195193
const GroupedCheckbox({
196194
super.key,
197195
required this.options,
198196
required this.orientation,
199197
required this.onChanged,
200-
required this.focusNode,
201198
this.value,
202199
this.disabled,
203200
this.activeColor,
@@ -227,43 +224,40 @@ class GroupedCheckbox<T> extends StatelessWidget {
227224
widgetList.add(buildItem(i));
228225
}
229226

230-
return Focus(
231-
focusNode: focusNode,
232-
child: switch (orientation) {
233-
OptionsOrientation.auto => OverflowBar(
234-
alignment: MainAxisAlignment.spaceEvenly,
227+
return switch (orientation) {
228+
OptionsOrientation.auto => OverflowBar(
229+
alignment: MainAxisAlignment.spaceEvenly,
230+
children: widgetList,
231+
),
232+
OptionsOrientation.vertical => SingleChildScrollView(
233+
scrollDirection: Axis.vertical,
234+
child: Column(
235+
crossAxisAlignment: CrossAxisAlignment.start,
235236
children: widgetList,
236237
),
237-
OptionsOrientation.vertical => SingleChildScrollView(
238-
scrollDirection: Axis.vertical,
239-
child: Column(
240-
crossAxisAlignment: CrossAxisAlignment.start,
241-
children: widgetList,
242-
),
238+
),
239+
OptionsOrientation.horizontal => SingleChildScrollView(
240+
scrollDirection: Axis.horizontal,
241+
child: Row(
242+
children: widgetList.map((item) {
243+
return Column(children: <Widget>[item]);
244+
}).toList(),
243245
),
244-
OptionsOrientation.horizontal => SingleChildScrollView(
245-
scrollDirection: Axis.horizontal,
246-
child: Row(
247-
children: widgetList.map((item) {
248-
return Column(children: <Widget>[item]);
249-
}).toList(),
250-
),
246+
),
247+
OptionsOrientation.wrap => SingleChildScrollView(
248+
child: Wrap(
249+
spacing: wrapSpacing,
250+
runSpacing: wrapRunSpacing,
251+
textDirection: wrapTextDirection,
252+
crossAxisAlignment: wrapCrossAxisAlignment,
253+
verticalDirection: wrapVerticalDirection,
254+
alignment: wrapAlignment,
255+
direction: Axis.horizontal,
256+
runAlignment: wrapRunAlignment,
257+
children: widgetList,
251258
),
252-
OptionsOrientation.wrap => SingleChildScrollView(
253-
child: Wrap(
254-
spacing: wrapSpacing,
255-
runSpacing: wrapRunSpacing,
256-
textDirection: wrapTextDirection,
257-
crossAxisAlignment: wrapCrossAxisAlignment,
258-
verticalDirection: wrapVerticalDirection,
259-
alignment: wrapAlignment,
260-
direction: Axis.horizontal,
261-
runAlignment: wrapRunAlignment,
262-
children: widgetList,
263-
),
264-
)
265-
},
266-
);
259+
)
260+
};
267261
}
268262

269263
/// the composite of all the components for the option at index

lib/src/widgets/grouped_radio.dart

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,11 @@ class GroupedRadio<T> extends StatefulWidget {
181181
/// [wrapSpacing] is used as inter-item right margin
182182
final BoxDecoration? itemDecoration;
183183

184-
final FocusNode focusNode;
185-
186184
const GroupedRadio({
187185
super.key,
188186
required this.options,
189187
required this.orientation,
190188
required this.onChanged,
191-
required this.focusNode,
192189
this.value,
193190
this.disabled,
194191
this.activeColor,
@@ -220,39 +217,36 @@ class _GroupedRadioState<T> extends State<GroupedRadio<T?>> {
220217
widgetList.add(buildItem(i));
221218
}
222219

223-
return Focus(
224-
focusNode: widget.focusNode,
225-
child: switch (widget.orientation) {
226-
OptionsOrientation.auto => OverflowBar(
227-
alignment: MainAxisAlignment.spaceEvenly,
220+
return switch (widget.orientation) {
221+
OptionsOrientation.auto => OverflowBar(
222+
alignment: MainAxisAlignment.spaceEvenly,
223+
children: widgetList,
224+
),
225+
OptionsOrientation.vertical => SingleChildScrollView(
226+
scrollDirection: Axis.vertical,
227+
child: Column(
228+
crossAxisAlignment: CrossAxisAlignment.start,
228229
children: widgetList,
229230
),
230-
OptionsOrientation.vertical => SingleChildScrollView(
231-
scrollDirection: Axis.vertical,
232-
child: Column(
233-
crossAxisAlignment: CrossAxisAlignment.start,
234-
children: widgetList,
235-
),
236-
),
237-
OptionsOrientation.horizontal => SingleChildScrollView(
238-
scrollDirection: Axis.horizontal,
239-
child: Row(children: widgetList),
231+
),
232+
OptionsOrientation.horizontal => SingleChildScrollView(
233+
scrollDirection: Axis.horizontal,
234+
child: Row(children: widgetList),
235+
),
236+
OptionsOrientation.wrap => SingleChildScrollView(
237+
child: Wrap(
238+
spacing: widget.wrapSpacing,
239+
runSpacing: widget.wrapRunSpacing,
240+
textDirection: widget.wrapTextDirection,
241+
crossAxisAlignment: widget.wrapCrossAxisAlignment,
242+
verticalDirection: widget.wrapVerticalDirection,
243+
alignment: widget.wrapAlignment,
244+
direction: Axis.horizontal,
245+
runAlignment: widget.wrapRunAlignment,
246+
children: widgetList,
240247
),
241-
OptionsOrientation.wrap => SingleChildScrollView(
242-
child: Wrap(
243-
spacing: widget.wrapSpacing,
244-
runSpacing: widget.wrapRunSpacing,
245-
textDirection: widget.wrapTextDirection,
246-
crossAxisAlignment: widget.wrapCrossAxisAlignment,
247-
verticalDirection: widget.wrapVerticalDirection,
248-
alignment: widget.wrapAlignment,
249-
direction: Axis.horizontal,
250-
runAlignment: widget.wrapRunAlignment,
251-
children: widgetList,
252-
),
253-
)
254-
},
255-
);
248+
)
249+
};
256250
}
257251

258252
/// the composite of all the components for the option at index

0 commit comments

Comments
 (0)