Skip to content

Commit a612131

Browse files
feat: improve focus on date pickers
1 parent 4d5d2dc commit a612131

File tree

2 files changed

+138
-125
lines changed

2 files changed

+138
-125
lines changed

lib/src/fields/form_builder_date_range_picker.dart

Lines changed: 82 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -134,40 +134,44 @@ class FormBuilderDateRangePicker
134134
builder: (FormFieldState<DateTimeRange?> field) {
135135
final state = field as _FormBuilderDateRangePickerState;
136136

137-
return TextField(
138-
enabled: state.enabled,
139-
style: style,
140-
focusNode: state.effectiveFocusNode,
141-
decoration: state.decoration,
142-
// initialValue: "${_initialValue ?? ''}",
143-
maxLines: maxLines,
144-
keyboardType: keyboardType,
145-
obscureText: obscureText,
146-
onEditingComplete: onEditingComplete,
147-
controller: state._effectiveController,
148-
autocorrect: autocorrect,
149-
autofocus: autofocus,
150-
buildCounter: buildCounter,
151-
mouseCursor: mouseCursor,
152-
cursorColor: cursorColor,
153-
cursorRadius: cursorRadius,
154-
cursorWidth: cursorWidth,
155-
enableInteractiveSelection: enableInteractiveSelection,
156-
maxLength: maxLength,
157-
inputFormatters: inputFormatters,
158-
keyboardAppearance: keyboardAppearance,
159-
maxLengthEnforcement: maxLengthEnforcement,
160-
scrollPadding: scrollPadding,
161-
textAlign: textAlign,
162-
textCapitalization: textCapitalization,
163-
textDirection: textDirection,
164-
textInputAction: textInputAction,
165-
textAlignVertical: textAlignVertical,
166-
strutStyle: strutStyle,
167-
readOnly: true,
168-
expands: expands,
169-
minLines: minLines,
170-
showCursor: showCursor,
137+
return FocusTraversalGroup(
138+
policy: ReadingOrderTraversalPolicy(),
139+
child: TextField(
140+
onTap: () => state.showPicker(),
141+
enabled: state.enabled,
142+
style: style,
143+
focusNode: state.effectiveFocusNode,
144+
decoration: state.decoration,
145+
// initialValue: "${_initialValue ?? ''}",
146+
maxLines: maxLines,
147+
keyboardType: keyboardType,
148+
obscureText: obscureText,
149+
onEditingComplete: onEditingComplete,
150+
controller: state._effectiveController,
151+
autocorrect: autocorrect,
152+
autofocus: autofocus,
153+
buildCounter: buildCounter,
154+
mouseCursor: mouseCursor,
155+
cursorColor: cursorColor,
156+
cursorRadius: cursorRadius,
157+
cursorWidth: cursorWidth,
158+
enableInteractiveSelection: enableInteractiveSelection,
159+
maxLength: maxLength,
160+
inputFormatters: inputFormatters,
161+
keyboardAppearance: keyboardAppearance,
162+
maxLengthEnforcement: maxLengthEnforcement,
163+
scrollPadding: scrollPadding,
164+
textAlign: textAlign,
165+
textCapitalization: textCapitalization,
166+
textDirection: textDirection,
167+
textInputAction: textInputAction,
168+
textAlignVertical: textAlignVertical,
169+
strutStyle: strutStyle,
170+
readOnly: true,
171+
expands: expands,
172+
minLines: minLines,
173+
showCursor: showCursor,
174+
),
171175
);
172176
},
173177
);
@@ -195,55 +199,57 @@ class _FormBuilderDateRangePickerState extends FormBuilderFieldDecorationState<
195199
super.initState();
196200
_effectiveController =
197201
widget.controller ?? TextEditingController(text: _valueToText());
198-
effectiveFocusNode.addListener(_handleFocus);
202+
203+
effectiveFocusNode.onKeyEvent = (node, event) {
204+
if (enabled &&
205+
event is KeyDownEvent &&
206+
event.logicalKey == LogicalKeyboardKey.space &&
207+
node.hasFocus) {
208+
showPicker();
209+
return KeyEventResult.handled;
210+
}
211+
return KeyEventResult.ignored;
212+
};
199213
}
200214

201215
@override
202216
void dispose() {
203-
effectiveFocusNode.removeListener(_handleFocus);
204217
// Dispose the _effectiveController when initState created it
205218
if (null == widget.controller) {
206219
_effectiveController.dispose();
207220
}
208221
super.dispose();
209222
}
210223

