Repository: ionic-team/ionic-framework
File: core/src/components/action-sheet/action-sheet.tsx (TypeScript)

Code before:
    return `action-sheet-button-${this.overlayIndex}-${buttonIndex}`;
  }

  private onBackdropTap = () => {
    this.dismiss(undefined, BACKDROP);
  };

  private dispatchCancelHandler = (ev: CustomEvent) => {
    const role = ev.detail.role;
    if (isCancel(role)) {
      const cancelButton = this.getButtons().find((b) => b.role === 'cancel');
      this.callButtonHandler(cancelButton);
    }
  };

  /**
   * When the action sheet has radio buttons, we want to follow the
   * keyboard navigation pattern for radio groups:
   * - Arrow Down/Right: Move to the next radio button (wrap to first if at end)
   * - Arrow Up/Left: Move to the previous radio button (wrap to last if at start)
   * - Space/Enter: Select the focused radio button and trigger its handler
   */
  @Listen('keydown')
  onKeydown(ev: KeyboardEvent) {
    // Only handle keyboard navigation if we have radio buttons
    if (!this.hasRadioButtons || !this.presented) {
      return;
    }

    const target = ev.target as HTMLElement;

    // Ignore if the target element is not within the action sheet or not a radio button
    if (
      !this.el.contains(target) ||
      !target.classList.contains('action-sheet-button') ||
      target.getAttribute('role') !== 'radio'
    ) {
      return;
    }

    // Get all radio button elements and filter out disabled ones
    const radios = Array.from(this.el.querySelectorAll('.action-sheet-button[role="radio"]')).filter(
      (el) => !(el as HTMLButtonElement).disabled
    ) as HTMLButtonElement[];

    const currentIndex = radios.findIndex((radio) => radio.id === target.id);
    if (currentIndex === -1) {
      return;
    }

    let nextEl: HTMLButtonElement | undefined;

Review comment (suggestion):
We don't have to do it on this PR, but I think a lot of this code could be moved to a shared util that is also used by radio group:

https://github.com/ionic-team/ionic-framework/blob/bf0f1e36e4dd73c968ade75c714b89b3f10dd4ef/core/src/components/radio-group/radio-group.tsx#L232-L294

Code after:
    }
    const allButtons = this.getButtons();
    const buttonIndex = index !== undefined ? index : allButtons.indexOf(button);
    return `action-sheet-button-${this.overlayIndex}-${buttonIndex}`;
  }

  private onBackdropTap = () => {
    this.dismiss(undefined, BACKDROP);
  };

  private dispatchCancelHandler = (ev: CustomEvent) => {
    const role = ev.detail.role;
    if (isCancel(role)) {
      const cancelButton = this.getButtons().find((b) => b.role === 'cancel');
      this.callButtonHandler(cancelButton);
    }
  };

  /**
   * When the action sheet has radio buttons, we want to follow the
   * keyboard navigation pattern for radio groups:
   * - Arrow Down/Right: Move to the next radio button (wrap to first if at end)
   * - Arrow Up/Left: Move to the previous radio button (wrap to last if at start)
   * - Space/Enter: Select the focused radio button and trigger its handler
   */
  @Listen('keydown')
  onKeydown(ev: KeyboardEvent) {
    // Only handle keyboard navigation if we have radio buttons
    if (!this.hasRadioButtons || !this.presented) {
      return;
    }

    const target = ev.target as HTMLElement;

    // Ignore if the target element is not within the action sheet or not a radio button
    if (
      !this.el.contains(target) ||
      !target.classList.contains('action-sheet-button') ||
      target.getAttribute('role') !== 'radio'
    ) {
      return;
    }

    // Get all radio button elements and filter out disabled ones
    const radios = Array.from(this.el.querySelectorAll('.action-sheet-button[role="radio"]')).filter(
      (el) => !(el as HTMLButtonElement).disabled
    ) as HTMLButtonElement[];
    const currentIndex = radios.findIndex((radio) => radio.id === target.id);

    if (currentIndex === -1) {
      return;