How does controller, controllerGrip, and hand communicate with XR device buttons in threejs?

(May 15 2023) In this example from threejs, the controllers are defined using these codes:

// controllers

controller1 = renderer.xr.getController( 0 );
controller1.addEventListener( 'selectstart', onSelectStart );
controller1.addEventListener( 'selectend', onSelectEnd );
scene.add( controller1 );

controller2 = renderer.xr.getController( 1 );
controller2.addEventListener( 'selectstart', onSelectStart );
controller2.addEventListener( 'selectend', onSelectEnd );
scene.add( controller2 );

const controllerModelFactory = new XRControllerModelFactory();

controllerGrip1 = renderer.xr.getControllerGrip( 0 );
controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) );
scene.add( controllerGrip1 );

controllerGrip2 = renderer.xr.getControllerGrip( 1 );
controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
scene.add( controllerGrip2 );

According to the documentation from threejs, both renderer.xr.getController and renderer.xr.getControllerGrip returns a THREE.Group.

.getController ( index : Integer ) : Group
index — The index of the controller.

Returns a Group representing the so called target ray space of the XR controller. Use this space for visualizing 3D objects that support the user in pointing tasks like UI interaction.

.getControllerGrip ( index : Integer ) : Group
index — The index of the controller.

Returns a Group representing the so called grip space of the XR controller. Use this space if the user is going to hold other 3D objects like a lightsaber.

Note: If you want to show something in the user’s hand AND offer a pointing ray at the same time, you’ll want to attached the handheld object to the group returned by .getControllerGrip() and the ray to the group returned by .getController(). The idea is to have two different groups in two different coordinate spaces for the same WebXR controller.

.getHand ( index : Integer ) : Group
index — The index of the controller.

Returns a Group representing the so called hand or joint space of the XR controller. Use this space for visualizing the user’s hands when no physical controllers are used.

My question is, if controller1/2 and controllerGrip1/2 are all just instances of Group, how does the controller1.addEventListener( 'selectstart', onSelectStart ); work so that the code is able respond to the device? And what is the difference between controller and controllerGrip and hands? It seems by only adding event listeners to the controller, the code also respond to hand pinch, so why is this?

When I add event listener to ‘selectstart’, if I am using hand input, where my understanding based on MDN’s docs is that my hand only fires ‘pinchstart’ event, but when I’m using threejs’s controller manager the event that is binded with ‘selectstart’ also get fired. Why is this? How can I choose to make ‘pinchstart’ react differently than controller’s ‘selectstart’?