211-
Future<void> _handleFocus() async {
212-
if (effectiveFocusNode.hasFocus && enabled) {
213-
effectiveFocusNode.unfocus();
214-
/*final initialFirstDate = value?.isEmpty ?? true
215-
? (widget.initialFirstDate ?? DateTime.now())
216-
: value[0];
217-
final initialLastDate = value?.isEmpty ?? true
218-
? (widget.initialLastDate ?? initialFirstDate)
219-
: (value.length < 2 ? initialFirstDate : value[1]);*/
220-
final picked = await showDateRangePicker(
221-
context: context,
222-
firstDate: widget.firstDate,
223-
lastDate: widget.lastDate,
224-
locale: widget.locale,
225-
textDirection: widget.textDirection,
226-
cancelText: widget.cancelText,
227-
confirmText: widget.confirmText,
228-
currentDate: widget.currentDate,
229-
errorFormatText: widget.errorFormatText,
230-
builder: widget.pickerBuilder,
231-
errorInvalidRangeText: widget.errorInvalidRangeText,
232-
errorInvalidText: widget.errorInvalidText,
233-
fieldEndHintText: widget.fieldEndHintText,
234-
fieldEndLabelText: widget.fieldEndLabelText,
235-
fieldStartHintText: widget.fieldStartHintText,
236-
fieldStartLabelText: widget.fieldStartLabelText,
237-
helpText: widget.helpText,
238-
initialDateRange: value,
239-
initialEntryMode: widget.initialEntryMode,
240-
routeSettings: widget.routeSettings,
241-
saveText: widget.saveText,
242-
useRootNavigator: widget.useRootNavigator,
243-
);
244-
if (picked != null) {
245-
didChange(picked);
246-
}
224+
Future<void> showPicker() async {
225+
effectiveFocusNode.requestFocus();
226+
227+
final picked = await showDateRangePicker(
228+
context: context,
229+
firstDate: widget.firstDate,
230+
lastDate: widget.lastDate,
231+
locale: widget.locale,
232+
textDirection: widget.textDirection,
233+
cancelText: widget.cancelText,
234+
confirmText: widget.confirmText,
235+
currentDate: widget.currentDate,
236+
errorFormatText: widget.errorFormatText,
237+
builder: widget.pickerBuilder,
238+
errorInvalidRangeText: widget.errorInvalidRangeText,
239+
errorInvalidText: widget.errorInvalidText,
240+
fieldEndHintText: widget.fieldEndHintText,
241+
fieldEndLabelText: widget.fieldEndLabelText,
242+
fieldStartHintText: widget.fieldStartHintText,
243+
fieldStartLabelText: widget.fieldStartLabelText,
244+
helpText: widget.helpText,
245+
initialDateRange: value,
246+
initialEntryMode: widget.initialEntryMode,
247+
routeSettings: widget.routeSettings,
248+
saveText: widget.saveText,
249+
useRootNavigator: widget.useRootNavigator,
250+
);
251+
if (picked != null) {
252+
didChange(picked);
247253
}
248254
}
249255

@@ -277,14 +283,15 @@ class _FormBuilderDateRangePickerState extends FormBuilderFieldDecorationState<
277283
@override
278284
InputDecoration get decoration => widget.allowClear
279285
? super.decoration.copyWith(
280-
suffix: IconButton(
286+
suffix: IconButton(
281287
padding: EdgeInsets.zero,
282288
constraints: const BoxConstraints(maxWidth: 24, maxHeight: 24),
283289
onPressed: () {
284290
focus();
285291
didChange(null);
286-
effectiveFocusNode.unfocus();
287292
},
288-
icon: widget.clearIcon ?? const Icon(Icons.clear)))
293+
icon: widget.clearIcon ?? const Icon(Icons.clear),
294+
),
295+
)
289296
: super.decoration;
290297
}

