summaryrefslogtreecommitdiff
path: root/chromium/ui/webui/resources/js/cr/ui/focus_grid.ts
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/webui/resources/js/cr/ui/focus_grid.ts')
-rw-r--r--chromium/ui/webui/resources/js/cr/ui/focus_grid.ts172
1 files changed, 172 insertions, 0 deletions
diff --git a/chromium/ui/webui/resources/js/cr/ui/focus_grid.ts b/chromium/ui/webui/resources/js/cr/ui/focus_grid.ts
new file mode 100644
index 00000000000..a765f727cc4
--- /dev/null
+++ b/chromium/ui/webui/resources/js/cr/ui/focus_grid.ts
@@ -0,0 +1,172 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// clang-format off
+import {assert} from '../../assert.m.js';
+
+import {FocusRow, FocusRowDelegate} from './focus_row.m.js';
+// clang-format on
+
+/**
+ * A class to manage grid of focusable elements in a 2D grid. For example,
+ * given this grid:
+ *
+ * focusable [focused] focusable (row: 0, col: 1)
+ * focusable focusable focusable
+ * focusable focusable focusable
+ *
+ * Pressing the down arrow would result in the focus moving down 1 row and
+ * keeping the same column:
+ *
+ * focusable focusable focusable
+ * focusable [focused] focusable (row: 1, col: 1)
+ * focusable focusable focusable
+ *
+ * And pressing right or tab at this point would move the focus to:
+ *
+ * focusable focusable focusable
+ * focusable focusable [focused] (row: 1, col: 2)
+ * focusable focusable focusable
+ */
+export class FocusGrid implements FocusRowDelegate {
+ rows: FocusRow[] = [];
+ private ignoreFocusChange_: boolean = false;
+ private lastFocused_: EventTarget|null = null;
+
+ onFocus(row: FocusRow, e: Event) {
+ if (this.ignoreFocusChange_) {
+ this.ignoreFocusChange_ = false;
+ } else {
+ this.lastFocused_ = e.currentTarget;
+ }
+
+ this.rows.forEach(function(r) {
+ r.makeActive(r === row);
+ });
+ }
+
+ onKeydown(row: FocusRow, e: KeyboardEvent) {
+ const rowIndex = this.rows.indexOf(row);
+ assert(rowIndex >= 0);
+
+ let newRow = -1;
+
+ if (e.key === 'ArrowUp') {
+ newRow = rowIndex - 1;
+ } else if (e.key === 'ArrowDown') {
+ newRow = rowIndex + 1;
+ } else if (e.key === 'PageUp') {
+ newRow = 0;
+ } else if (e.key === 'PageDown') {
+ newRow = this.rows.length - 1;
+ }
+
+ const rowToFocus = this.rows[newRow];
+ if (rowToFocus) {
+ this.ignoreFocusChange_ = true;
+ rowToFocus.getEquivalentElement(this.lastFocused_ as HTMLElement).focus();
+ e.preventDefault();
+ return true;
+ }
+
+ return false;
+ }
+
+ getCustomEquivalent(_sampleElement: HTMLElement) {
+ return null;
+ }
+
+ /**
+ * Unregisters event handlers and removes all |this.rows|.
+ */
+ destroy() {
+ this.rows.forEach(function(row) {
+ row.destroy();
+ });
+ this.rows.length = 0;
+ }
+
+ /**
+ * @param target A target item to find in this grid.
+ * @return The row index. -1 if not found.
+ */
+ getRowIndexForTarget(target: HTMLElement): number {
+ for (let i = 0; i < this.rows.length; ++i) {
+ if (this.rows[i]!.getElements().indexOf(target) >= 0) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @param root An element to search for.
+ * @return The row with root of |root| or null.
+ */
+ getRowForRoot(root: HTMLElement): FocusRow|null {
+ for (let i = 0; i < this.rows.length; ++i) {
+ if (this.rows[i]!.root === root) {
+ return this.rows[i]!;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds |row| to the end of this list.
+ * @param row The row that needs to be added to this grid.
+ */
+ addRow(row: FocusRow) {
+ this.addRowBefore(row, null);
+ }
+
+ /**
+ * Adds |row| before |nextRow|. If |nextRow| is not in the list or it's
+ * null, |row| is added to the end.
+ * @param row The row that needs to be added to this grid.
+ * @param nextRow The row that should follow |row|.
+ */
+ addRowBefore(row: FocusRow, nextRow: FocusRow|null) {
+ row.delegate = row.delegate || this;
+
+ const nextRowIndex = nextRow ? this.rows.indexOf(nextRow) : -1;
+ if (nextRowIndex === -1) {
+ this.rows.push(row);
+ } else {
+ this.rows.splice(nextRowIndex, 0, row);
+ }
+ }
+
+ /**
+ * Removes a row from the focus row. No-op if row is not in the grid.
+ * @param row The row that needs to be removed.
+ */
+ removeRow(row: FocusRow|null) {
+ const nextRowIndex = row ? this.rows.indexOf(row) : -1;
+ if (nextRowIndex > -1) {
+ this.rows.splice(nextRowIndex, 1);
+ }
+ }
+
+ /**
+ * Makes sure that at least one row is active. Should be called once, after
+ * adding all rows to FocusGrid.
+ * @param preferredRow The row to select if no other row is
+ * active. Selects the first item if this is beyond the range of the
+ * grid.
+ */
+ ensureRowActive(preferredRow?: number) {
+ if (this.rows.length === 0) {
+ return;
+ }
+
+ for (let i = 0; i < this.rows.length; ++i) {
+ if (this.rows[i]!.isActive()) {
+ return;
+ }
+ }
+
+ (this.rows[preferredRow || 0] || this.rows[0]!).makeActive(true);
+ }
+}