html - Issue with Tabbing into Button Column Using Keyboard Suppress Event - Stack Overflow

I'm encountering an issue in my AG Grid implementation in Angular. I have added a keyboard suppres

I'm encountering an issue in my AG Grid implementation in Angular. I have added a keyboard suppress event to allow users to tab into a cell and interact with a button inside that cell using the keyboard. However, when the user tabs into the button column, they seem to get "locked" into the button, preventing further navigation using the Tab key. Tabbing through the headers works fine, but this issue only arises with the button inside the grid cell.

appponent.ts

import { Component } from '@angular/core';
import { AgGridAngular } from 'ag-grid-angular';
import type { ColDef, GridApi, SuppressKeyboardEventParams } from 'ag-grid-community';
import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community';

// Register all Community features
ModuleRegistry.registerModules([AllCommunityModule]);
import { ButtonRendererComponent } from './button-renderer/button-rendererponent';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AgGridAngular, ButtonRendererComponent],
  templateUrl: './appponent.html',
  styleUrls: ['./appponent.scss']
})
export class AppComponent {
  private gridApi!: GridApi;
  searchText = '';

  rowData = [
    { name: "Dog", type: "Mammal", size: "Medium", speed: "Fast", lifespan: "10-15 years" },
    { name: "Cat", type: "Mammal", size: "Small", speed: "Fast", lifespan: "12-18 years" },
    { name: "Turtle", type: "Reptile", size: "Small", speed: "Slow", lifespan: "50-100 years" },
    { name: "Rabbit", type: "Mammal", size: "Small", speed: "Fast", lifespan: "8-12 years" },
    { name: "Parrot", type: "Bird", size: "Small", speed: "Medium", lifespan: "50-80 years" },
    { name: "Elephant", type: "Mammal", size: "Large", speed: "Slow", lifespan: "60-70 years" }
  ];

  columnDefs: ColDef[] = [
    { field: "name", sortable: true, filter: true },
    { field: "type", sortable: true, filter: true },
    { field: "size", sortable: true, filter: true },
    { field: "speed", sortable: true, filter: true },
    { field: "lifespan", sortable: true, filter: true },
    {
      headerName: 'Action',
      field: 'action',
      cellRenderer: ButtonRendererComponent
    }
  ];

  public defaultColDef: ColDef = {
    minWidth: 130,
    suppressKeyboardEvent : this.suppressKeyboardEvent
  };

  onGridReady(params: any) {
    this.gridApi = params.api;
  }


  // Function to trigger the alert
  sayHello() {
    alert('Hello!');
  }

  GRID_CELL_CLASSNAME = 'ag-cell';

  getAllFocusableElementsOf(el: HTMLElement) {
  return Array.from<HTMLElement>(
    el.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    ),
  ).filter((focusableEl) => {
    return focusableEl.tabIndex !== -1;
  });
}

 getEventPath(event: Event): HTMLElement[] {
  const path: HTMLElement[] = [];
  let currentTarget: any = event.target;
  while (currentTarget) {
    path.push(currentTarget);
    currentTarget = currentTarget.parentElement;
  }
  return path;
}

 suppressKeyboardEvent({ event }: SuppressKeyboardEventParams<any>) {
  const { key, shiftKey } = event;
  const path = this.getEventPath(event);
  const isTabForward = key === 'Tab' && shiftKey === false;
  const isTabBackward = key === 'Tab' && shiftKey === true;
  let suppressEvent = false;

  if (isTabForward || isTabBackward) {
    const eGridCell = path.find((el) => {
      return el.classList?.contains(this.GRID_CELL_CLASSNAME);
    });

    if (!eGridCell) {
      return suppressEvent;
    }

    const focusableChildrenElements = this.getAllFocusableElementsOf(eGridCell);

    const lastCellChildEl = focusableChildrenElements[focusableChildrenElements.length - 1];
    const firstCellChildEl = focusableChildrenElements[0];

    if (focusableChildrenElements.length === 0) {
      return false;
    }

    const currentIndex = focusableChildrenElements.indexOf(document.activeElement as HTMLElement);

    if (isTabForward) {
      const isLastChildFocused = lastCellChildEl && document.activeElement === lastCellChildEl;
      if (!isLastChildFocused) {
        suppressEvent = true;
        event.preventDefault();
        focusableChildrenElements[currentIndex + 1].focus();
      }
    } else {
      const isFirstChildFocused = firstCellChildEl && document.activeElement === firstCellChildEl;
      if (!isFirstChildFocused) {
        suppressEvent = true;
        event.preventDefault();
        focusableChildrenElements[currentIndex - 1].focus();
      }
    }
  }
  return suppressEvent;
}

}

