fix: auto focus on link name textfield after open the link_edit_menu (#7757)

* fix: auto focus on link name textfield after open the link_edit_menu

* chore: update pubspec.yaml
This commit is contained in:
Morn 2025-04-16 16:56:37 +08:00 committed by GitHub
parent f727dde74b
commit 13acc3af86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 100 additions and 70 deletions

View File

@ -13,6 +13,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:appflowy_editor/src/editor/util/link_util.dart'; import 'package:appflowy_editor/src/editor/util/link_util.dart';
import 'package:flutter/services.dart';
import 'link_create_menu.dart'; import 'link_create_menu.dart';
import 'link_search_text_field.dart'; import 'link_search_text_field.dart';
import 'link_styles.dart'; import 'link_styles.dart';
@ -38,15 +39,14 @@ class LinkEditMenu extends StatefulWidget {
} }
class _LinkEditMenuState extends State<LinkEditMenu> { class _LinkEditMenuState extends State<LinkEditMenu> {
ValueChanged<LinkInfo> get onApply => widget.onApply;
ValueChanged<LinkInfo> get onRemoveLink => widget.onRemoveLink; ValueChanged<LinkInfo> get onRemoveLink => widget.onRemoveLink;
VoidCallback get onDismiss => widget.onDismiss; VoidCallback get onDismiss => widget.onDismiss;
late TextEditingController linkNameController = late TextEditingController linkNameController =
TextEditingController(text: linkInfo.name); TextEditingController(text: linkInfo.name);
final textFocusNode = FocusNode(); late FocusNode textFocusNode = FocusNode(onKeyEvent: onFocusKeyEvent);
late FocusNode menuFocusNode = FocusNode(onKeyEvent: onFocusKeyEvent);
late LinkInfo linkInfo = widget.linkInfo; late LinkInfo linkInfo = widget.linkInfo;
late LinkSearchTextField searchTextField; late LinkSearchTextField searchTextField;
bool isShowingSearchResult = false; bool isShowingSearchResult = false;
@ -74,12 +74,14 @@ class _LinkEditMenuState extends State<LinkEditMenu> {
if (mounted) setState(() {}); if (mounted) setState(() {});
}, },
)..searchRecentViews(); )..searchRecentViews();
makeSureHasFocus();
} }
@override @override
void dispose() { void dispose() {
linkNameController.dispose(); linkNameController.dispose();
textFocusNode.dispose(); textFocusNode.dispose();
menuFocusNode.dispose();
searchTextField.dispose(); searchTextField.dispose();
super.dispose(); super.dispose();
} }
@ -91,56 +93,59 @@ class _LinkEditMenuState extends State<LinkEditMenu> {
final errorHeight = showErrorText ? 20.0 : 0.0; final errorHeight = showErrorText ? 20.0 : 0.0;
return GestureDetector( return GestureDetector(
onTap: onDismiss, onTap: onDismiss,
child: Container( child: Focus(
width: 400, focusNode: menuFocusNode,
height: 250 + (showingRecent ? 32 : 0), child: Container(
color: Colors.white.withAlpha(1), width: 400,
child: Stack( height: 250 + (showingRecent ? 32 : 0),
children: [ color: Colors.white.withAlpha(1),
GestureDetector( child: Stack(
onTap: hideSearchResult, children: [
child: Container( GestureDetector(
width: 400, onTap: hideSearchResult,
height: 192 + errorHeight, child: Container(
decoration: buildToolbarLinkDecoration(context), width: 400,
height: 192 + errorHeight,
decoration: buildToolbarLinkDecoration(context),
),
), ),
), Positioned(
Positioned( top: 16,
top: 16, left: 20,
left: 20, child: FlowyText.semibold(
child: FlowyText.semibold( LocaleKeys.document_toolbar_pageOrURL.tr(),
LocaleKeys.document_toolbar_pageOrURL.tr(), color: LinkStyle.textTertiary,
color: LinkStyle.textTertiary, fontSize: 12,
fontSize: 12, figmaLineHeight: 16,
figmaLineHeight: 16, ),
), ),
), Positioned(
Positioned( top: 80 + errorHeight,
top: 80 + errorHeight, left: 20,
left: 20, child: FlowyText.semibold(
child: FlowyText.semibold( LocaleKeys.document_toolbar_linkName.tr(),
LocaleKeys.document_toolbar_linkName.tr(), color: LinkStyle.textTertiary,
color: LinkStyle.textTertiary, fontSize: 12,
fontSize: 12, figmaLineHeight: 16,
figmaLineHeight: 16, ),
), ),
), Positioned(
Positioned( top: 144 + errorHeight,
top: 144 + errorHeight, left: 20,
left: 20, child: buildButtons(),
child: buildButtons(), ),
), Positioned(
Positioned( top: 100 + errorHeight,
top: 100 + errorHeight, left: 20,
left: 20, child: buildNameTextField(),
child: buildNameTextField(), ),
), Positioned(
Positioned( top: 36,
top: 36, left: 20,
left: 20, child: buildLinkField(),
child: buildLinkField(), ),
), ],
], ),
), ),
), ),
); );
@ -254,23 +259,7 @@ class _LinkEditMenuState extends State<LinkEditMenu> {
fontColor: Colors.white, fontColor: Colors.white,
fillColor: LinkStyle.fillThemeThick, fillColor: LinkStyle.fillThemeThick,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
onPressed: () { onPressed: onApply,
if (isShowingSearchResult) {
onConfirm();
return;
}
if (linkInfo.link.isEmpty) {
widget.onRemoveLink(linkInfo);
return;
}
if (linkInfo.link.isEmpty || !isUri(linkInfo.link)) {
setState(() {
showErrorText = true;
});
return;
}
widget.onApply.call(linkInfo);
},
); );
}, },
), ),
@ -287,6 +276,7 @@ class _LinkEditMenuState extends State<LinkEditMenu> {
child: TextFormField( child: TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
focusNode: textFocusNode, focusNode: textFocusNode,
autofocus: true,
textAlign: TextAlign.left, textAlign: TextAlign.left,
controller: linkNameController, controller: linkNameController,
style: TextStyle( style: TextStyle(
@ -395,6 +385,45 @@ class _LinkEditMenuState extends State<LinkEditMenu> {
); );
} }
KeyEventResult onFocusKeyEvent(FocusNode node, KeyEvent key) {
if (key is! KeyDownEvent) return KeyEventResult.ignored;
if (key.logicalKey == LogicalKeyboardKey.enter) {
onApply();
return KeyEventResult.handled;
} else if (key.logicalKey == LogicalKeyboardKey.escape) {
onDismiss();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
}
Future<void> makeSureHasFocus() async {
final focusNode = textFocusNode;
if (!mounted || focusNode.hasFocus) return;
focusNode.requestFocus();
WidgetsBinding.instance.addPostFrameCallback((_) {
makeSureHasFocus();
});
}
void onApply() {
if (isShowingSearchResult) {
onConfirm();
return;
}
if (linkInfo.link.isEmpty) {
widget.onRemoveLink(linkInfo);
return;
}
if (linkInfo.link.isEmpty || !isUri(linkInfo.link)) {
setState(() {
showErrorText = true;
});
return;
}
widget.onApply.call(linkInfo);
}
void onConfirm() { void onConfirm() {
searchTextField.onSearchResult( searchTextField.onSearchResult(
onLink: onLinkSelected, onLink: onLinkSelected,
@ -404,6 +433,7 @@ class _LinkEditMenuState extends State<LinkEditMenu> {
searchTextField.unfocus(); searchTextField.unfocus();
}, },
); );
menuFocusNode.requestFocus();
} }
Future<void> getPageView() async { Future<void> getPageView() async {

View File

@ -98,8 +98,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "2361899" ref: "680222f"
resolved-ref: "23618990b2f4ab88df67d50598b2b53cd6853e0a" resolved-ref: "680222f503f90d07c08c99c42764f9b08fd0f46c"
url: "https://github.com/AppFlowy-IO/appflowy-editor.git" url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git source: git
version: "5.1.0" version: "5.1.0"

View File

@ -187,7 +187,7 @@ dependency_overrides:
appflowy_editor: appflowy_editor:
git: git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "2361899" ref: "680222f"
appflowy_editor_plugins: appflowy_editor_plugins:
git: git: