From 67fe35e2853fd8427a6a80846f16e43fb2220d3e Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Fri, 10 Apr 2026 10:19:37 +0500 Subject: [PATCH 01/15] fix: selection fixed & menu works in mid of table Signed-off-by: hassaansaleem28 --- .../BlockEditor/TableMenu/TableMenu.test.tsx | 233 ++++++++++++++++++ .../BlockEditor/TableMenu/TableMenu.tsx | 96 +++++++- .../components/BlockEditor/block-editor.less | 4 + 3 files changed, 326 insertions(+), 7 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx new file mode 100644 index 000000000000..56b7a52ed6a0 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx @@ -0,0 +1,233 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { render } from '@testing-library/react'; +import { Editor } from '@tiptap/react'; +import tippy from 'tippy.js'; +import TableMenu from './TableMenu'; + +jest.mock('tippy.js', () => { + return { + __esModule: true, + default: jest.fn(), + }; +}); + +const mockSetProps = jest.fn(); +const mockShow = jest.fn(); +const mockHide = jest.fn(); +const mockDestroy = jest.fn(); +const mockRun = jest.fn(); +let removeSpy: jest.SpyInstance; + +const createRect = (rect: Partial): DOMRect => { + return { + x: 0, + y: 0, + width: 0, + height: 0, + top: 0, + right: 0, + bottom: 0, + left: 0, + toJSON: jest.fn(), + ...rect, + } as DOMRect; +}; + +const createChainMethods = () => { + return { + addRowAfter: jest.fn().mockReturnValue({ run: mockRun }), + addColumnAfter: jest.fn().mockReturnValue({ run: mockRun }), + deleteRow: jest.fn().mockReturnValue({ run: mockRun }), + deleteColumn: jest.fn().mockReturnValue({ run: mockRun }), + deleteTable: jest.fn().mockReturnValue({ run: mockRun }), + }; +}; + +const mockChain = jest.fn().mockImplementation(() => { + return { + focus: jest.fn().mockImplementation(() => createChainMethods()), + }; +}); + +const mockEditor = { + view: { + dom: document.createElement('div'), + }, + isEditable: true, + chain: mockChain, +} as unknown as Editor; + +describe('TableMenu', () => { + beforeAll(() => { + removeSpy = jest + .spyOn(HTMLElement.prototype, 'remove') + .mockImplementation(() => undefined); + }); + + afterAll(() => { + removeSpy.mockRestore(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + (tippy as unknown as jest.Mock).mockReturnValue({ + setProps: mockSetProps, + show: mockShow, + hide: mockHide, + destroy: mockDestroy, + }); + }); + + it('anchors menu to clicked table cell instead of full table bounds', () => { + render(); + + const tableWrapper = document.createElement('div'); + tableWrapper.className = 'tableWrapper'; + + const table = document.createElement('table'); + const row = document.createElement('tr'); + const cell = document.createElement('td'); + const content = document.createElement('span'); + + row.appendChild(cell); + cell.appendChild(content); + table.appendChild(row); + tableWrapper.appendChild(table); + document.body.appendChild(tableWrapper); + + const wrapperRect = createRect({ + x: 20, + y: 10, + top: 10, + left: 20, + right: 620, + bottom: 410, + width: 600, + height: 400, + }); + + const cellRect = createRect({ + x: 280, + y: 180, + top: 180, + left: 280, + right: 460, + bottom: 212, + width: 180, + height: 32, + }); + + jest.spyOn(tableWrapper, 'getBoundingClientRect').mockReturnValue(wrapperRect); + jest.spyOn(cell, 'getBoundingClientRect').mockReturnValue(cellRect); + + content.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); + + expect(mockSetProps).toHaveBeenCalledTimes(1); + expect(mockShow).toHaveBeenCalledTimes(1); + + const tippyProps = mockSetProps.mock.calls[0][0] as { + getReferenceClientRect: () => DOMRect; + }; + + expect(tippyProps.getReferenceClientRect()).toEqual(cellRect); + expect(tippyProps.getReferenceClientRect()).not.toEqual(wrapperRect); + + tableWrapper.remove(); + }); + + it('anchors to selected-cells bounding area when click target is table wrapper', () => { + render(); + + const tableWrapper = document.createElement('div'); + tableWrapper.className = 'tableWrapper'; + + const firstSelectedCell = document.createElement('td'); + firstSelectedCell.className = 'selectedCell'; + + const secondSelectedCell = document.createElement('td'); + secondSelectedCell.className = 'selectedCell'; + + tableWrapper.appendChild(firstSelectedCell); + tableWrapper.appendChild(secondSelectedCell); + document.body.appendChild(tableWrapper); + + const firstRect = createRect({ + top: 100, + left: 200, + right: 250, + bottom: 140, + width: 50, + height: 40, + }); + + const secondRect = createRect({ + top: 130, + left: 260, + right: 330, + bottom: 170, + width: 70, + height: 40, + }); + + jest.spyOn(firstSelectedCell, 'getBoundingClientRect').mockReturnValue(firstRect); + jest.spyOn(secondSelectedCell, 'getBoundingClientRect').mockReturnValue(secondRect); + + tableWrapper.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); + + const tippyProps = mockSetProps.mock.calls[0][0] as { + getReferenceClientRect: () => DOMRect; + }; + const rect = tippyProps.getReferenceClientRect(); + + expect(rect.top).toBe(100); + expect(rect.left).toBe(200); + expect(rect.right).toBe(330); + expect(rect.bottom).toBe(170); + expect(rect.width).toBe(130); + expect(rect.height).toBe(70); + + tableWrapper.remove(); + }); + + it('falls back to table wrapper bounds when no selected cells exist', () => { + render(); + + const tableWrapper = document.createElement('div'); + tableWrapper.className = 'tableWrapper'; + document.body.appendChild(tableWrapper); + + const wrapperRect = createRect({ + x: 32, + y: 48, + top: 48, + left: 32, + right: 672, + bottom: 448, + width: 640, + height: 400, + }); + + jest.spyOn(tableWrapper, 'getBoundingClientRect').mockReturnValue(wrapperRect); + + tableWrapper.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); + + const tippyProps = mockSetProps.mock.calls[0][0] as { + getReferenceClientRect: () => DOMRect; + }; + + expect(tippyProps.getReferenceClientRect()).toEqual(wrapperRect); + + tableWrapper.remove(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index 95a28a917014..a75fd21370d0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -24,6 +24,81 @@ interface TableMenuProps { editor: Editor; } +const TABLE_WRAPPER_SELECTOR = '.tableWrapper'; +const TABLE_CELL_SELECTOR = 'td, th'; +const SELECTED_TABLE_CELL_SELECTOR = 'td.selectedCell, th.selectedCell'; + +const buildRect = ( + top: number, + left: number, + right: number, + bottom: number +): DOMRect => { + return { + x: left, + y: top, + top, + left, + right, + bottom, + width: right - left, + height: bottom - top, + toJSON: () => ({ + x: left, + y: top, + top, + left, + right, + bottom, + width: right - left, + height: bottom - top, + }), + } as DOMRect; +}; + +const getSelectedCellsRect = (tableWrapper: Element): DOMRect | null => { + const selectedCells = tableWrapper.querySelectorAll( + SELECTED_TABLE_CELL_SELECTOR + ); + + if (!selectedCells.length) { + return null; + } + + let top = Number.POSITIVE_INFINITY; + let left = Number.POSITIVE_INFINITY; + let right = Number.NEGATIVE_INFINITY; + let bottom = Number.NEGATIVE_INFINITY; + + selectedCells.forEach((cell) => { + const rect = cell.getBoundingClientRect(); + + top = Math.min(top, rect.top); + left = Math.min(left, rect.left); + right = Math.max(right, rect.right); + bottom = Math.max(bottom, rect.bottom); + }); + + return buildRect(top, left, right, bottom); +}; + +const getReferenceClientRect = + (target: Element) => (): DOMRect => { + const cell = target.closest(TABLE_CELL_SELECTOR); + + if (cell) { + return cell.getBoundingClientRect(); + } + + const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); + + if (!tableWrapper) { + return target.getBoundingClientRect(); + } + + return getSelectedCellsRect(tableWrapper) ?? tableWrapper.getBoundingClientRect(); + }; + const TableMenu = (props: TableMenuProps) => { const { editor } = props; const { view, isEditable } = editor; @@ -31,16 +106,23 @@ const TableMenu = (props: TableMenuProps) => { const tableMenuPopup = useRef(null); const handleMouseDown = useCallback((event: MouseEvent) => { - const target = event.target as HTMLElement; - const table = target?.closest('.tableWrapper'); + const { target } = event; - if (table?.contains(target)) { - tableMenuPopup.current?.setProps({ - getReferenceClientRect: () => table.getBoundingClientRect(), - }); + if (!(target instanceof Element)) { + return; + } + + const table = target.closest(TABLE_WRAPPER_SELECTOR); - tableMenuPopup.current?.show(); + if (!table) { + return; } + + tableMenuPopup.current?.setProps({ + getReferenceClientRect: getReferenceClientRect(target), + }); + + tableMenuPopup.current?.show(); }, []); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less index ef2d85ce24fd..5acf75d797a1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less @@ -125,6 +125,10 @@ font-weight: 500; } + .tableWrapper *::selection { + color: @text-color; + } + blockquote { border-left: 4px solid @border-color; } From 04b385d44a043dd12bd2205e4904f7a59781e0bb Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Sat, 11 Apr 2026 11:54:27 +0500 Subject: [PATCH 02/15] fix: run make ui-checkstyle-src DONE Signed-off-by: hassaansaleem28 --- .../BlockEditor/TableMenu/TableMenu.test.tsx | 16 +++++++++--- .../BlockEditor/TableMenu/TableMenu.tsx | 25 ++++++++++--------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx index 56b7a52ed6a0..547e27cdc96b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx @@ -128,7 +128,9 @@ describe('TableMenu', () => { height: 32, }); - jest.spyOn(tableWrapper, 'getBoundingClientRect').mockReturnValue(wrapperRect); + jest + .spyOn(tableWrapper, 'getBoundingClientRect') + .mockReturnValue(wrapperRect); jest.spyOn(cell, 'getBoundingClientRect').mockReturnValue(cellRect); content.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); @@ -180,8 +182,12 @@ describe('TableMenu', () => { height: 40, }); - jest.spyOn(firstSelectedCell, 'getBoundingClientRect').mockReturnValue(firstRect); - jest.spyOn(secondSelectedCell, 'getBoundingClientRect').mockReturnValue(secondRect); + jest + .spyOn(firstSelectedCell, 'getBoundingClientRect') + .mockReturnValue(firstRect); + jest + .spyOn(secondSelectedCell, 'getBoundingClientRect') + .mockReturnValue(secondRect); tableWrapper.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); @@ -218,7 +224,9 @@ describe('TableMenu', () => { height: 400, }); - jest.spyOn(tableWrapper, 'getBoundingClientRect').mockReturnValue(wrapperRect); + jest + .spyOn(tableWrapper, 'getBoundingClientRect') + .mockReturnValue(wrapperRect); tableWrapper.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index a75fd21370d0..42a8abccc6e0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -82,22 +82,23 @@ const getSelectedCellsRect = (tableWrapper: Element): DOMRect | null => { return buildRect(top, left, right, bottom); }; -const getReferenceClientRect = - (target: Element) => (): DOMRect => { - const cell = target.closest(TABLE_CELL_SELECTOR); +const getReferenceClientRect = (target: Element) => (): DOMRect => { + const cell = target.closest(TABLE_CELL_SELECTOR); - if (cell) { - return cell.getBoundingClientRect(); - } + if (cell) { + return cell.getBoundingClientRect(); + } - const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); + const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); - if (!tableWrapper) { - return target.getBoundingClientRect(); - } + if (!tableWrapper) { + return target.getBoundingClientRect(); + } - return getSelectedCellsRect(tableWrapper) ?? tableWrapper.getBoundingClientRect(); - }; + return ( + getSelectedCellsRect(tableWrapper) ?? tableWrapper.getBoundingClientRect() + ); +}; const TableMenu = (props: TableMenuProps) => { const { editor } = props; From b7c0605060c0d3169db487ba2d5e80943069388e Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Sat, 11 Apr 2026 16:54:35 +0500 Subject: [PATCH 03/15] copilot suggestion & refactor Signed-off-by: hassaansaleem28 --- .../BlockEditor/TableMenu/TableMenu.test.tsx | 307 ++++++++++-------- .../BlockEditor/TableMenu/TableMenu.tsx | 6 +- 2 files changed, 173 insertions(+), 140 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx index 547e27cdc96b..2fac4cc83a43 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx @@ -27,7 +27,6 @@ const mockShow = jest.fn(); const mockHide = jest.fn(); const mockDestroy = jest.fn(); const mockRun = jest.fn(); -let removeSpy: jest.SpyInstance; const createRect = (rect: Partial): DOMRect => { return { @@ -68,17 +67,33 @@ const mockEditor = { chain: mockChain, } as unknown as Editor; -describe('TableMenu', () => { - beforeAll(() => { - removeSpy = jest - .spyOn(HTMLElement.prototype, 'remove') - .mockImplementation(() => undefined); - }); +const renderTableMenu = () => { + const rendered = render(); - afterAll(() => { - removeSpy.mockRestore(); - }); + const safeUnmount = () => { + const calls = (tippy as unknown as jest.Mock).mock.calls; + const latestCall = calls[calls.length - 1]; + const tippyOptions = latestCall?.[1] as + | { + content?: HTMLElement; + } + | undefined; + const menuContent = tippyOptions?.content; + + if (menuContent && !menuContent.isConnected) { + rendered.container.appendChild(menuContent); + } + + rendered.unmount(); + }; + + return { + ...rendered, + safeUnmount, + }; +}; +describe('TableMenu', () => { beforeEach(() => { jest.clearAllMocks(); (tippy as unknown as jest.Mock).mockReturnValue({ @@ -90,152 +105,166 @@ describe('TableMenu', () => { }); it('anchors menu to clicked table cell instead of full table bounds', () => { - render(); + const { safeUnmount } = renderTableMenu(); const tableWrapper = document.createElement('div'); tableWrapper.className = 'tableWrapper'; - const table = document.createElement('table'); - const row = document.createElement('tr'); - const cell = document.createElement('td'); - const content = document.createElement('span'); - - row.appendChild(cell); - cell.appendChild(content); - table.appendChild(row); - tableWrapper.appendChild(table); - document.body.appendChild(tableWrapper); - - const wrapperRect = createRect({ - x: 20, - y: 10, - top: 10, - left: 20, - right: 620, - bottom: 410, - width: 600, - height: 400, - }); - - const cellRect = createRect({ - x: 280, - y: 180, - top: 180, - left: 280, - right: 460, - bottom: 212, - width: 180, - height: 32, - }); - - jest - .spyOn(tableWrapper, 'getBoundingClientRect') - .mockReturnValue(wrapperRect); - jest.spyOn(cell, 'getBoundingClientRect').mockReturnValue(cellRect); - - content.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); - - expect(mockSetProps).toHaveBeenCalledTimes(1); - expect(mockShow).toHaveBeenCalledTimes(1); - - const tippyProps = mockSetProps.mock.calls[0][0] as { - getReferenceClientRect: () => DOMRect; - }; - - expect(tippyProps.getReferenceClientRect()).toEqual(cellRect); - expect(tippyProps.getReferenceClientRect()).not.toEqual(wrapperRect); - - tableWrapper.remove(); + try { + const table = document.createElement('table'); + const row = document.createElement('tr'); + const cell = document.createElement('td'); + const content = document.createElement('span'); + + row.appendChild(cell); + cell.appendChild(content); + table.appendChild(row); + tableWrapper.appendChild(table); + document.body.appendChild(tableWrapper); + + const wrapperRect = createRect({ + x: 20, + y: 10, + top: 10, + left: 20, + right: 620, + bottom: 410, + width: 600, + height: 400, + }); + + const cellRect = createRect({ + x: 280, + y: 180, + top: 180, + left: 280, + right: 460, + bottom: 212, + width: 180, + height: 32, + }); + + jest + .spyOn(tableWrapper, 'getBoundingClientRect') + .mockReturnValue(wrapperRect); + jest.spyOn(cell, 'getBoundingClientRect').mockReturnValue(cellRect); + + content.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); + + expect(mockSetProps).toHaveBeenCalledTimes(1); + expect(mockShow).toHaveBeenCalledTimes(1); + + const tippyProps = mockSetProps.mock.calls[0][0] as { + getReferenceClientRect: () => DOMRect; + }; + + expect(tippyProps.getReferenceClientRect()).toEqual(cellRect); + expect(tippyProps.getReferenceClientRect()).not.toEqual(wrapperRect); + } finally { + tableWrapper.remove(); + safeUnmount(); + } }); it('anchors to selected-cells bounding area when click target is table wrapper', () => { - render(); + const { safeUnmount } = renderTableMenu(); const tableWrapper = document.createElement('div'); tableWrapper.className = 'tableWrapper'; - const firstSelectedCell = document.createElement('td'); - firstSelectedCell.className = 'selectedCell'; - - const secondSelectedCell = document.createElement('td'); - secondSelectedCell.className = 'selectedCell'; - - tableWrapper.appendChild(firstSelectedCell); - tableWrapper.appendChild(secondSelectedCell); - document.body.appendChild(tableWrapper); - - const firstRect = createRect({ - top: 100, - left: 200, - right: 250, - bottom: 140, - width: 50, - height: 40, - }); - - const secondRect = createRect({ - top: 130, - left: 260, - right: 330, - bottom: 170, - width: 70, - height: 40, - }); - - jest - .spyOn(firstSelectedCell, 'getBoundingClientRect') - .mockReturnValue(firstRect); - jest - .spyOn(secondSelectedCell, 'getBoundingClientRect') - .mockReturnValue(secondRect); - - tableWrapper.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); - - const tippyProps = mockSetProps.mock.calls[0][0] as { - getReferenceClientRect: () => DOMRect; - }; - const rect = tippyProps.getReferenceClientRect(); - - expect(rect.top).toBe(100); - expect(rect.left).toBe(200); - expect(rect.right).toBe(330); - expect(rect.bottom).toBe(170); - expect(rect.width).toBe(130); - expect(rect.height).toBe(70); - - tableWrapper.remove(); + try { + const firstSelectedCell = document.createElement('td'); + firstSelectedCell.className = 'selectedCell'; + + const secondSelectedCell = document.createElement('td'); + secondSelectedCell.className = 'selectedCell'; + + tableWrapper.appendChild(firstSelectedCell); + tableWrapper.appendChild(secondSelectedCell); + document.body.appendChild(tableWrapper); + + const firstRect = createRect({ + top: 100, + left: 200, + right: 250, + bottom: 140, + width: 50, + height: 40, + }); + + const secondRect = createRect({ + top: 130, + left: 260, + right: 330, + bottom: 170, + width: 70, + height: 40, + }); + + jest + .spyOn(firstSelectedCell, 'getBoundingClientRect') + .mockReturnValue(firstRect); + jest + .spyOn(secondSelectedCell, 'getBoundingClientRect') + .mockReturnValue(secondRect); + + tableWrapper.dispatchEvent( + new MouseEvent('mousedown', { bubbles: true }) + ); + + const tippyProps = mockSetProps.mock.calls[0][0] as { + getReferenceClientRect: () => DOMRect; + }; + const rect = tippyProps.getReferenceClientRect(); + + expect(rect.top).toBe(100); + expect(rect.left).toBe(200); + expect(rect.right).toBe(330); + expect(rect.bottom).toBe(170); + expect(rect.width).toBe(130); + expect(rect.height).toBe(70); + } finally { + tableWrapper.remove(); + safeUnmount(); + } }); it('falls back to table wrapper bounds when no selected cells exist', () => { - render(); + const { safeUnmount } = renderTableMenu(); const tableWrapper = document.createElement('div'); tableWrapper.className = 'tableWrapper'; - document.body.appendChild(tableWrapper); - - const wrapperRect = createRect({ - x: 32, - y: 48, - top: 48, - left: 32, - right: 672, - bottom: 448, - width: 640, - height: 400, - }); - - jest - .spyOn(tableWrapper, 'getBoundingClientRect') - .mockReturnValue(wrapperRect); - - tableWrapper.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); - - const tippyProps = mockSetProps.mock.calls[0][0] as { - getReferenceClientRect: () => DOMRect; - }; - - expect(tippyProps.getReferenceClientRect()).toEqual(wrapperRect); - tableWrapper.remove(); + try { + document.body.appendChild(tableWrapper); + + const wrapperRect = createRect({ + x: 32, + y: 48, + top: 48, + left: 32, + right: 672, + bottom: 448, + width: 640, + height: 400, + }); + + jest + .spyOn(tableWrapper, 'getBoundingClientRect') + .mockReturnValue(wrapperRect); + + tableWrapper.dispatchEvent( + new MouseEvent('mousedown', { bubbles: true }) + ); + + const tippyProps = mockSetProps.mock.calls[0][0] as { + getReferenceClientRect: () => DOMRect; + }; + + expect(tippyProps.getReferenceClientRect()).toEqual(wrapperRect); + } finally { + tableWrapper.remove(); + safeUnmount(); + } }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index 42a8abccc6e0..cede5fd6d7f2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -153,12 +153,16 @@ const TableMenu = (props: TableMenuProps) => { }, [isEditable]); useEffect(() => { + if (!isEditable) { + return; + } + document.addEventListener('mousedown', handleMouseDown); return () => { document.removeEventListener('mousedown', handleMouseDown); }; - }, [handleMouseDown]); + }, [handleMouseDown, isEditable]); return (
From f6f9e0e84ef53759163ff16aff307bbd8d23a6fc Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Wed, 15 Apr 2026 19:19:28 +0500 Subject: [PATCH 04/15] fix: text Color on selection Signed-off-by: hassaansaleem28 --- .../ui/src/components/BlockEditor/block-editor.less | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less index 5acf75d797a1..f209cbd9ee81 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less @@ -126,7 +126,14 @@ } .tableWrapper *::selection { - color: @text-color; + color: @white; + background-color: @primary-color; + } + + /* FOR Firefox */ + .tableWrapper *::-moz-selection { + color: @white; + background-color: @primary-color; } blockquote { From 168b1df187c0d4fb27ba5a58d249d97fdc3e395d Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 16 Apr 2026 06:30:01 +0500 Subject: [PATCH 05/15] add copiot suggestion Signed-off-by: hassaansaleem28 --- .../ui/src/components/BlockEditor/TableMenu/TableMenu.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index cede5fd6d7f2..968a0b93c123 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -82,7 +82,7 @@ const getSelectedCellsRect = (tableWrapper: Element): DOMRect | null => { return buildRect(top, left, right, bottom); }; -const getReferenceClientRect = (target: Element) => (): DOMRect => { +const getReferenceRect = (target: Element): DOMRect => { const cell = target.closest(TABLE_CELL_SELECTOR); if (cell) { @@ -119,8 +119,10 @@ const TableMenu = (props: TableMenuProps) => { return; } + const referenceRect = getReferenceRect(target); + tableMenuPopup.current?.setProps({ - getReferenceClientRect: getReferenceClientRect(target), + getReferenceClientRect: () => referenceRect, }); tableMenuPopup.current?.show(); From cec2a5a1de46b8af86bca7989c1ad40f733912fe Mon Sep 17 00:00:00 2001 From: Muhammad Hassaan Saleem Date: Thu, 16 Apr 2026 06:34:36 +0500 Subject: [PATCH 06/15] Update openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ui/src/components/BlockEditor/TableMenu/TableMenu.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index 968a0b93c123..a0181d1b5ddd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -119,10 +119,13 @@ const TableMenu = (props: TableMenuProps) => { return; } - const referenceRect = getReferenceRect(target); + const referenceElement = + target.closest(TABLE_CELL_SELECTOR) ?? + target.closest(TABLE_WRAPPER_SELECTOR) ?? + target; tableMenuPopup.current?.setProps({ - getReferenceClientRect: () => referenceRect, + getReferenceClientRect: () => getReferenceRect(referenceElement), }); tableMenuPopup.current?.show(); From 58a91ed18e7d0c5bf69ce33b8a83910da63e03c4 Mon Sep 17 00:00:00 2001 From: Muhammad Hassaan Saleem Date: Thu, 16 Apr 2026 06:39:52 +0500 Subject: [PATCH 07/15] Update openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../ui/src/components/BlockEditor/TableMenu/TableMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index a0181d1b5ddd..a5c50a2b0dba 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -113,9 +113,9 @@ const TableMenu = (props: TableMenuProps) => { return; } - const table = target.closest(TABLE_WRAPPER_SELECTOR); + const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); - if (!table) { + if (!tableWrapper) { return; } From fc37405193407d1be0dea91ff61c103303ec924c Mon Sep 17 00:00:00 2001 From: Muhammad Hassaan Saleem Date: Thu, 16 Apr 2026 06:45:20 +0500 Subject: [PATCH 08/15] Update openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/components/BlockEditor/TableMenu/TableMenu.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index a5c50a2b0dba..c2a6f5fe74a0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -113,19 +113,17 @@ const TableMenu = (props: TableMenuProps) => { return; } - const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); + const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); if (!tableWrapper) { return; } - const referenceElement = - target.closest(TABLE_CELL_SELECTOR) ?? - target.closest(TABLE_WRAPPER_SELECTOR) ?? - target; + const tableCell = target.closest(TABLE_CELL_SELECTOR); + const referenceElement = tableCell ?? tableWrapper ?? target; tableMenuPopup.current?.setProps({ - getReferenceClientRect: () => getReferenceRect(referenceElement), + getReferenceClientRect: () => referenceElement.getBoundingClientRect(), }); tableMenuPopup.current?.show(); From 9cc705752909064eb134f4c5d3b122854888de95 Mon Sep 17 00:00:00 2001 From: Muhammad Hassaan Saleem Date: Thu, 16 Apr 2026 06:45:37 +0500 Subject: [PATCH 09/15] Update openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../src/components/BlockEditor/TableMenu/TableMenu.test.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx index 2fac4cc83a43..67e577d9f25d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx @@ -144,10 +144,8 @@ describe('TableMenu', () => { height: 32, }); - jest - .spyOn(tableWrapper, 'getBoundingClientRect') - .mockReturnValue(wrapperRect); - jest.spyOn(cell, 'getBoundingClientRect').mockReturnValue(cellRect); + tableWrapper.getBoundingClientRect = jest.fn(() => wrapperRect); + cell.getBoundingClientRect = jest.fn(() => cellRect); content.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); From ece04f0c49eca47ea5b4f67d88673ac06bd60404 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 16 Apr 2026 07:27:39 +0500 Subject: [PATCH 10/15] refactor & run lint tests Signed-off-by: hassaansaleem28 --- .../src/main/resources/ui/index.html | 5 ++-- .../BlockEditor/TableMenu/TableMenu.test.tsx | 24 ++++++++++------- .../BlockEditor/TableMenu/TableMenu.tsx | 27 +++++-------------- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/index.html b/openmetadata-ui/src/main/resources/ui/index.html index 22133873d703..cfded796e527 100644 --- a/openmetadata-ui/src/main/resources/ui/index.html +++ b/openmetadata-ui/src/main/resources/ui/index.html @@ -109,13 +109,14 @@ window.dataLayer = window.dataLayer || []; window.dataLayer.push({ 'gtm.start': new Date().getTime(), - event: 'gtm.js' + event: 'gtm.js', }); const gtmScript = document.createElement('script'); gtmScript.async = true; gtmScript.setAttribute('nonce', '${cspNonce}'); - gtmScript.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-554C968W'; + gtmScript.src = + 'https://www.googletagmanager.com/gtm.js?id=GTM-554C968W'; document.head.appendChild(gtmScript); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx index 67e577d9f25d..84e0d614ea49 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx @@ -43,6 +43,13 @@ const createRect = (rect: Partial): DOMRect => { } as DOMRect; }; +const mockBoundingClientRect = (element: Element, rect: DOMRect) => { + Object.defineProperty(element, 'getBoundingClientRect', { + configurable: true, + value: () => rect, + }); +}; + const createChainMethods = () => { return { addRowAfter: jest.fn().mockReturnValue({ run: mockRun }), @@ -144,8 +151,13 @@ describe('TableMenu', () => { height: 32, }); +<<<<<<< HEAD tableWrapper.getBoundingClientRect = jest.fn(() => wrapperRect); cell.getBoundingClientRect = jest.fn(() => cellRect); +======= + mockBoundingClientRect(tableWrapper, wrapperRect); + mockBoundingClientRect(cell, cellRect); +>>>>>>> a8a1ed21e2 (refactor & run lint tests) content.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); @@ -199,12 +211,8 @@ describe('TableMenu', () => { height: 40, }); - jest - .spyOn(firstSelectedCell, 'getBoundingClientRect') - .mockReturnValue(firstRect); - jest - .spyOn(secondSelectedCell, 'getBoundingClientRect') - .mockReturnValue(secondRect); + mockBoundingClientRect(firstSelectedCell, firstRect); + mockBoundingClientRect(secondSelectedCell, secondRect); tableWrapper.dispatchEvent( new MouseEvent('mousedown', { bubbles: true }) @@ -247,9 +255,7 @@ describe('TableMenu', () => { height: 400, }); - jest - .spyOn(tableWrapper, 'getBoundingClientRect') - .mockReturnValue(wrapperRect); + mockBoundingClientRect(tableWrapper, wrapperRect); tableWrapper.dispatchEvent( new MouseEvent('mousedown', { bubbles: true }) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index c2a6f5fe74a0..ac7407b63417 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -82,24 +82,6 @@ const getSelectedCellsRect = (tableWrapper: Element): DOMRect | null => { return buildRect(top, left, right, bottom); }; -const getReferenceRect = (target: Element): DOMRect => { - const cell = target.closest(TABLE_CELL_SELECTOR); - - if (cell) { - return cell.getBoundingClientRect(); - } - - const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); - - if (!tableWrapper) { - return target.getBoundingClientRect(); - } - - return ( - getSelectedCellsRect(tableWrapper) ?? tableWrapper.getBoundingClientRect() - ); -}; - const TableMenu = (props: TableMenuProps) => { const { editor } = props; const { view, isEditable } = editor; @@ -120,10 +102,15 @@ const TableMenu = (props: TableMenuProps) => { } const tableCell = target.closest(TABLE_CELL_SELECTOR); - const referenceElement = tableCell ?? tableWrapper ?? target; + + const getReferenceClientRect = tableCell + ? () => tableCell.getBoundingClientRect() + : () => + getSelectedCellsRect(tableWrapper) ?? + tableWrapper.getBoundingClientRect(); tableMenuPopup.current?.setProps({ - getReferenceClientRect: () => referenceElement.getBoundingClientRect(), + getReferenceClientRect, }); tableMenuPopup.current?.show(); From ed2146186340d534b9b676c7e278101e9cbd68b2 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 16 Apr 2026 07:38:38 +0500 Subject: [PATCH 11/15] fix(ui): resolve merge conflict markers in table menu test --- .../src/components/BlockEditor/TableMenu/TableMenu.test.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx index 84e0d614ea49..ff76383e26b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.test.tsx @@ -151,13 +151,8 @@ describe('TableMenu', () => { height: 32, }); -<<<<<<< HEAD - tableWrapper.getBoundingClientRect = jest.fn(() => wrapperRect); - cell.getBoundingClientRect = jest.fn(() => cellRect); -======= mockBoundingClientRect(tableWrapper, wrapperRect); mockBoundingClientRect(cell, cellRect); ->>>>>>> a8a1ed21e2 (refactor & run lint tests) content.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); From 13465536340f93b3db2f9503c7129af9d24fb21a Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 16 Apr 2026 07:58:00 +0500 Subject: [PATCH 12/15] fix(ui): use TS5-safe closest typing in table menu --- .../ui/src/components/BlockEditor/TableMenu/TableMenu.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index ac7407b63417..36c6d5dda64d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -95,13 +95,17 @@ const TableMenu = (props: TableMenuProps) => { return; } - const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR); + const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR) as + | HTMLElement + | null; if (!tableWrapper) { return; } - const tableCell = target.closest(TABLE_CELL_SELECTOR); + const tableCell = target.closest(TABLE_CELL_SELECTOR) as + | HTMLElement + | null; const getReferenceClientRect = tableCell ? () => tableCell.getBoundingClientRect() From e8ff11638242f8d19a7ff5daf4b60ca99cef85e3 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 16 Apr 2026 08:13:45 +0500 Subject: [PATCH 13/15] run lints tests Signed-off-by: hassaansaleem28 --- .../src/components/BlockEditor/TableMenu/TableMenu.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx index 36c6d5dda64d..5d8a0ae41c79 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/TableMenu/TableMenu.tsx @@ -95,17 +95,15 @@ const TableMenu = (props: TableMenuProps) => { return; } - const tableWrapper = target.closest(TABLE_WRAPPER_SELECTOR) as - | HTMLElement - | null; + const tableWrapper = target.closest( + TABLE_WRAPPER_SELECTOR + ) as HTMLElement | null; if (!tableWrapper) { return; } - const tableCell = target.closest(TABLE_CELL_SELECTOR) as - | HTMLElement - | null; + const tableCell = target.closest(TABLE_CELL_SELECTOR) as HTMLElement | null; const getReferenceClientRect = tableCell ? () => tableCell.getBoundingClientRect() From ddf8a6640d7143d3c5d16893dc449db3e2ca7f70 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 16 Apr 2026 19:11:35 +0500 Subject: [PATCH 14/15] chore(ui): drop unintended ui index.html change from branch --- .../src/main/resources/ui/index.html | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/index.html b/openmetadata-ui/src/main/resources/ui/index.html index cfded796e527..b640bfac291c 100644 --- a/openmetadata-ui/src/main/resources/ui/index.html +++ b/openmetadata-ui/src/main/resources/ui/index.html @@ -26,7 +26,7 @@ - @@ -89,7 +89,7 @@ href="${basePath}favicons/favicon-16x16.png" /> - - From 23ba886ff7280d0b05d11f29dace32379a8234b7 Mon Sep 17 00:00:00 2001 From: hassaansaleem28 Date: Thu, 16 Apr 2026 23:41:04 +0500 Subject: [PATCH 15/15] chore(ui): sync index.html CSP/GTM handling with upstream main --- .../src/main/resources/ui/index.html | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/index.html b/openmetadata-ui/src/main/resources/ui/index.html index b640bfac291c..22133873d703 100644 --- a/openmetadata-ui/src/main/resources/ui/index.html +++ b/openmetadata-ui/src/main/resources/ui/index.html @@ -26,7 +26,7 @@ - @@ -89,7 +89,7 @@ href="${basePath}favicons/favicon-16x16.png" /> - -