Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7dfcd62

Browse filesBrowse files
add useFocusScopeNode hook (#317)
1 parent 99738db commit 7dfcd62
Copy full SHA for 7dfcd62

File tree

Expand file treeCollapse file tree

5 files changed

+232
-4
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+232
-4
lines changed

‎packages/flutter_hooks/CHANGELOG.md

Copy file name to clipboardExpand all lines: packages/flutter_hooks/CHANGELOG.md
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
## Unreleased docs
2-
3-
Added korean translation thanks to @sejun2
1+
## Unreleased minor
42

3+
- Added korean translation (thanks to @sejun2)
4+
- Add `useFocusScopeNode` hook (thanks to @iamsahilsonawane)
55
## 0.18.5+1
66

77
Update links to the repository
+74Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
part of 'hooks.dart';
2+
3+
/// Creates an automatically disposed [FocusScopeNode].
4+
///
5+
/// See also:
6+
/// - [FocusScopeNode]
7+
FocusScopeNode useFocusScopeNode({
8+
String? debugLabel,
9+
FocusOnKeyCallback? onKey,
10+
FocusOnKeyEventCallback? onKeyEvent,
11+
bool skipTraversal = false,
12+
bool canRequestFocus = true,
13+
}) {
14+
return use(
15+
_FocusScopeNodeHook(
16+
debugLabel: debugLabel,
17+
onKey: onKey,
18+
onKeyEvent: onKeyEvent,
19+
skipTraversal: skipTraversal,
20+
canRequestFocus: canRequestFocus,
21+
),
22+
);
23+
}
24+
25+
class _FocusScopeNodeHook extends Hook<FocusScopeNode> {
26+
const _FocusScopeNodeHook({
27+
this.debugLabel,
28+
this.onKey,
29+
this.onKeyEvent,
30+
required this.skipTraversal,
31+
required this.canRequestFocus,
32+
});
33+
34+
final String? debugLabel;
35+
final FocusOnKeyCallback? onKey;
36+
final FocusOnKeyEventCallback? onKeyEvent;
37+
final bool skipTraversal;
38+
final bool canRequestFocus;
39+
40+
@override
41+
_FocusScopeNodeHookState createState() {
42+
return _FocusScopeNodeHookState();
43+
}
44+
}
45+
46+
class _FocusScopeNodeHookState
47+
extends HookState<FocusScopeNode, _FocusScopeNodeHook> {
48+
late final FocusScopeNode _focusScopeNode = FocusScopeNode(
49+
debugLabel: hook.debugLabel,
50+
onKey: hook.onKey,
51+
onKeyEvent: hook.onKeyEvent,
52+
skipTraversal: hook.skipTraversal,
53+
canRequestFocus: hook.canRequestFocus,
54+
);
55+
56+
@override
57+
void didUpdateHook(_FocusScopeNodeHook oldHook) {
58+
_focusScopeNode
59+
..debugLabel = hook.debugLabel
60+
..skipTraversal = hook.skipTraversal
61+
..canRequestFocus = hook.canRequestFocus
62+
..onKey = hook.onKey
63+
..onKeyEvent = hook.onKeyEvent;
64+
}
65+
66+
@override
67+
FocusScopeNode build(BuildContext context) => _focusScopeNode;
68+
69+
@override
70+
void dispose() => _focusScopeNode.dispose();
71+
72+
@override
73+
String get debugLabel => 'useFocusScopeNode';
74+
}

‎packages/flutter_hooks/lib/src/hooks.dart

Copy file name to clipboardExpand all lines: packages/flutter_hooks/lib/src/hooks.dart
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ part 'misc.dart';
1414
part 'primitives.dart';
1515
part 'tab_controller.dart';
1616
part 'text_controller.dart';
17-
part 'focus.dart';
17+
part 'focus_node.dart';
18+
part 'focus_scope_node.dart';
1819
part 'scroll_controller.dart';
1920
part 'page_controller.dart';
2021
part 'widgets_binding_observer.dart';
+153Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import 'package:flutter/foundation.dart';
2+
import 'package:flutter/widgets.dart';
3+
import 'package:flutter_hooks/flutter_hooks.dart';
4+
5+
import 'mock.dart';
6+
7+
void main() {
8+
testWidgets('creates a focus scope node and disposes it', (tester) async {
9+
late FocusScopeNode focusScopeNode;
10+
await tester.pumpWidget(
11+
HookBuilder(builder: (_) {
12+
focusScopeNode = useFocusScopeNode();
13+
return Container();
14+
}),
15+
);
16+
17+
expect(focusScopeNode, isA<FocusScopeNode>());
18+
// ignore: invalid_use_of_protected_member
19+
expect(focusScopeNode.hasListeners, isFalse);
20+
21+
final previousValue = focusScopeNode;
22+
23+
await tester.pumpWidget(
24+
HookBuilder(builder: (_) {
25+
focusScopeNode = useFocusScopeNode();
26+
return Container();
27+
}),
28+
);
29+
30+
expect(previousValue, focusScopeNode);
31+
// ignore: invalid_use_of_protected_member
32+
expect(focusScopeNode.hasListeners, isFalse);
33+
34+
await tester.pumpWidget(Container());
35+
36+
expect(
37+
() => focusScopeNode.dispose(),
38+
throwsAssertionError,
39+
);
40+
});
41+
42+
testWidgets('debugFillProperties', (tester) async {
43+
await tester.pumpWidget(
44+
HookBuilder(builder: (context) {
45+
useFocusScopeNode();
46+
return const SizedBox();
47+
}),
48+
);
49+
50+
final element = tester.element(find.byType(HookBuilder));
51+
52+
expect(
53+
element
54+
.toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage)
55+
.toStringDeep(),
56+
equalsIgnoringHashCodes(
57+
'HookBuilder\n'
58+
' │ useFocusScopeNode: FocusScopeNode#00000\n'
59+
' └SizedBox(renderObject: RenderConstrainedBox#00000)\n',
60+
),
61+
);
62+
});
63+
64+
testWidgets('default values matches with FocusScopeNode', (tester) async {
65+
final official = FocusScopeNode();
66+
67+
late FocusScopeNode focusScopeNode;
68+
await tester.pumpWidget(
69+
HookBuilder(builder: (_) {
70+
focusScopeNode = useFocusScopeNode();
71+
return Container();
72+
}),
73+
);
74+
75+
expect(focusScopeNode.debugLabel, official.debugLabel);
76+
expect(focusScopeNode.onKey, official.onKey);
77+
expect(focusScopeNode.skipTraversal, official.skipTraversal);
78+
expect(focusScopeNode.canRequestFocus, official.canRequestFocus);
79+
});
80+
81+
testWidgets('has all the FocusScopeNode parameters', (tester) async {
82+
KeyEventResult onKey(FocusNode node, RawKeyEvent event) =>
83+
KeyEventResult.ignored;
84+
85+
KeyEventResult onKeyEvent(FocusNode node, KeyEvent event) =>
86+
KeyEventResult.ignored;
87+
88+
late FocusScopeNode focusScopeNode;
89+
await tester.pumpWidget(
90+
HookBuilder(builder: (_) {
91+
focusScopeNode = useFocusScopeNode(
92+
debugLabel: 'Foo',
93+
onKey: onKey,
94+
onKeyEvent: onKeyEvent,
95+
skipTraversal: true,
96+
canRequestFocus: false,
97+
);
98+
return Container();
99+
}),
100+
);
101+
102+
expect(focusScopeNode.debugLabel, 'Foo');
103+
expect(focusScopeNode.onKey, onKey);
104+
expect(focusScopeNode.onKeyEvent, onKeyEvent);
105+
expect(focusScopeNode.skipTraversal, true);
106+
expect(focusScopeNode.canRequestFocus, false);
107+
});
108+
109+
testWidgets('handles parameter change', (tester) async {
110+
KeyEventResult onKey(FocusNode node, RawKeyEvent event) =>
111+
KeyEventResult.ignored;
112+
KeyEventResult onKey2(FocusNode node, RawKeyEvent event) =>
113+
KeyEventResult.ignored;
114+
115+
KeyEventResult onKeyEvent(FocusNode node, KeyEvent event) =>
116+
KeyEventResult.ignored;
117+
KeyEventResult onKeyEvent2(FocusNode node, KeyEvent event) =>
118+
KeyEventResult.ignored;
119+
120+
late FocusScopeNode focusScopeNode;
121+
await tester.pumpWidget(
122+
HookBuilder(builder: (_) {
123+
focusScopeNode = useFocusScopeNode(
124+
debugLabel: 'Foo',
125+
onKey: onKey,
126+
onKeyEvent: onKeyEvent,
127+
skipTraversal: true,
128+
canRequestFocus: false,
129+
);
130+
131+
return Container();
132+
}),
133+
);
134+
135+
await tester.pumpWidget(
136+
HookBuilder(builder: (_) {
137+
focusScopeNode = useFocusScopeNode(
138+
debugLabel: 'Bar',
139+
onKey: onKey2,
140+
onKeyEvent: onKeyEvent2,
141+
);
142+
143+
return Container();
144+
}),
145+
);
146+
147+
expect(focusScopeNode.onKey, onKey2);
148+
expect(focusScopeNode.onKeyEvent, onKeyEvent2);
149+
expect(focusScopeNode.debugLabel, 'Bar');
150+
expect(focusScopeNode.skipTraversal, false);
151+
expect(focusScopeNode.canRequestFocus, true);
152+
});
153+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.