Skip to content
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

Accept focusNode property in DropdownItem #260

Open
menghinidev opened this issue Apr 22, 2024 · 0 comments
Open

Accept focusNode property in DropdownItem #260

menghinidev opened this issue Apr 22, 2024 · 0 comments

Comments

@menghinidev
Copy link

Issue
When creating a new DropdownItem it should be possible to assign a focusNode instead of making the package define it for each item in the menu.
Developers need to access item's focus node in order to achive custom behaviour and at the moment it is not possibile.

Proposal
Accept FocusNode? focusNode as constructor parameter for DropdownItem.
Below an idea of what the code should look like:

class DropdownItem<T> extends _DropdownMenuItemContainer {
  /// Creates a dropdown item.
  ///
  /// The [child] property must be set.
  const DropdownItem({
    required super.child,
    super.height,
    super.intrinsicHeight,
    super.alignment,
    this.onTap,
    this.value,
    this.enabled = true,
    this.closeOnTap = true,
    this.focusNode,
    super.key,
  });

  /// Called when the dropdown menu item is tapped.
  final VoidCallback? onTap;

  /// Called when the dropdown menu item is tapped.
  final FocusNode? focusNode;

  /// The value to return if the user selects this menu item.
  ///
  /// Eventually returned in a call to [DropdownButton.onChanged].
  final T? value;

  /// Whether or not a user can select this menu item.
  ///
  /// Defaults to `true`.
  final bool enabled;

  /// Whether the dropdown should close when the item is tapped.
  ///
  /// Defaults to true.
  final bool closeOnTap;

  /// Creates a copy of this DropdownItem but with the given fields replaced with the new values.
  DropdownItem<T> copyWith({
    Widget? child,
    double? height,
    bool? intrinsicHeight,
    void Function()? onTap,
    T? value,
    bool? enabled,
    AlignmentGeometry? alignment,
    bool? closeOnTap,
    FocusNode? focusNode,
  }) {
    return DropdownItem<T>(
      height: height ?? this.height,
      intrinsicHeight: intrinsicHeight ?? this.intrinsicHeight,
      onTap: onTap ?? this.onTap,
      value: value ?? this.value,
      enabled: enabled ?? this.enabled,
      alignment: alignment ?? this.alignment,
      closeOnTap: closeOnTap ?? this.closeOnTap,
      focusNode: focusNode,
      child: child ?? this.child,
    );
  }
}

And it should be passed by _DropdownItemButton to the proper InkWell, as my understanding it should be something like this but didn't try it as a solution.
If you have any other ways in order to achive custom usage of items focusNode please reach me out here.

class _DropdownItemButtonState<T> extends State<_DropdownItemButton<T>> {
  void _handleFocusChange(bool focused) {
    final bool inTraditionalMode;
    switch (FocusManager.instance.highlightMode) {
      case FocusHighlightMode.touch:
        inTraditionalMode = false;
        // TODO(Ahmed): Remove decorative breaks and add lint to it [flutter>=v3.10.0].
        break;
      case FocusHighlightMode.traditional:
        inTraditionalMode = true;
        break;
    }

    if (focused && inTraditionalMode) {
      final _MenuLimits menuLimits = widget.route.getMenuLimits(
        widget.buttonRect,
        widget.constraints.maxHeight,
        widget.mediaQueryPadding,
        widget.itemIndex,
      );
      widget.route.scrollController!.animateTo(
        menuLimits.scrollOffset,
        curve: Curves.easeInOut,
        duration: const Duration(milliseconds: 100),
      );
    }
  }

  void _handleOnTap() {
    final DropdownItem<T> dropdownItem = widget.route.items[widget.itemIndex];

    dropdownItem.onTap?.call();

    if (dropdownItem.closeOnTap) {
      Navigator.pop(
        context,
        _DropdownRouteResult<T>(dropdownItem.value),
      );
    }
  }

  static const Map<ShortcutActivator, Intent> _webShortcuts =
      <ShortcutActivator, Intent>{
    // On the web, up/down don't change focus, *except* in a <select>
    // element, which is what a dropdown emulates.
    SingleActivator(LogicalKeyboardKey.arrowDown):
        DirectionalFocusIntent(TraversalDirection.down),
    SingleActivator(LogicalKeyboardKey.arrowUp):
        DirectionalFocusIntent(TraversalDirection.up),
  };

  MenuItemStyleData get menuItemStyle => widget.route.menuItemStyle;

  @override
  Widget build(BuildContext context) {
    final double menuCurveEnd = widget.route.dropdownStyle.openInterval.end;

    final DropdownItem<T> dropdownItem = widget.route.items[widget.itemIndex];
    final double unit = 0.5 / (widget.route.items.length + 1.5);
    final double start =
        _clampDouble(menuCurveEnd + (widget.itemIndex + 1) * unit, 0.0, 1.0);
    final double end = _clampDouble(start + 1.5 * unit, 0.0, 1.0);
    final CurvedAnimation opacity = CurvedAnimation(
        parent: widget.route.animation!, curve: Interval(start, end));

    Widget child = Container(
      padding: (menuItemStyle.padding ?? _kMenuItemPadding)
          .resolve(widget.textDirection),
      child: dropdownItem,
    );
    // An [InkWell] is added to the item only if it is enabled
    // isNoSelectedItem to avoid first item highlight when no item selected
    if (dropdownItem.enabled) {
      final bool isSelectedItem = !widget.route.isNoSelectedItem &&
          widget.itemIndex == widget.route.selectedIndex;
      child = InkWell(
        focusNode: dropdownItem.focusNode,
        autofocus: isSelectedItem,
        enableFeedback: widget.enableFeedback,
        onTap: _handleOnTap,
        onFocusChange: _handleFocusChange,
        borderRadius: menuItemStyle.borderRadius,
        overlayColor: menuItemStyle.overlayColor,
        child: isSelectedItem
            ? menuItemStyle.selectedMenuItemBuilder?.call(context, child) ??
                child
            : child,
      );
    }
    child = FadeTransition(opacity: opacity, child: child);
    if (kIsWeb && dropdownItem.enabled) {
      child = Shortcuts(
        shortcuts: _webShortcuts,
        child: child,
      );
    }
    return child;
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant