Skip to content

NSTabView doesn't send mouseUp event because it doesn't inherit from NSControl #2111

@eablokker

Description

@eablokker

Environment

react-native -v: command not found (Metro says v0.73.2)
npm ls react-native-macos: react-native-macos@0.73.15
node -v: v18.19.0
npm -v: 10.3.0
yarn --version: 3.6.1
xcodebuild -version: Xcode 13.2.1 Build version 13C100

Steps to reproduce the bug

  1. Create a custom native tabView component
// MyTabViewManager.m

#import <AppKit/AppKit.h>
#import <React/RCTViewManager.h>

@interface MyTabView : NSTabView
@end

@interface MyTabViewManager : RCTViewManager<NSTabViewDelegate>
@end

@implementation MyTabViewManager

RCT_EXPORT_MODULE(MyTabView)

- (MyTabView *)view {
  MyTabView *tabView = [[MyTabView alloc] init];
  tabView.delegate = self;

  // Insert some tabs
  NSTabViewItem *tabViewItem = [[NSTabViewItem alloc] init];
  tabViewItem.label = @"Tab One";

  // Add empty view
  tabViewItem.view = [NSView new];

  [tabView addTabViewItem:tabViewItem];

  // Insert another tab
  NSTabViewItem *tabViewItem2 = [[NSTabViewItem alloc] init];
  tabViewItem2.label = @"Tab Two";

  // Add empty view
  tabViewItem2.view = [NSView new];

  [tabView addTabViewItem:tabViewItem2];

  return tabView;
}
  1. Create a TabView React component
// MyTabView.tsx

import React from 'react';
import {requireNativeComponent} from 'react-native';

const TabView = () => {
  return <MyTabView style={{ height: 300 }} />;
};

const MyTabView = requireNativeComponent('MyTabView');

module.exports = TabView;
  1. Click on tab label buttons.

Expected Behavior

The tab buttons should activate on click.

Actual Behavior

The first click activates the button but the second click logs the error "Touch is already recorded. This is a critical bug." On the first click, the mouseDown event fires but mouseUp does not. On the second click the mouseDown does not fire but mouseUp does.

Reproducible Demo

No response

Additional context

The bug is located in RCTTouchHandler

// Pair the mouse down events with mouse up events so our _nativeTouches cache doesn't get stale
if ([targetView isKindOfClass:[NSControl class]]) {
  _shouldSendMouseUpOnSystemBehalf = [(NSControl*)targetView isEnabled];
} else if ([targetView isKindOfClass:[NSText class]]) {
  _shouldSendMouseUpOnSystemBehalf = [(NSText*)targetView isSelectable];
}
else if ([targetView.superview isKindOfClass:[RCTUITextField class]]) {
  _shouldSendMouseUpOnSystemBehalf = [(RCTUITextField*)targetView.superview isSelectable];
} else {
  _shouldSendMouseUpOnSystemBehalf = NO;
}

NSTabView does not inherit from NSControl. According to the view hierarchy inspector this is the class inheritance hierarchy:

  • NSTabView
  • NSView
  • NSResponder
  • NSObject

This code change seems to work

} else if ([targetView isKindOfClass:[NSTabView class]]) {
  _shouldSendMouseUpOnSystemBehalf = YES;
}

However this by itself seems to still have click locations in the wrong place. If I combine this with a hitTest on the custom NSTabView then it works:

- (NSView *)hitTest:(NSPoint)aPoint {
  if (!self.isHidden && [self mouse:aPoint inRect:self.bounds]) {
    // Convert point to each subview and check
    for (NSView *subview in [self.subviews reverseObjectEnumerator]) {
      NSPoint convertedPoint = [self convertPoint:aPoint toView:subview];
      NSView *hitTestView = [subview hitTest:convertedPoint];
      if (hitTestView) {
        return hitTestView;
      }
    }
    // If no subview should handle this point, return self or nil based on whether this view should handle clicks
    return self;
  }
  return nil;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions