{"record":{"id":"93139e30bf8996e9","repo":"BookStackApp/BookStack","slug":"node-at-cords-not-tablecellnode","errorCode":null,"errorMessage":"Node at cords not TableCellNode.","messagePattern":"Node at cords not TableCellNode\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"resources/js/wysiwyg/lexical/table/LexicalTableNode.ts","lineNumber":293,"sourceCode":"\n    const node = $getNearestNodeFromDOMNode(cell.elem);\n\n    if ($isTableCellNode(node)) {\n      return node;\n    }\n\n    return null;\n  }\n\n  getCellNodeFromCordsOrThrow(\n    x: number,\n    y: number,\n    table: TableDOMTable,\n  ): TableCellNode {\n    const node = this.getCellNodeFromCords(x, y, table);\n\n    if (!node) {\n      throw new Error('Node at cords not TableCellNode.');\n    }\n\n    return node;\n  }\n\n  canSelectBefore(): true {\n    return true;\n  }\n\n  canIndent(): false {\n    return false;\n  }\n}\n\nexport function $getElementForTableNode(\n  editor: LexicalEditor,\n  tableNode: TableNode,\n): TableDOMTable {","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts#L275-L311","documentation":"getCellNodeFromCordsOrThrow maps (x, y) grid coordinates to a TableCellNode via getCellNodeFromCords, which returns null when the coordinates are out of range. This wrapper throws 'Node at cords not TableCellNode.' instead of returning null.","triggerScenarios":"selectTableNodeInDirection computing an out-of-bounds coordinate when moving a table-cell selection beyond the grid, or coordinate lookups against a stale TableDOMTable that no longer matches the current table.","commonSituations":"Programmatic table selection moved past the last row/column; table resized between selection update and lookup; tests driving direction commands without bounds clamping.","solutions":["Bounds-check target x/y against the grid size before calling selectTableNodeInDirection or getCellNodeFromCordsOrThrow.","Use the non-throwing getCellNodeFromCords and return early on null.","Re-derive coordinates from the current selection inside the same editor.update so the grid data is fresh.","Verify the TableObserver is attached and its table data rebuilt after structural table edits."],"exampleFix":"// before\nconst cellNode = tableNode.getCellNodeFromCordsOrThrow(x, y, table); // throws\n\n// after\nconst cellNode = tableNode.getCellNodeFromCords(x, y, table);\nif (!cellNode) return;","handlingStrategy":"validation","validationCode":"const rows = tableNode.getRows();\nconst cols = tableNode.getColumns?.() ?? 0;\nif (x < 0 || y < 0 || y >= rows.length || x >= cols) return; // skip out-of-bounds selection","typeGuard":null,"tryCatchPattern":"try {\n  const node = tableNode.getCellNodeFromCordsOrThrow(x, y, table);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Node at cords not TableCellNode.') return;\n  throw e;\n}","preventionTips":["Bounds-check x/y against getRows()/getColumns() before directional selection","Derive coordinates from live selection inside the same editor.update","Use nullable getCellNodeFromCords when out-of-grid moves are expected"],"tags":["lexical","table","coordinates","selection"],"backgroundTag":"node-at-coords-not-tablecell","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}