diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 71ee3dc72a..2affb2fb1a 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -22,8 +22,10 @@ import { createCellEvent, getCellStyle, getColSpan, + getColumnInColumns, getLeftRightKey, getNextSelectedCellPosition, + getRowInRows, isCtrlKeyHeldDown, isDefaultCellInput, isSelectedCellEditable, @@ -360,6 +362,9 @@ export function DataGrid(props: DataGridPr enableVirtualization }); + /** + * computed values + */ const topSummaryRowsCount = topSummaryRows?.length ?? 0; const bottomSummaryRowsCount = bottomSummaryRows?.length ?? 0; const summaryRowsCount = topSummaryRowsCount + bottomSummaryRowsCount; @@ -368,15 +373,9 @@ export function DataGrid(props: DataGridPr const minRowIdx = -headerAndTopSummaryRowsCount; const mainHeaderRowIdx = minRowIdx + groupedColumnHeaderRowsCount; const maxRowIdx = rows.length + bottomSummaryRowsCount - 1; - - const [selectedPosition, setSelectedPosition] = useState( - (): SelectCellState | EditCellState => ({ idx: -1, rowIdx: minRowIdx - 1, mode: 'SELECT' }) - ); - - /** - * computed values - */ + const maxColIdx = columns.length - 1; const isTreeGrid = role === 'treegrid'; + const minColIdx = isTreeGrid ? -1 : 0; const headerRowsHeight = headerRowsCount * headerRowHeight; const summaryRowsHeight = summaryRowsCount * summaryRowHeight; const clientHeight = gridHeight - headerRowsHeight - summaryRowsHeight; @@ -384,6 +383,30 @@ export function DataGrid(props: DataGridPr const { leftKey, rightKey } = getLeftRightKey(direction); const ariaRowCount = rawAriaRowCount ?? headerRowsCount + rows.length + summaryRowsCount; + let [selectedPosition, setSelectedPosition] = useState( + (): SelectCellState | EditCellState => ({ idx: -1, rowIdx: minRowIdx - 1, mode: 'SELECT' }) + ); + + // Reinitialize the position and immediately use the new state + // if the current values are no longer valid. + // This can happen if a column or row is removed. + if ( + !( + selectedPosition.idx === -1 && + selectedPosition.rowIdx === minRowIdx - 1 && + selectedPosition.mode === 'SELECT' + ) && + (selectedPosition.idx < minColIdx || + selectedPosition.idx > maxColIdx || + selectedPosition.rowIdx < minRowIdx || + selectedPosition.rowIdx > maxRowIdx) + ) { + selectedPosition = { idx: -1, rowIdx: minRowIdx - 1, mode: 'SELECT' }; + // eslint-disable-next-line sonarjs/no-useless-react-setstate + setSelectedPosition(selectedPosition); + setDraggedOverRowIdx(undefined); + } + const defaultGridComponents = useMemo( () => ({ renderCheckbox, @@ -457,8 +480,6 @@ export function DataGrid(props: DataGridPr setIsColumnResizing ); - const minColIdx = isTreeGrid ? -1 : 0; - const maxColIdx = columns.length - 1; const selectedCellIsWithinSelectionBounds = isCellWithinSelectionBounds(selectedPosition); const selectedCellIsWithinViewportBounds = isCellWithinViewportBounds(selectedPosition); @@ -561,7 +582,7 @@ export function DataGrid(props: DataGridPr ) { const step = sign(rowIdx - previousRowIdx); for (let i = previousRowIdx + step; i < rowIdx; i += step) { - const row = rows[i]; + const row = getRow(i); if (isRowSelectionDisabled?.(row) === true) continue; if (checked) { newSelectedRows.add(rowKeyGetter(row)); @@ -579,12 +600,11 @@ export function DataGrid(props: DataGridPr if (mode === 'EDIT') return; if (onCellKeyDown && isRowIdxWithinViewportBounds(rowIdx)) { - const row = rows[rowIdx]; const cellEvent = createCellEvent(event); onCellKeyDown( { mode: 'SELECT', - row, + row: getRow(rowIdx), column: columns[idx], rowIdx, selectCell @@ -633,7 +653,7 @@ export function DataGrid(props: DataGridPr function updateRow(column: CalculatedColumn, rowIdx: number, row: R) { if (typeof onRowsChange !== 'function') return; - if (row === rows[rowIdx]) return; + if (row === getRow(rowIdx)) return; const updatedRows = rows.with(rowIdx, row); onRowsChange(updatedRows, { indexes: [rowIdx], @@ -643,29 +663,38 @@ export function DataGrid(props: DataGridPr function commitEditorChanges() { if (selectedPosition.mode !== 'EDIT') return; - updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row); + updateRow(getColumn(selectedPosition.idx), selectedPosition.rowIdx, selectedPosition.row); } function handleCellCopy(event: CellClipboardEvent) { - if (!selectedCellIsWithinViewportBounds) return; + if (!selectedCellIsWithinViewportBounds || typeof onCellCopy !== 'function') return; + const { idx, rowIdx } = selectedPosition; - onCellCopy?.({ row: rows[rowIdx], column: columns[idx] }, event); + + // Prevent copy/paste on group rows + if (idx === -1) return; + + onCellCopy({ row: getRow(rowIdx), column: getColumn(idx) }, event); } function handleCellPaste(event: CellClipboardEvent) { - if (!onCellPaste || !onRowsChange || !isCellEditable(selectedPosition)) { + if ( + typeof onCellPaste !== 'function' || + typeof onRowsChange !== 'function' || + !isCellEditable(selectedPosition) + ) { return; } const { idx, rowIdx } = selectedPosition; - const column = columns[idx]; - const updatedRow = onCellPaste({ row: rows[rowIdx], column }, event); + const column = getColumn(idx); + const updatedRow = onCellPaste({ row: getRow(rowIdx), column }, event); updateRow(column, rowIdx, updatedRow); } function handleCellInput(event: KeyboardEvent) { if (!selectedCellIsWithinViewportBounds) return; - const row = rows[selectedPosition.rowIdx]; + const row = getRow(selectedPosition.rowIdx); const { key, shiftKey } = event; // Select the row on Shift + Space @@ -752,14 +781,15 @@ export function DataGrid(props: DataGridPr if (onRowsChange == null) return; const { rowIdx, idx } = selectedPosition; - const column = columns[idx]; - const sourceRow = rows[rowIdx]; + const column = getColumn(idx); + const sourceRow = getRow(rowIdx); const updatedRows = [...rows]; const indexes: number[] = []; for (let i = startRowIdx; i < endRowIdx; i++) { if (isCellEditable({ rowIdx: i, idx })) { - const updatedRow = onFill!({ columnKey: column.key, sourceRow, targetRow: rows[i] }); - if (updatedRow !== rows[i]) { + const targetRow = getRow(i); + const updatedRow = onFill!({ columnKey: column.key, sourceRow, targetRow }); + if (updatedRow !== targetRow) { updatedRows[i] = updatedRow; indexes.push(i); } @@ -774,6 +804,14 @@ export function DataGrid(props: DataGridPr /** * utils */ + function getColumn(index: number) { + return getColumnInColumns(columns, index); + } + + function getRow(index: number) { + return getRowInRows(rows, index); + } + function isColIdxWithinSelectionBounds(idx: number) { return idx >= minColIdx && idx <= maxColIdx; } @@ -808,7 +846,7 @@ export function DataGrid(props: DataGridPr const samePosition = isSamePosition(selectedPosition, position); if (options?.enableEditor && isCellEditable(position)) { - const row = rows[position.rowIdx]; + const row = getRow(position.rowIdx); setSelectedPosition({ ...position, mode: 'EDIT', row, originalRow: row }); } else if (samePosition) { // Avoid re-renders if the selected cell state is the same @@ -821,8 +859,8 @@ export function DataGrid(props: DataGridPr if (onSelectedCellChange && !samePosition) { onSelectedCellChange({ rowIdx: position.rowIdx, - row: isRowIdxWithinViewportBounds(position.rowIdx) ? rows[position.rowIdx] : undefined, - column: columns[position.idx] + row: isRowIdxWithinViewportBounds(position.rowIdx) ? getRow(position.rowIdx) : undefined, + column: getColumn(position.idx) }); } } @@ -952,14 +990,14 @@ export function DataGrid(props: DataGridPr } const { idx, rowIdx } = selectedPosition; - const column = columns[idx]; + const column = getColumn(idx); if (column.renderEditCell == null || column.editable === false) { return; } const isLastRow = rowIdx === maxRowIdx; const columnWidth = getColumnWidth(column); - const colSpan = column.colSpan?.({ type: 'ROW', row: rows[rowIdx] }) ?? 1; + const colSpan = column.colSpan?.({ type: 'ROW', row: getRow(rowIdx) }) ?? 1; const { insetInlineStart, ...style } = getCellStyle(column, colSpan); const marginEnd = 'calc(var(--rdg-drag-handle-size) * -0.5 + 1px)'; const isLastColumn = column.idx + colSpan - 1 === maxColIdx; @@ -999,7 +1037,7 @@ export function DataGrid(props: DataGridPr } const { idx, row } = selectedPosition; - const column = columns[idx]; + const column = getColumn(idx); const colSpan = getColSpan(column, lastFrozenColumnIndex, { type: 'ROW', row }); const closeOnExternalRowChange = column.editorOptions?.closeOnExternalRowChange ?? true; @@ -1025,7 +1063,7 @@ export function DataGrid(props: DataGridPr if ( closeOnExternalRowChange && - rows[selectedPosition.rowIdx] !== selectedPosition.originalRow + getRow(selectedPosition.rowIdx) !== selectedPosition.originalRow ) { // Discard changes if rows are updated from outside closeEditor(false); @@ -1048,7 +1086,7 @@ export function DataGrid(props: DataGridPr function getRowViewportColumns(rowIdx: number) { // idx can be -1 if grouping is enabled - const selectedColumn = selectedPosition.idx === -1 ? undefined : columns[selectedPosition.idx]; + const selectedColumn = columns[selectedPosition.idx]; if ( selectedColumn !== undefined && selectedPosition.rowIdx === rowIdx && @@ -1086,7 +1124,7 @@ export function DataGrid(props: DataGridPr const rowIdx = isRowOutsideViewport ? selectedRowIdx : viewportRowIdx; let rowColumns = viewportColumns; - const selectedColumn = selectedIdx === -1 ? undefined : columns[selectedIdx]; + const selectedColumn = selectedIdx === -1 ? undefined : getColumn(selectedIdx); if (selectedColumn !== undefined) { if (isRowOutsideViewport) { // if the row is outside the viewport then only render the selected cell @@ -1097,7 +1135,7 @@ export function DataGrid(props: DataGridPr } } - const row = rows[rowIdx]; + const row = getRow(rowIdx); const gridRowStart = headerAndTopSummaryRowsCount + rowIdx + 1; let key: K | number = rowIdx; let isRowSelected = false; diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index bafb2e0f10..5c77d858a3 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'; import type { Key } from 'react'; import { useLatestFunc } from './hooks'; -import { assertIsValidKeyGetter, getLeftRightKey } from './utils'; +import { assertIsValidKeyGetter, getLeftRightKey, getRowInRows } from './utils'; import type { CellClipboardEvent, CellCopyArgs, @@ -122,7 +122,7 @@ export function TreeDataGrid({ ): [Readonly>, number] => { let groupRowsCount = 0; const groups: GroupByDictionary = {}; - for (const [key, childRows] of Object.entries(rowGrouper(rows, groupByKey))) { + for (const [key, childRows] of Object.entries(rowGrouper(rows, groupByKey!))) { // Recursively group each parent group const [childGroups, childRowsCount] = remainingGroupByKeys.length === 0 @@ -159,7 +159,7 @@ export function TreeDataGrid({ Object.keys(rows).forEach((groupKey, posInSet, keys) => { const id = groupIdGetter(groupKey, parentId); const isExpanded = expandedGroupIds.has(id); - const { childRows, childGroups, startRowIndex } = rows[groupKey]; + const { childRows, childGroups, startRowIndex } = rows[groupKey]!; const groupRow: GroupRow = { id, @@ -189,6 +189,13 @@ export function TreeDataGrid({ } }, [expandedGroupIds, groupedRows, rawRows, groupIdGetter]); + const getRow = useCallback( + (index: number) => { + return getRowInRows(rows, index); + }, + [rows] + ); + const rowHeight = useMemo(() => { if (typeof rawRowHeight === 'function') { return (row: R | GroupRow): number => { @@ -206,7 +213,7 @@ export function TreeDataGrid({ (row: R | GroupRow) => { const rowIdx = rows.indexOf(row); for (let i = rowIdx - 1; i >= 0; i--) { - const parentRow = rows[i]; + const parentRow = getRow(i); if (isGroupRow(parentRow) && (!isGroupRow(row) || row.parentId === parentRow.id)) { return [parentRow, i] as const; } @@ -214,7 +221,7 @@ export function TreeDataGrid({ return undefined; }, - [isGroupRow, rows] + [isGroupRow, rows, getRow] ); const rowKeyGetter = useCallback( @@ -299,7 +306,7 @@ export function TreeDataGrid({ if (args.mode === 'EDIT') return; const { column, rowIdx, selectCell } = args; const idx = column?.idx ?? -1; - const row = rows[rowIdx]; + const row = getRow(rowIdx); if (!isGroupRow(row)) return; if ( @@ -347,8 +354,8 @@ export function TreeDataGrid({ const updatedRawRows = [...rawRows]; const rawIndexes: number[] = []; for (const index of indexes) { - const rawIndex = rawRows.indexOf(rows[index] as R); - updatedRawRows[rawIndex] = updatedRows[index]; + const rawIndex = rawRows.indexOf(getRow(index) as R); + updatedRawRows[rawIndex] = getRowInRows(updatedRows, index); rawIndexes.push(rawIndex); } onRowsChange(updatedRawRows, { diff --git a/src/hooks/useCalculatedColumns.ts b/src/hooks/useCalculatedColumns.ts index 9929d91a17..afa9ce4658 100644 --- a/src/hooks/useCalculatedColumns.ts +++ b/src/hooks/useCalculatedColumns.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { clampColumnWidth, max, min } from '../utils'; +import { clampColumnWidth, getColumnInColumns, max, min } from '../utils'; import type { CalculatedColumn, CalculatedColumnParent, ColumnOrColumnGroup, Omit } from '../types'; import { renderValue } from '../cellRenderers'; import { SELECT_COLUMN_KEY } from '../Columns'; @@ -189,14 +189,14 @@ export function useCalculatedColumns({ } if (lastFrozenColumnIndex !== -1) { - const columnMetric = columnMetrics.get(columns[lastFrozenColumnIndex])!; + const columnMetric = columnMetrics.get(getColumnInColumns(columns, lastFrozenColumnIndex))!; totalFrozenColumnWidth = columnMetric.left + columnMetric.width; } const layoutCssVars: Record = {}; for (let i = 0; i <= lastFrozenColumnIndex; i++) { - const column = columns[i]; + const column = getColumnInColumns(columns, i); layoutCssVars[`--rdg-frozen-left-${column.idx}`] = `${columnMetrics.get(column)!.left}px`; } @@ -222,7 +222,7 @@ export function useCalculatedColumns({ // get the first visible non-frozen column index let colVisibleStartIdx = firstUnfrozenColumnIdx; while (colVisibleStartIdx < lastColIdx) { - const { left, width } = columnMetrics.get(columns[colVisibleStartIdx])!; + const { left, width } = columnMetrics.get(getColumnInColumns(columns, colVisibleStartIdx))!; // if the right side of the columnn is beyond the left side of the available viewport, // then it is the first column that's at least partially visible if (left + width > viewportLeft) { @@ -234,7 +234,7 @@ export function useCalculatedColumns({ // get the last visible non-frozen column index let colVisibleEndIdx = colVisibleStartIdx; while (colVisibleEndIdx < lastColIdx) { - const { left, width } = columnMetrics.get(columns[colVisibleEndIdx])!; + const { left, width } = columnMetrics.get(getColumnInColumns(columns, colVisibleEndIdx))!; // if the right side of the column is beyond or equal to the right side of the available viewport, // then it the last column that's at least partially visible, as the previous column's right side is not beyond the viewport. if (left + width >= viewportRight) { diff --git a/src/hooks/useGridDimensions.ts b/src/hooks/useGridDimensions.ts index 907d6f02e3..bfeeb7441f 100644 --- a/src/hooks/useGridDimensions.ts +++ b/src/hooks/useGridDimensions.ts @@ -19,7 +19,7 @@ export function useGridDimensions() { setBlockSize(clientHeight); const resizeObserver = new ResizeObserver((entries) => { - const size = entries[0].contentBoxSize[0]; + const size = entries[0]!.contentBoxSize[0]!; // we use flushSync here to avoid flashing scrollbars flushSync(() => { diff --git a/src/hooks/useViewportColumns.ts b/src/hooks/useViewportColumns.ts index c79d085399..0b4e43677a 100644 --- a/src/hooks/useViewportColumns.ts +++ b/src/hooks/useViewportColumns.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { getColSpan } from '../utils'; +import { getColSpan, getColumnInColumns, getRowInRows } from '../utils'; import type { CalculatedColumn, Maybe } from '../types'; interface ViewportColumnsArgs { @@ -52,7 +52,7 @@ export function useViewportColumns({ // check viewport rows for (let rowIdx = rowOverscanStartIdx; rowIdx <= rowOverscanEndIdx; rowIdx++) { - const row = rows[rowIdx]; + const row = getRowInRows(rows, rowIdx); if ( updateStartIdx(colIdx, getColSpan(column, lastFrozenColumnIndex, { type: 'ROW', row })) ) { @@ -103,7 +103,7 @@ export function useViewportColumns({ return useMemo((): readonly CalculatedColumn[] => { const viewportColumns: CalculatedColumn[] = []; for (let colIdx = 0; colIdx <= colOverscanEndIdx; colIdx++) { - const column = columns[colIdx]; + const column = getColumnInColumns(columns, colIdx); if (colIdx < startIdx && !column.frozen) continue; viewportColumns.push(column); diff --git a/src/hooks/useViewportRows.ts b/src/hooks/useViewportRows.ts index 13b8502302..1eef73be7c 100644 --- a/src/hooks/useViewportRows.ts +++ b/src/hooks/useViewportRows.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { floor, max, min } from '../utils'; +import { floor, getRowInRows, max, min } from '../utils'; interface ViewportRowsArgs { rows: readonly R[]; @@ -80,14 +80,14 @@ export function useViewportRows({ return { totalRowHeight, gridTemplateRows, - getRowTop: (rowIdx: number) => rowPositions[validateRowIdx(rowIdx)].top, - getRowHeight: (rowIdx: number) => rowPositions[validateRowIdx(rowIdx)].height, + getRowTop: (rowIdx: number) => getRowInRows(rowPositions, validateRowIdx(rowIdx)).top, + getRowHeight: (rowIdx: number) => getRowInRows(rowPositions, validateRowIdx(rowIdx)).height, findRowIdx(offset: number) { let start = 0; let end = rowPositions.length - 1; while (start <= end) { const middle = start + floor((end - start) / 2); - const currentOffset = rowPositions[middle].top; + const currentOffset = getRowInRows(rowPositions, middle).top; if (currentOffset === offset) return middle; diff --git a/src/utils/colSpanUtils.ts b/src/utils/colSpanUtils.ts index e986ba90b3..74e2eb2634 100644 --- a/src/utils/colSpanUtils.ts +++ b/src/utils/colSpanUtils.ts @@ -3,7 +3,7 @@ import type { CalculatedColumn, ColSpanArgs } from '../types'; export function getColSpan( column: CalculatedColumn, lastFrozenColumnIndex: number, - args: ColSpanArgs + args: ColSpanArgs, NoInfer> ): number | undefined { const colSpan = typeof column.colSpan === 'function' ? column.colSpan(args) : 1; if ( diff --git a/src/utils/index.ts b/src/utils/index.ts index f6bd990888..3bc15351f0 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -10,6 +10,23 @@ export * from './styleUtils'; export const { min, max, floor, sign, abs } = Math; +export function getColumnInColumns( + columns: readonly CalculatedColumn[], + index: number +) { + if (index < 0 || index >= columns.length) { + throw new Error(`columns[${index}] is out of bounds (length: ${columns.length})`); + } + return columns[index]!; +} + +export function getRowInRows(rows: readonly R[], index: number) { + if (index < 0 || index >= rows.length) { + throw new Error(`rows[${index}] is out of bounds (length: ${rows.length})`); + } + return rows[index]!; +} + export function assertIsValidKeyGetter( keyGetter: Maybe<(row: NoInfer) => K> ): asserts keyGetter is (row: R) => K { diff --git a/src/utils/selectedCellUtils.ts b/src/utils/selectedCellUtils.ts index db13f05da6..b14e23f111 100644 --- a/src/utils/selectedCellUtils.ts +++ b/src/utils/selectedCellUtils.ts @@ -5,6 +5,7 @@ import type { Maybe, Position } from '../types'; +import { getColumnInColumns, getRowInRows } from '.'; import { getColSpan } from './colSpanUtils'; interface IsSelectedCellEditableOpts { @@ -18,8 +19,8 @@ export function isSelectedCellEditable({ columns, rows }: IsSelectedCellEditableOpts): boolean { - const column = columns[selectedPosition.idx]; - const row = rows[selectedPosition.rowIdx]; + const column = getColumnInColumns(columns, selectedPosition.idx); + const row = getRowInRows(rows, selectedPosition.rowIdx); return isCellEditableUtil(column, row); } @@ -76,19 +77,19 @@ function getSelectedCellColSpan({ ) { return getColSpan(column, lastFrozenColumnIndex, { type: 'SUMMARY', - row: topSummaryRows[rowIdx + topSummaryRowsCount] + row: getRowInRows(topSummaryRows, rowIdx + topSummaryRowsCount) }); } if (rowIdx >= 0 && rowIdx < rows.length) { - const row = rows[rowIdx]; + const row = getRowInRows(rows, rowIdx); return getColSpan(column, lastFrozenColumnIndex, { type: 'ROW', row }); } if (bottomSummaryRows) { return getColSpan(column, lastFrozenColumnIndex, { type: 'SUMMARY', - row: bottomSummaryRows[rowIdx - rows.length] + row: getRowInRows(bottomSummaryRows, rowIdx - rows.length) }); } @@ -145,7 +146,7 @@ export function getNextSelectedCellPosition({ const setHeaderGroupColAndRowSpan = () => { if (moveNext) { // find the parent at the same row level - const nextColumn = columns[nextIdx]; + const nextColumn = getColumnInColumns(columns, nextIdx); let { parent } = nextColumn; while (parent !== undefined) { const parentRowIdx = getParentRowIdx(parent); @@ -157,7 +158,7 @@ export function getNextSelectedCellPosition({ } } else if (moveUp) { // find the first reachable parent - const nextColumn = columns[nextIdx]; + const nextColumn = getColumnInColumns(columns, nextIdx); let { parent } = nextColumn; let found = false; while (parent !== undefined) { @@ -211,7 +212,7 @@ export function getNextSelectedCellPosition({ // Find the last reachable parent for the new rowIdx // This check is needed when navigating to a column // that does not have a parent matching the new rowIdx - const nextColumn = columns[nextIdx]; + const nextColumn = getColumnInColumns(columns, nextIdx); let { parent } = nextColumn; const nextParentRowIdx = nextRowIdx; nextRowIdx = mainHeaderRowIdx; diff --git a/tsconfig.lib.json b/tsconfig.lib.json index e1cc3af350..6e3366feca 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -4,7 +4,8 @@ "composite": false, "declaration": true, "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], - "noCheck": true + "noCheck": true, + "noUncheckedIndexedAccess": true }, "files": ["src/index.ts"], "include": ["src/globals.d.ts"] diff --git a/tsconfig.src.json b/tsconfig.src.json index b535e9f135..91fda87a05 100644 --- a/tsconfig.src.json +++ b/tsconfig.src.json @@ -1,7 +1,8 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"] + "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"], + "noUncheckedIndexedAccess": true }, "include": ["src/**/*"] }