Add useScrollIntoView hook
This commit is contained in:
@@ -14,13 +14,13 @@ import {
|
|||||||
SarifInterpretationData,
|
SarifInterpretationData,
|
||||||
} from "../../common/interface-types";
|
} from "../../common/interface-types";
|
||||||
import { parseSarifLocation, isNoLocation } from "../../common/sarif-utils";
|
import { parseSarifLocation, isNoLocation } from "../../common/sarif-utils";
|
||||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
|
||||||
import { sendTelemetry } from "../common/telemetry";
|
import { sendTelemetry } from "../common/telemetry";
|
||||||
import { AlertTableHeader } from "./AlertTableHeader";
|
import { AlertTableHeader } from "./AlertTableHeader";
|
||||||
import { AlertTableNoResults } from "./AlertTableNoResults";
|
import { AlertTableNoResults } from "./AlertTableNoResults";
|
||||||
import { AlertTableTruncatedMessage } from "./AlertTableTruncatedMessage";
|
import { AlertTableTruncatedMessage } from "./AlertTableTruncatedMessage";
|
||||||
import { AlertTableResultRow } from "./AlertTableResultRow";
|
import { AlertTableResultRow } from "./AlertTableResultRow";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
|
import { useScrollIntoView } from "./useScrollIntoView";
|
||||||
|
|
||||||
type AlertTableProps = ResultTableProps & {
|
type AlertTableProps = ResultTableProps & {
|
||||||
resultSet: InterpretedResultSet<SarifInterpretationData>;
|
resultSet: InterpretedResultSet<SarifInterpretationData>;
|
||||||
@@ -29,17 +29,14 @@ type AlertTableProps = ResultTableProps & {
|
|||||||
export function AlertTable(props: AlertTableProps) {
|
export function AlertTable(props: AlertTableProps) {
|
||||||
const { databaseUri, resultSet } = props;
|
const { databaseUri, resultSet } = props;
|
||||||
|
|
||||||
const scroller = useRef<ScrollIntoViewHelper | undefined>(undefined);
|
|
||||||
if (scroller.current === undefined) {
|
|
||||||
scroller.current = new ScrollIntoViewHelper();
|
|
||||||
}
|
|
||||||
useEffect(() => scroller.current?.update());
|
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState<Set<string>>(new Set<string>());
|
const [expanded, setExpanded] = useState<Set<string>>(new Set<string>());
|
||||||
const [selectedItem, setSelectedItem] = useState<Keys.ResultKey | undefined>(
|
const [selectedItem, setSelectedItem] = useState<Keys.ResultKey | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedItemRef = useRef<any>();
|
||||||
|
useScrollIntoView(selectedItem, selectedItemRef);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a list of `keys`, toggle the first, and if we 'open' the
|
* Given a list of `keys`, toggle the first, and if we 'open' the
|
||||||
* first item, open all the rest as well. This mimics vscode's file
|
* first item, open all the rest as well. This mimics vscode's file
|
||||||
@@ -162,7 +159,6 @@ export function AlertTable(props: AlertTableProps) {
|
|||||||
newExpanded.delete(Keys.keyToString(selectedItem));
|
newExpanded.delete(Keys.keyToString(selectedItem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scroller.current?.scrollIntoViewOnNextUpdate();
|
|
||||||
setExpanded(newExpanded);
|
setExpanded(newExpanded);
|
||||||
setSelectedItem(key);
|
setSelectedItem(key);
|
||||||
},
|
},
|
||||||
@@ -203,11 +199,11 @@ export function AlertTable(props: AlertTableProps) {
|
|||||||
resultIndex={resultIndex}
|
resultIndex={resultIndex}
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
selectedItem={selectedItem}
|
selectedItem={selectedItem}
|
||||||
|
selectedItemRef={selectedItemRef}
|
||||||
databaseUri={databaseUri}
|
databaseUri={databaseUri}
|
||||||
sourceLocationPrefix={sourceLocationPrefix}
|
sourceLocationPrefix={sourceLocationPrefix}
|
||||||
updateSelectionCallback={updateSelectionCallback}
|
updateSelectionCallback={updateSelectionCallback}
|
||||||
toggleExpanded={toggle}
|
toggleExpanded={toggle}
|
||||||
scroller={scroller.current}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import * as Sarif from "sarif";
|
|||||||
import * as Keys from "./result-keys";
|
import * as Keys from "./result-keys";
|
||||||
import { SarifLocation } from "./locations/SarifLocation";
|
import { SarifLocation } from "./locations/SarifLocation";
|
||||||
import { selectableZebraStripe } from "./result-table-utils";
|
import { selectableZebraStripe } from "./result-table-utils";
|
||||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -12,12 +11,12 @@ interface Props {
|
|||||||
pathIndex: number;
|
pathIndex: number;
|
||||||
resultIndex: number;
|
resultIndex: number;
|
||||||
selectedItem: undefined | Keys.ResultKey;
|
selectedItem: undefined | Keys.ResultKey;
|
||||||
|
selectedItemRef: React.RefObject<any>;
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
updateSelectionCallback: (
|
updateSelectionCallback: (
|
||||||
resultKey: Keys.PathNode | Keys.Result | undefined,
|
resultKey: Keys.PathNode | Keys.Result | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
scroller?: ScrollIntoViewHelper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlertTablePathNodeRow(props: Props) {
|
export function AlertTablePathNodeRow(props: Props) {
|
||||||
@@ -27,10 +26,10 @@ export function AlertTablePathNodeRow(props: Props) {
|
|||||||
pathIndex,
|
pathIndex,
|
||||||
resultIndex,
|
resultIndex,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
|
selectedItemRef,
|
||||||
databaseUri,
|
databaseUri,
|
||||||
sourceLocationPrefix,
|
sourceLocationPrefix,
|
||||||
updateSelectionCallback,
|
updateSelectionCallback,
|
||||||
scroller,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const pathNodeKey: Keys.PathNode = useMemo(
|
const pathNodeKey: Keys.PathNode = useMemo(
|
||||||
@@ -51,7 +50,7 @@ export function AlertTablePathNodeRow(props: Props) {
|
|||||||
const zebraIndex = resultIndex + stepIndex;
|
const zebraIndex = resultIndex + stepIndex;
|
||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
ref={scroller?.ref(isSelected)}
|
ref={isSelected ? selectedItemRef : undefined}
|
||||||
className={isSelected ? "vscode-codeql__selected-path-node" : undefined}
|
className={isSelected ? "vscode-codeql__selected-path-node" : undefined}
|
||||||
>
|
>
|
||||||
<td className="vscode-codeql__icon-cell">
|
<td className="vscode-codeql__icon-cell">
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as React from "react";
|
|||||||
import * as Sarif from "sarif";
|
import * as Sarif from "sarif";
|
||||||
import * as Keys from "./result-keys";
|
import * as Keys from "./result-keys";
|
||||||
import { selectableZebraStripe } from "./result-table-utils";
|
import { selectableZebraStripe } from "./result-table-utils";
|
||||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
|
||||||
import { AlertTablePathNodeRow } from "./AlertTablePathNodeRow";
|
import { AlertTablePathNodeRow } from "./AlertTablePathNodeRow";
|
||||||
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
@@ -13,13 +12,13 @@ interface Props {
|
|||||||
resultIndex: number;
|
resultIndex: number;
|
||||||
currentPathExpanded: boolean;
|
currentPathExpanded: boolean;
|
||||||
selectedItem: undefined | Keys.ResultKey;
|
selectedItem: undefined | Keys.ResultKey;
|
||||||
|
selectedItemRef: React.RefObject<any>;
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
updateSelectionCallback: (
|
updateSelectionCallback: (
|
||||||
resultKey: Keys.PathNode | Keys.Result | undefined,
|
resultKey: Keys.PathNode | Keys.Result | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
toggleExpanded: (e: React.MouseEvent, keys: Keys.ResultKey[]) => void;
|
toggleExpanded: (e: React.MouseEvent, keys: Keys.ResultKey[]) => void;
|
||||||
scroller?: ScrollIntoViewHelper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlertTablePathRow(props: Props) {
|
export function AlertTablePathRow(props: Props) {
|
||||||
@@ -29,8 +28,8 @@ export function AlertTablePathRow(props: Props) {
|
|||||||
resultIndex,
|
resultIndex,
|
||||||
currentPathExpanded,
|
currentPathExpanded,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
|
selectedItemRef,
|
||||||
toggleExpanded,
|
toggleExpanded,
|
||||||
scroller,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const pathKey = useMemo(
|
const pathKey = useMemo(
|
||||||
@@ -50,7 +49,7 @@ export function AlertTablePathRow(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr
|
<tr
|
||||||
ref={scroller?.ref(isPathSpecificallySelected)}
|
ref={isPathSpecificallySelected ? selectedItemRef : undefined}
|
||||||
{...selectableZebraStripe(isPathSpecificallySelected, resultIndex)}
|
{...selectableZebraStripe(isPathSpecificallySelected, resultIndex)}
|
||||||
>
|
>
|
||||||
<td className="vscode-codeql__icon-cell">
|
<td className="vscode-codeql__icon-cell">
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as React from "react";
|
|||||||
import * as Sarif from "sarif";
|
import * as Sarif from "sarif";
|
||||||
import * as Keys from "./result-keys";
|
import * as Keys from "./result-keys";
|
||||||
import { info, listUnordered } from "./octicons";
|
import { info, listUnordered } from "./octicons";
|
||||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
|
||||||
import { selectableZebraStripe } from "./result-table-utils";
|
import { selectableZebraStripe } from "./result-table-utils";
|
||||||
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
import { AlertTableDropdownIndicatorCell } from "./AlertTableDropdownIndicatorCell";
|
||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
@@ -15,13 +14,13 @@ interface Props {
|
|||||||
resultIndex: number;
|
resultIndex: number;
|
||||||
expanded: Set<string>;
|
expanded: Set<string>;
|
||||||
selectedItem: undefined | Keys.ResultKey;
|
selectedItem: undefined | Keys.ResultKey;
|
||||||
|
selectedItemRef: React.RefObject<any>;
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
sourceLocationPrefix: string;
|
sourceLocationPrefix: string;
|
||||||
updateSelectionCallback: (
|
updateSelectionCallback: (
|
||||||
resultKey: Keys.PathNode | Keys.Result | undefined,
|
resultKey: Keys.PathNode | Keys.Result | undefined,
|
||||||
) => void;
|
) => void;
|
||||||
toggleExpanded: (e: React.MouseEvent, keys: Keys.ResultKey[]) => void;
|
toggleExpanded: (e: React.MouseEvent, keys: Keys.ResultKey[]) => void;
|
||||||
scroller?: ScrollIntoViewHelper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AlertTableResultRow(props: Props) {
|
export function AlertTableResultRow(props: Props) {
|
||||||
@@ -30,11 +29,11 @@ export function AlertTableResultRow(props: Props) {
|
|||||||
resultIndex,
|
resultIndex,
|
||||||
expanded,
|
expanded,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
|
selectedItemRef,
|
||||||
databaseUri,
|
databaseUri,
|
||||||
sourceLocationPrefix,
|
sourceLocationPrefix,
|
||||||
updateSelectionCallback,
|
updateSelectionCallback,
|
||||||
toggleExpanded,
|
toggleExpanded,
|
||||||
scroller,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const resultKey: Keys.Result = useMemo(
|
const resultKey: Keys.Result = useMemo(
|
||||||
@@ -81,7 +80,7 @@ export function AlertTableResultRow(props: Props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr
|
<tr
|
||||||
ref={scroller?.ref(resultRowIsSelected)}
|
ref={resultRowIsSelected ? selectedItemRef : undefined}
|
||||||
{...selectableZebraStripe(resultRowIsSelected, resultIndex)}
|
{...selectableZebraStripe(resultRowIsSelected, resultIndex)}
|
||||||
>
|
>
|
||||||
{result.codeFlows === undefined ? (
|
{result.codeFlows === undefined ? (
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import RawTableRow from "./RawTableRow";
|
|||||||
import { ResultRow } from "../../common/bqrs-cli-types";
|
import { ResultRow } from "../../common/bqrs-cli-types";
|
||||||
import { onNavigation } from "./ResultsApp";
|
import { onNavigation } from "./ResultsApp";
|
||||||
import { tryGetResolvableLocation } from "../../common/bqrs-utils";
|
import { tryGetResolvableLocation } from "../../common/bqrs-utils";
|
||||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
|
||||||
import { sendTelemetry } from "../common/telemetry";
|
import { sendTelemetry } from "../common/telemetry";
|
||||||
import { assertNever } from "../../common/helpers-pure";
|
import { assertNever } from "../../common/helpers-pure";
|
||||||
import { EmptyQueryResultsMessage } from "./EmptyQueryResultsMessage";
|
import { EmptyQueryResultsMessage } from "./EmptyQueryResultsMessage";
|
||||||
|
import { useScrollIntoView } from "./useScrollIntoView";
|
||||||
|
|
||||||
type RawTableProps = {
|
type RawTableProps = {
|
||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
@@ -38,11 +38,8 @@ export function RawTable({
|
|||||||
}: RawTableProps) {
|
}: RawTableProps) {
|
||||||
const [selectedItem, setSelectedItem] = useState<TableItem | undefined>();
|
const [selectedItem, setSelectedItem] = useState<TableItem | undefined>();
|
||||||
|
|
||||||
const scroller = useRef<ScrollIntoViewHelper | undefined>(undefined);
|
const selectedItemRef = useRef<any>();
|
||||||
if (scroller.current === undefined) {
|
useScrollIntoView(selectedItem, selectedItemRef);
|
||||||
scroller.current = new ScrollIntoViewHelper();
|
|
||||||
}
|
|
||||||
useEffect(() => scroller.current?.update());
|
|
||||||
|
|
||||||
const setSelection = useCallback((row: number, column: number): void => {
|
const setSelection = useCallback((row: number, column: number): void => {
|
||||||
setSelectedItem({ row, column });
|
setSelectedItem({ row, column });
|
||||||
@@ -76,11 +73,10 @@ export function RawTable({
|
|||||||
jumpToLocation(location, databaseUri);
|
jumpToLocation(location, databaseUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scroller.current?.scrollIntoViewOnNextUpdate();
|
|
||||||
return { row: nextRow, column: nextColumn };
|
return { row: nextRow, column: nextColumn };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[databaseUri, resultSet, scroller],
|
[databaseUri, resultSet],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNavigationEvent = useCallback(
|
const handleNavigationEvent = useCallback(
|
||||||
@@ -140,7 +136,7 @@ export function RawTable({
|
|||||||
selectedItem?.row === rowIndex ? selectedItem?.column : undefined
|
selectedItem?.row === rowIndex ? selectedItem?.column : undefined
|
||||||
}
|
}
|
||||||
onSelected={setSelection}
|
onSelected={setSelection}
|
||||||
scroller={scroller.current}
|
selectedItemRef={selectedItemRef}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as React from "react";
|
|||||||
import { ResultRow } from "../../common/bqrs-cli-types";
|
import { ResultRow } from "../../common/bqrs-cli-types";
|
||||||
import { selectedRowClassName, zebraStripe } from "./result-table-utils";
|
import { selectedRowClassName, zebraStripe } from "./result-table-utils";
|
||||||
import RawTableValue from "./RawTableValue";
|
import RawTableValue from "./RawTableValue";
|
||||||
import { ScrollIntoViewHelper } from "./scroll-into-view-helper";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
@@ -10,8 +9,8 @@ interface Props {
|
|||||||
databaseUri: string;
|
databaseUri: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
selectedColumn?: number;
|
selectedColumn?: number;
|
||||||
|
selectedItemRef?: React.Ref<any>;
|
||||||
onSelected?: (row: number, column: number) => void;
|
onSelected?: (row: number, column: number) => void;
|
||||||
scroller?: ScrollIntoViewHelper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RawTableRow(props: Props) {
|
export default function RawTableRow(props: Props) {
|
||||||
@@ -26,7 +25,7 @@ export default function RawTableRow(props: Props) {
|
|||||||
const isSelected = props.selectedColumn === columnIndex;
|
const isSelected = props.selectedColumn === columnIndex;
|
||||||
return (
|
return (
|
||||||
<td
|
<td
|
||||||
ref={props.scroller?.ref(isSelected)}
|
ref={isSelected ? props.selectedItemRef : undefined}
|
||||||
key={columnIndex}
|
key={columnIndex}
|
||||||
{...(isSelected ? { className: selectedRowClassName } : {})}
|
{...(isSelected ? { className: selectedRowClassName } : {})}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import { createRef } from "react";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some book-keeping needed to scroll a specific HTML element into view in a React component.
|
|
||||||
*/
|
|
||||||
export class ScrollIntoViewHelper {
|
|
||||||
private selectedElementRef = createRef<HTMLElement | any>(); // need 'any' to work around typing bug in React
|
|
||||||
private shouldScrollIntoView = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If `isSelected` is true, gets the `ref={}` attribute to use for an element that we might want to scroll into view.
|
|
||||||
*/
|
|
||||||
public ref(isSelected: boolean) {
|
|
||||||
return isSelected ? this.selectedElementRef : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Causes the element whose `ref={}` was set to be scrolled into view after the next render.
|
|
||||||
*/
|
|
||||||
public scrollIntoViewOnNextUpdate() {
|
|
||||||
this.shouldScrollIntoView = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Should be called from `componentDidUpdate` and `componentDidMount`.
|
|
||||||
*
|
|
||||||
* Scrolls the component into view if requested.
|
|
||||||
*/
|
|
||||||
public update() {
|
|
||||||
if (!this.shouldScrollIntoView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.shouldScrollIntoView = false;
|
|
||||||
const element = this.selectedElementRef.current as HTMLElement | null;
|
|
||||||
if (element == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
// The selected item's bounding box might be on screen, but hidden underneath the sticky header
|
|
||||||
// which overlaps the table view. As a workaround we hardcode a fixed distance from the top which
|
|
||||||
// we consider to be obscured. It does not have to exact, as it's just a threshold for when to scroll.
|
|
||||||
const heightOfStickyHeader = 30;
|
|
||||||
if (rect.top < heightOfStickyHeader || rect.bottom > window.innerHeight) {
|
|
||||||
element.scrollIntoView({
|
|
||||||
block: "center", // vertically align to center
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (rect.left < 0 || rect.right > window.innerWidth) {
|
|
||||||
element.scrollIntoView({
|
|
||||||
block: "nearest",
|
|
||||||
inline: "nearest", // horizontally align as little as possible
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
extensions/ql-vscode/src/view/results/useScrollIntoView.tsx
Normal file
29
extensions/ql-vscode/src/view/results/useScrollIntoView.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { RefObject, useEffect } from "react";
|
||||||
|
|
||||||
|
export function useScrollIntoView<T>(
|
||||||
|
selectedElement: T | undefined,
|
||||||
|
selectedElementRef: RefObject<any>,
|
||||||
|
) {
|
||||||
|
useEffect(() => {
|
||||||
|
const element = selectedElementRef.current as HTMLElement | undefined;
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
// The selected item's bounding box might be on screen, but hidden underneath the sticky header
|
||||||
|
// which overlaps the table view. As a workaround we hardcode a fixed distance from the top which
|
||||||
|
// we consider to be obscured. It does not have to exact, as it's just a threshold for when to scroll.
|
||||||
|
const heightOfStickyHeader = 30;
|
||||||
|
if (rect.top < heightOfStickyHeader || rect.bottom > window.innerHeight) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
block: "center", // vertically align to center
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (rect.left < 0 || rect.right > window.innerWidth) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
block: "nearest",
|
||||||
|
inline: "nearest", // horizontally align as little as possible
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [selectedElement, selectedElementRef]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user