carbon-components-angular
Version:
Next generation components
629 lines (525 loc) • 21.8 kB
HTML
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>carbon-components-angular documentation</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="../images/favicon.ico">
<link rel="stylesheet" href="../styles/style.css">
<link rel="stylesheet" href="../styles/dark.css">
<style>
footer.carbon {
position: absolute;
bottom: 0;
width: 100%;
z-index: 9999;
}
#root > div {
/*
* Subtracting the height of the footer to prevent
* overlaying the footer ontop of content
*/
height: calc(100vh - 48px);
}
</style>
</head>
<body>
<script>
// Blocking script to avoid flickering dark mode
// Dark mode toggle button
var useDark = window.matchMedia('(prefers-color-scheme: dark)');
var darkModeState = useDark.matches;
var $darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input');
var $darkModeToggles = document.querySelectorAll('.dark-mode-switch');
var darkModeStateLocal = localStorage.getItem('compodoc_darkmode-state');
function checkToggle(check) {
for (var i = 0; i < $darkModeToggleSwitchers.length; i++) {
$darkModeToggleSwitchers[i].checked = check;
}
}
function toggleDarkMode(state) {
if (window.localStorage) {
localStorage.setItem('compodoc_darkmode-state', state);
}
checkToggle(state);
const hasClass = document.body.classList.contains('dark');
if (state) {
for (var i = 0; i < $darkModeToggles.length; i++) {
$darkModeToggles[i].classList.add('dark');
}
if (!hasClass) {
document.body.classList.add('dark');
}
} else {
for (var i = 0; i < $darkModeToggles.length; i++) {
$darkModeToggles[i].classList.remove('dark');
}
if (hasClass) {
document.body.classList.remove('dark');
}
}
}
useDark.addEventListener('change', function (evt) {
toggleDarkMode(evt.matches);
});
if (darkModeStateLocal) {
darkModeState = darkModeStateLocal === 'true';
}
toggleDarkMode(darkModeState);
</script>
<div class="navbar navbar-default navbar-fixed-top d-md-none p-0">
<div class="d-flex">
<a href="../" class="navbar-brand">carbon-components-angular documentation</a>
<button type="button" class="btn btn-default btn-menu ion-ios-menu" id="btn-menu"></button>
</div>
</div>
<div class="xs-menu menu" id="mobile-menu">
<div id="book-search-input" role="search"><input type="text" placeholder="Type to search"></div> <compodoc-menu></compodoc-menu>
</div>
<div class="container-fluid main">
<div class="row main">
<div class="d-none d-md-block menu">
<compodoc-menu mode="normal"></compodoc-menu>
</div>
<!-- START CONTENT -->
<div class="content interface">
<div class="content-data">
<ol class="breadcrumb">
<li class="breadcrumb-item">Interfaces</li>
<li class="breadcrumb-item"
>
DataGridPosition</li>
</ol>
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a href="#info"
class="nav-link"
class="nav-link active"
role="tab" id="info-tab" data-bs-toggle="tab" data-link="info">Info</a>
</li>
<li class="nav-item">
<a href="#source"
class="nav-link"
role="tab" id="source-tab" data-bs-toggle="tab" data-link="source">Source</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade active in" id="info">
<p class="comment">
<h3>File</h3>
</p>
<p class="comment">
<code>src/table/data-grid-interaction-model.class.ts</code>
</p>
<p class="comment">
<h3>Description</h3>
</p>
<p class="comment">
<p>The current and previous position in the grid.</p>
<p><code>current</code> and <code>previous</code> are tuples that follow the <code>[row, column]</code> convention.</p>
</p>
<section data-compodoc="block-index">
<h3 id="index">Index</h3>
<table class="table table-sm table-bordered index-table">
<tbody>
<tr>
<td class="col-md-4">
<h6><b>Properties</b></h6>
</td>
</tr>
<tr>
<td class="col-md-4">
<ul class="index-list">
<li>
<a href="#current"
>
current
</a>
</li>
<li>
<a href="#previous"
>
previous
</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</section>
<section data-compodoc="block-properties">
<h3 id="inputs">Properties</h3>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<a name="current"></a>
<span class="name "><b>current</b>
<a href="#current">
<span class="icon ion-ios-link"></span>
</a>
</span>
</td>
</tr>
<tr>
<td class="col-md-4">
<code>current: <code>[number, number]</code>
</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<i>Type : </i> <code>[number, number]</code>
</td>
</tr>
</tbody>
</table>
<table class="table table-sm table-bordered">
<tbody>
<tr>
<td class="col-md-4">
<a name="previous"></a>
<span class="name "><b>previous</b>
<a href="#previous">
<span class="icon ion-ios-link"></span>
</a>
</span>
</td>
</tr>
<tr>
<td class="col-md-4">
<code>previous: <code>[number, number]</code>
</code>
</td>
</tr>
<tr>
<td class="col-md-4">
<i>Type : </i> <code>[number, number]</code>
</td>
</tr>
</tbody>
</table>
</section>
</div>
<div class="tab-pane fade tab-source-code" id="source">
<pre class="line-numbers compodoc-sourcecode"><code class="language-typescript">import {
BehaviorSubject,
Observable,
combineLatest
} from "rxjs";
import { map } from "rxjs/operators";
import { TableAdapter } from "./table-adapter.class";
import { tabbableSelectorIgnoreTabIndex, getFocusElementList } from "carbon-components-angular/common";
/**
* The current and previous position in the grid.
*
* `current` and `previous` are tuples that follow the `[row, column]` convention.
*/
export interface DataGridPosition {
current: [number, number];
previous: [number, number];
}
/**
* `DataGridInteractionModel` provides centralized control over arbitrary 2d grids, following the w3 specs.
*
* Refs:
* - https://www.w3.org/TR/wai-aria-practices/examples/grid/dataGrids.html
* - https://www.w3.org/TR/wai-aria-practices/#grid
*
* Example usage (taken from `table.component`):
```typescript
// a standard HTML table
const table = this.elementRef.nativeElement.querySelector("table") as HTMLTableElement;
// `TableDomAdapter` implements `TableAdapter` and provides a consistent interface to query rows and columns in a table
const tableAdapter = new TableDomAdapter(table);
// the keydown events that we'll use for keyboard navigation of the table
const keydownEventStream = fromEvent<KeyboardEvent>(table, "keydown");
// the click events we'll use to ensure focus is updated correctly on click
const clickEventStream = fromEvent<MouseEvent>(table, "click");
// the `DataGridInteractionModel` instance!
this.interactionModel = new DataGridInteractionModel(keydownEventStream, clickEventStream, tableAdapter);
// subscribe to the combined position updates
this.interactionModel.position.subscribe(event => {
const [currentRow, currentColumn] = event.current;
const [previousRow, previousColumn] = event.previous;
// query the TableAdapter for the cell at the current row and column ...
const currentElement = tableAdapter.getCell(currentRow, currentColumn);
// ... and make it focusable it
Table.setTabIndex(currentElement, 0);
// if the model has just initialized don't focus or reset anything
if (previousRow === -1 || previousColumn === -1) { return; }
// query the TableAdapter for the cell at the previous row and column ...
const previousElement = tableAdapter.getCell(previousRow, previousColumn);
// ... and make it unfocusable (now there is only a single focusable cell)
Table.setTabIndex(previousElement, -1);
// finally, focus the current cell (skipped during initilzation)
Table.focus(currentElement);
});
```
*/
export class DataGridInteractionModel {
/**
* An Observable that provides an aggregated view of the `rowIndex` and `columnIndex` Observables
*/
readonly position: Observable<DataGridPosition>;
/**
* An Observable that provides the current and previous row indexes.
*/
readonly rowIndex: Observable<{ current: number, previous: number }>;
/**
* An Observable that provides the current and previous column indexes.
*/
readonly columnIndex: Observable<{ current: number, previous: number }>;
/**
* Internal subject to handle changes in row
*/
protected rowSubject = new BehaviorSubject({ current: 0, previous: -1 });
/**
* Internal subject to handle changes in column
*/
protected columnSubject = new BehaviorSubject({ current: 0, previous: -1 });
/**
* The latest value emitted by the rowSubject
*/
protected get currentRow() {
return this.rowSubject.getValue().current;
}
/**
* The latest value emitted by the columnSubject
*/
protected get currentColumn() {
return this.columnSubject.getValue().current;
}
/**
* The last column as reported by the adapter
*/
protected get lastColumn() {
return this.tableAdapter.lastColumnIndex;
}
/**
* The last row as reported by the adapter
*/
protected get lastRow() {
return this.tableAdapter.lastRowIndex;
}
/**
* `DataGridInteractionModel` requires knowledge of events, and a representation of your table/grid to be useful.
*
* @param keyboardEventStream an Observable of KeyboardEvents. Should be scoped to the table container.
* @param clickEventStream an Observable of ClickEvents. should only include clicks that take action on items known by the TableAdapter
* @param tableAdapter an instance of a concrete class that implements TableAdapter. The standard carbon table uses TableDomAdapter
*/
constructor(
protected keyboardEventStream: Observable<KeyboardEvent>,
protected clickEventStream: Observable<MouseEvent>,
protected tableAdapter: TableAdapter
) {
this.rowIndex = this.rowSubject.asObservable();
this.columnIndex = this.columnSubject.asObservable();
this.position = combineLatest(this.rowIndex, this.columnIndex).pipe(map(positions => {
const [row, column] = positions;
return {
current: [row.current, column.current],
previous: [row.previous, column.previous]
};
})) as Observable<DataGridPosition>;
this.keyboardEventStream.subscribe(this.handleKeyboardEvent.bind(this));
this.clickEventStream.subscribe(this.handleClickEvent.bind(this));
}
/**
* Handles moving the position according to the w3 datagrid navigation specs
*
* Refs:
* - https://www.w3.org/TR/wai-aria-practices/examples/grid/dataGrids.html
* - https://www.w3.org/TR/wai-aria-practices/#grid
*
* @param event the KeyboardEvent to handle
*/
handleKeyboardEvent(event: KeyboardEvent) {
const currentCell = this.tableAdapter.getCell(this.currentRow, this.currentColumn);
let currentColumn = this.tableAdapter.findColumnIndex(currentCell);
let currentRow = this.tableAdapter.findRowIndex(currentCell);
switch (event.key) {
case "ArrowRight":
event.preventDefault();
// add the colspan since findColumnIndex will return the
// first column containing the cell (of N columns it may span)
// and we want to navigate to the next "real" column
this.goToColumn(currentColumn + currentCell.colSpan);
break;
case "ArrowLeft":
event.preventDefault();
// we only ever need to subtract 1 from the column, since findColumnIndex returns the
// first of N columns containing the cell
this.goToColumn(currentColumn - 1);
break;
case "ArrowDown":
event.preventDefault();
this.goToRow(currentRow + currentCell.rowSpan);
break;
case "ArrowUp":
event.preventDefault();
this.goToRow(currentRow - 1);
break;
case "Home":
event.preventDefault();
if (event.ctrlKey) {
this.goTo({row: 0, column: 0});
} else {
this.goToColumn(0);
}
break;
case "End":
event.preventDefault();
if (event.ctrlKey) {
this.goTo({ row: this.lastRow, column: this.lastColumn });
} else {
this.goToColumn(this.lastColumn);
}
break;
}
}
/**
* Handles moving the position to the clicked cell
*
* @param event the MouseEvent to handle
*/
handleClickEvent(event: MouseEvent) {
const cell = (event.target as HTMLElement).closest("td, th") as HTMLTableCellElement;
const [rowIndex, cellIndex] = this.tableAdapter.findIndex(cell);
this.goTo({ row: rowIndex, column: cellIndex });
}
/**
* Jump to a specific column without changing the row
*
* @param index column to jump to
*/
goToColumn(index: number) {
if (index > this.lastColumn || index < 0) { return; }
this.goTo({ row: this.currentRow, column: index});
}
/**
* Jump to a specific row without changing the column
*
* @param index row to jump to
*/
goToRow(index: number) {
if (index > this.lastRow || index < 0) { return; }
this.goTo({row: index, column: this.currentColumn});
}
/**
* Jump to the specified row and column
*
* @param param0 an object that contains `row` and `column` properties
*/
goTo({row, column}) {
this.rowSubject.next({ current: row, previous: this.currentRow });
this.columnSubject.next({ current: column, previous: this.currentColumn });
}
/**
* Convenience method to reset the tab indexes on a standard carbon table.
* For custom tables you may want to reset the indexes manually and simply call `.reset()`
*/
resetTabIndexes(newTabIndex = -1) {
for (let i = 0; i < this.tableAdapter.lastRowIndex; i++) {
const row = this.tableAdapter.getRow(i) as HTMLTableRowElement;
for (const cell of Array.from(row.cells)) {
const tabbableElements = getFocusElementList(cell, tabbableSelectorIgnoreTabIndex);
tabbableElements.forEach((node: HTMLElement) => node.tabIndex = newTabIndex);
cell.tabIndex = newTabIndex;
}
}
this.reset();
}
/**
* Resets the models focus position
*/
reset() {
this.rowSubject.next({ current: 0, previous: -1 });
this.columnSubject.next({ current: 0, previous: -1 });
}
}
</code></pre>
</div>
</div>
</div><div class="search-results">
<div class="has-results">
<h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
<ul class="search-results-list"></ul>
</div>
<div class="no-results">
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
</div>
</div>
</div>
<!-- END CONTENT -->
</div>
</div>
<label class="dark-mode-switch">
<input type="checkbox">
<span class="slider">
<svg class="slider-icon" viewBox="0 0 24 24" fill="none" height="20" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" width="20" xmlns="http://www.w3.org/2000/svg">
<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"></path>
</svg>
</span>
</label>
<script>
var COMPODOC_CURRENT_PAGE_DEPTH = 1;
var COMPODOC_CURRENT_PAGE_CONTEXT = 'interface';
var COMPODOC_CURRENT_PAGE_URL = 'DataGridPosition.html';
var MAX_SEARCH_RESULTS = 15;
</script>
<script>
$darkModeToggleSwitchers = document.querySelectorAll('.dark-mode-switch input');
checkToggle(darkModeState);
if ($darkModeToggleSwitchers.length > 0) {
for (var i = 0; i < $darkModeToggleSwitchers.length; i++) {
$darkModeToggleSwitchers[i].addEventListener('change', function (event) {
darkModeState = !darkModeState;
toggleDarkMode(darkModeState);
});
}
}
</script>
<script src="../js/libs/custom-elements.min.js"></script>
<script src="../js/libs/lit-html.js"></script>
<script src="../js/menu-wc.js" defer></script>
<script nomodule src="../js/menu-wc_es5.js" defer></script>
<script src="../js/libs/bootstrap-native.js"></script>
<script src="../js/libs/es6-shim.min.js"></script>
<script src="../js/libs/EventDispatcher.js"></script>
<script src="../js/libs/promise.min.js"></script>
<script src="../js/libs/zepto.min.js"></script>
<script src="../js/compodoc.js"></script>
<script src="../js/tabs.js"></script>
<script src="../js/menu.js"></script>
<script src="../js/libs/clipboard.min.js"></script>
<script src="../js/libs/prism.js"></script>
<script src="../js/sourceCode.js"></script>
<script src="../js/search/search.js"></script>
<script src="../js/search/lunr.min.js"></script>
<script src="../js/search/search-lunr.js"></script>
<script src="../js/search/search_index.js"></script>
<script src="../js/lazy-load-graphs.js"></script>
<footer class="carbon">
<dds-footer-container key="footer" disable-locale-button="true" size="micro" />
</footer>
<script
key="8"
type="module"
src="https://1.www.s81c.com/common/carbon-for-ibm-dotcom/tag/v1/latest/footer.min.js">
</script>
<!-- Storybook override -->
<script>
document.title = "Carbon Components Angular";
</script>
<script
src="//1.www.s81c.com/common/stats/ibm-common.js"
type="text/javascript"
async="async">
</script>
</body>
</html>