appponent.html

<ag-grid-angular 
  style="width: 100%; height: 500px;"
  class="ag-theme-quartz"
  [rowData]="rowData"
  [columnDefs]="columnDefs"
  [defaultColDef]="defaultColDef"
  [pagination]="true"
  [rowSelection]="'single'"
  (gridReady)="onGridReady($event)">
</ag-grid-angular>

button-rendererponent

import { Component } from '@angular/core';

@Component({
  standalone: true,
  templateUrl: './button-renderer.html',
})
export class ButtonRendererComponent {
  params: any;

  agInit(params: any): void {
    this.params = params;
  }

  onClick() {
    alert('Hello');
  }
}

button-renderer.html

<button (click)="onClick()">Click</button>

I'm encountering an issue in my AG Grid implementation in Angular. I have added a keyboard suppress event to allow users to tab into a cell and interact with a button inside that cell using the keyboard. However, when the user tabs into the button column, they seem to get "locked" into the button, preventing further navigation using the Tab key. Tabbing through the headers works fine, but this issue only arises with the button inside the grid cell.

appponent.ts

import { Component } from '@angular/core';
import { AgGridAngular } from 'ag-grid-angular';
import type { ColDef, GridApi, SuppressKeyboardEventParams } from 'ag-grid-community';
import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community';

// Register all Community features
ModuleRegistry.registerModules([AllCommunityModule]);
import { ButtonRendererComponent } from './button-renderer/button-rendererponent';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AgGridAngular, ButtonRendererComponent],
  templateUrl: './appponent.html',
  styleUrls: ['./appponent.scss']
})
export class AppComponent {
  private gridApi!: GridApi;
  searchText = '';

  rowData = [
    { name: "Dog", type: "Mammal", size: "Medium", speed: "Fast", lifespan: "10-15 years" },
    { name: "Cat", type: "Mammal", size: "Small", speed: "Fast", lifespan: "12-18 years" },
    { name: "Turtle", type: "Reptile", size: "Small", speed: "Slow", lifespan: "50-100 years" },
    { name: "Rabbit", type: "Mammal", size: "Small", speed: "Fast", lifespan: "8-12 years" },
    { name: "Parrot", type: "Bird", size: "Small", speed: "Medium", lifespan: "50-80 years" },
    { name: "Elephant", type: "Mammal", size: "Large", speed: "Slow", lifespan: "60-70 years" }
  ];

  columnDefs: ColDef[] = [
    { field: "name", sortable: true, filter: true },
    { field: "type", sortable: true, filter: true },
    { field: "size", sortable: true, filter: true },
    { field: "speed", sortable: true, filter: true },
    { field: "lifespan", sortable: true, filter: true },
    {
      headerName: 'Action',
      field: 'action',
      cellRenderer: ButtonRendererComponent
    }
  ];

  public defaultColDef: ColDef = {
    minWidth: 130,
    suppressKeyboardEvent : this.suppressKeyboardEvent
  };

  onGridReady(params: any) {
    this.gridApi = params.api;
  }


  // Function to trigger the alert
  sayHello() {
    alert('Hello!');
  }

  GRID_CELL_CLASSNAME = 'ag-cell';

  getAllFocusableElementsOf(el: HTMLElement) {
  return Array.from<HTMLElement>(
    el.querySelectorAll(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    ),
  ).filter((focusableEl) => {
    return focusableEl.tabIndex !== -1;
  });
}

 getEventPath(event: Event): HTMLElement[] {
  const path: HTMLElement[] = [];
  let currentTarget: any = event.target;
  while (currentTarget) {
    path.push(currentTarget);
    currentTarget = currentTarget.parentElement;
  }
  return path;
}