lib/src/fields/form_builder_date_time_picker.dart

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -201,39 +201,43 @@ class FormBuilderDateTimePicker extends FormBuilderFieldDecoration<DateTime> {
201201
builder: (FormFieldState<DateTime?> field) {
202202
final state = field as _FormBuilderDateTimePickerState;
203203

204-
return TextField(
205-
textDirection: textDirection,
206-
textAlign: textAlign,
207-
textAlignVertical: textAlignVertical,
208-
maxLength: maxLength,
209-
autofocus: autofocus,
210-
decoration: state.decoration,
211-
readOnly: true,
212-
enabled: state.enabled,
213-
autocorrect: autocorrect,
214-
controller: state._textFieldController,
215-
focusNode: state.effectiveFocusNode,
216-
inputFormatters: inputFormatters,
217-
keyboardType: keyboardType,
218-
maxLines: maxLines,
219-
obscureText: obscureText,
220-
showCursor: showCursor,
221-
minLines: minLines,
222-
expands: expands,
223-
style: style,
224-
onEditingComplete: onEditingComplete,
225-
buildCounter: buildCounter,
226-
mouseCursor: mouseCursor,
227-
cursorColor: cursorColor,
228-
cursorRadius: cursorRadius,
229-
cursorWidth: cursorWidth,
230-
enableInteractiveSelection: enableInteractiveSelection,
231-
keyboardAppearance: keyboardAppearance,
232-
scrollPadding: scrollPadding,
233-
strutStyle: strutStyle,
234-
textCapitalization: textCapitalization,
235-
textInputAction: textInputAction,
236-
maxLengthEnforcement: maxLengthEnforcement,
204+
return FocusTraversalGroup(
205+
policy: ReadingOrderTraversalPolicy(),
206+
child: TextField(
207+
onTap: () => state.showPicker(),
208+
textDirection: textDirection,
209+
textAlign: textAlign,
210+
textAlignVertical: textAlignVertical,
211+
maxLength: maxLength,
212+
autofocus: autofocus,
213+
decoration: state.decoration,
214+
readOnly: true,
215+
enabled: state.enabled,
216+
autocorrect: autocorrect,
217+
controller: state._textFieldController,
218+
focusNode: state.effectiveFocusNode,
219+
inputFormatters: inputFormatters,
220+
keyboardType: keyboardType,
221+
maxLines: maxLines,
222+
obscureText: obscureText,
223+
showCursor: showCursor,
224+
minLines: minLines,
225+
expands: expands,
226+
style: style,
227+
onEditingComplete: onEditingComplete,
228+
buildCounter: buildCounter,
229+
mouseCursor: mouseCursor,
230+
cursorColor: cursorColor,
231+
cursorRadius: cursorRadius,
232+
cursorWidth: cursorWidth,
233+
enableInteractiveSelection: enableInteractiveSelection,
234+
keyboardAppearance: keyboardAppearance,
235+
scrollPadding: scrollPadding,
236+
strutStyle: strutStyle,
237+
textCapitalization: textCapitalization,
238+
textInputAction: textInputAction,
239+
maxLengthEnforcement: maxLengthEnforcement,
240+
),
237241
);
238242
},
239243
);
@@ -258,36 +262,38 @@ class _FormBuilderDateTimePickerState extends FormBuilderFieldDecorationState<
258262
final initVal = value;
259263
_textFieldController.text =
260264
initVal == null ? '' : _dateFormat.format(initVal);
261-
effectiveFocusNode.addListener(_handleFocus);
265+
266+
effectiveFocusNode.onKeyEvent = (node, event) {
267+
if (event is KeyDownEvent &&
268+
event.logicalKey == LogicalKeyboardKey.space &&
269+
node.hasFocus) {
270+
showPicker();
271+
return KeyEventResult.handled;
272+
}
273+
return KeyEventResult.ignored;
274+
};
262275
}
263276

264277
@override
265278
void dispose() {
266-
effectiveFocusNode.removeListener(_handleFocus);
267279
// Dispose the _textFieldController when initState created it
268280
if (null == widget.controller) {
269281
_textFieldController.dispose();
270282
}
271283
super.dispose();
272284
}
273285

274-
Future<void> _handleFocus() async {
275-
if (effectiveFocusNode.hasFocus && enabled) {
276-
effectiveFocusNode.unfocus();
277-
await onShowPicker(value);
278-
}
279-
}
280-
281286
DateFormat _getDefaultDateTimeFormat() {
282287
final languageCode = widget.locale?.languageCode;
283-
switch (widget.inputType) {
284-
case InputType.time:
285-
return DateFormat.Hm(languageCode);
286-
case InputType.date:
287-
return DateFormat.yMd(languageCode);
288-
case InputType.both:
289-
return DateFormat.yMd(languageCode).add_Hms();
290-
}
288+
return switch (widget.inputType) {
289+
InputType.time => DateFormat.Hm(languageCode),
290+
InputType.date => DateFormat.yMd(languageCode),
291+
InputType.both => DateFormat.yMd(languageCode).add_Hms()
292+
};
293+
}
294+
295+
Future<void> showPicker() async {
296+
await onShowPicker(value);
291297
}
292298

293299
Future<DateTime?> onShowPicker(DateTime? currentValue) async {

0 commit comments

Comments
 (0)