Line data Source code
1 : import * as React from 'react';
2 :
3 : /// The focus of a single cell
4 1 : export enum CellFocus {
5 1 : None = 0,
6 : /// Another cell in the same row is hovered
7 1 : Secondary = 1,
8 : /// The cell itself is hovered
9 1 : Primary = 2,
10 1 : }
11 :
12 : /// The hovered cell of a data table, kept outside of React state.
13 : /// Cells subscribe by their data row so that moving the pointer only re-renders the previous and the next row.
14 1 : export class DataTableFocusStore {
15 1 : protected focusedRow: number | null = null;
16 1 : protected focusedField: number | null = null;
17 1 : protected listenersByRow = new Map<number, Set<() => void>>();
18 :
19 1 : public setFocus(row: number | null, field: number | null) {
20 1 : if (row === this.focusedRow && field === this.focusedField) {
21 1 : return;
22 : }
23 1 : const prevRow = this.focusedRow;
24 1 : this.focusedRow = row;
25 1 : this.focusedField = field;
26 1 : if (prevRow != null) {
27 1 : this.notify(prevRow);
28 : }
29 1 : if (row != null && row !== prevRow) {
30 1 : this.notify(row);
31 : }
32 1 : }
33 :
34 1 : public getFocus(row: number, field: number): CellFocus {
35 1 : if (row !== this.focusedRow) {
36 1 : return CellFocus.None;
37 : }
38 1 : return field === this.focusedField ? CellFocus.Primary : CellFocus.Secondary;
39 1 : }
40 :
41 1 : public subscribe(row: number, listener: () => void): () => void {
42 1 : let listeners = this.listenersByRow.get(row);
43 1 : if (listeners == null) {
44 1 : listeners = new Set();
45 1 : this.listenersByRow.set(row, listeners);
46 1 : }
47 1 : listeners.add(listener);
48 1 : return () => {
49 1 : const current = this.listenersByRow.get(row);
50 1 : if (current == null) return;
51 1 : current.delete(listener);
52 1 : if (current.size == 0) {
53 1 : this.listenersByRow.delete(row);
54 : }
55 1 : };
56 1 : }
57 :
58 1 : protected notify(row: number) {
59 1 : const listeners = this.listenersByRow.get(row);
60 1 : if (listeners == null) return;
61 1 : for (const listener of [...listeners]) {
62 1 : listener();
63 : }
64 1 : }
65 1 : }
66 :
67 : /// Subscribe to the focus of a single cell
68 1 : export function useCellFocus(store: DataTableFocusStore, row: number, field: number): CellFocus {
69 1 : const subscribe = React.useCallback((listener: () => void) => store.subscribe(row, listener), [store, row]);
70 1 : const getSnapshot = React.useCallback(() => store.getFocus(row, field), [store, row, field]);
71 1 : return React.useSyncExternalStore(subscribe, getSnapshot);
72 1 : }
|