 suppressKeyboardEvent({ event }: SuppressKeyboardEventParams<any>) {
  const { key, shiftKey } = event;
  const path = this.getEventPath(event);
  const isTabForward = key === 'Tab' && shiftKey === false;
  const isTabBackward = key === 'Tab' && shiftKey === true;
  let suppressEvent = false;

  if (isTabForward || isTabBackward) {
    const eGridCell = path.find((el) => {
      return el.classList?.contains(this.GRID_CELL_CLASSNAME);
    });

    if (!eGridCell) {
      return suppressEvent;
    }

    const focusableChildrenElements = this.getAllFocusableElementsOf(eGridCell);

    const lastCellChildEl = focusableChildrenElements[focusableChildrenElements.length - 1];
    const firstCellChildEl = focusableChildrenElements[0];

    if (focusableChildrenElements.length === 0) {
      return false;
    }

    const currentIndex = focusableChildrenElements.indexOf(document.activeElement as HTMLElement);

    if (isTabForward) {
      const isLastChildFocused = lastCellChildEl && document.activeElement === lastCellChildEl;
      if (!isLastChildFocused) {
        suppressEvent = true;
        event.preventDefault();
        focusableChildrenElements[currentIndex + 1].focus();
      }
    } else {
      const isFirstChildFocused = firstCellChildEl && document.activeElement === firstCellChildEl;
      if (!isFirstChildFocused) {
        suppressEvent = true;
        event.preventDefault();
        focusableChildrenElements[currentIndex - 1].focus();
      }
    }
  }
  return suppressEvent;
}

}

appponent.html

<ag-grid-angular 
  style="width: 100%; height: 500px;"
  class="ag-theme-quartz"
  [rowData]="rowData"
  [columnDefs]="columnDefs"
  [defaultColDef]="defaultColDef"
  [pagination]="true"
  [rowSelection]="'single'"
  (gridReady)="onGridReady($event)">
</ag-grid-angular>

button-rendererponent

import { Component } from '@angular/core';

@Component({
  standalone: true,
  templateUrl: './button-renderer.html',
})
export class ButtonRendererComponent {
  params: any;

  agInit(params: any): void {
    this.params = params;
  }

  onClick() {
    alert('Hello');
  }
}

button-renderer.html

<button (click)="onClick()">Click</button>
Share Improve this question edited Feb 3 at 9:36 DarkBee 15.5k8 gold badges72 silver badges118 bronze badges asked Feb 3 at 4:29 MogurMogur 193 bronze badges
Add a comment  | 

1 Answer 1

Reset to default -1

It looks like the suppressKeyboardEvent prevents the normal tab behavior even if you're already on the first/last element in the cell.

You should be able to fix it by using this version of the suppressKeyboardEvent function, which stops supressing the event once the first/last element has been reached!

suppressKeyboardEvent({ event }: SuppressKeyboardEventParams<any>) {
  const { key, shiftKey } = event;
  const path = this.getEventPath(event);
  const isTabForward = key === 'Tab' && !shiftKey;
  const isTabBackward = key === 'Tab' && shiftKey;
  let suppressEvent = false;

  if (isTabForward || isTabBackward) {
    const eGridCell = path.find((el) => el.classList?.contains(this.GRID_CELL_CLASSNAME));

    if (!eGridCell) {
      return suppressEvent;
    }

    const focusableChildrenElements = this.getAllFocusableElementsOf(eGridCell);

    if (focusableChildrenElements.length === 0) {
      return false; // No focusable elements, allow default tab behavior
    }

    const lastCellChildEl = focusableChildrenElements[focusableChildrenElements.length - 1];
    const firstCellChildEl = focusableChildrenElements[0];
    const currentIndex = focusableChildrenElements.indexOf(document.activeElement as HTMLElement);

    if (isTabForward) {
      if (document.activeElement === lastCellChildEl) {
        return false; // Allow tabbing out of the cell
      }
      suppressEvent = true;
      event.preventDefault();
      focusableChildrenElements[currentIndex + 1]?.focus();
    } else {
      if (document.activeElement === firstCellChildEl) {
        return false; // Allow shift+tab to move out of the cell
      }
      suppressEvent = true;
      event.preventDefault();
      focusableChildrenElements[currentIndex - 1]?.focus();
    }
  }
  return suppressEvent;
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745249327a4618581.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信