diff --git a/patches/@blocknote__core@0.46.2.patch b/patches/@blocknote__core@0.46.2.patch index a81ba7d9..9f70474d 100644 --- a/patches/@blocknote__core@0.46.2.patch +++ b/patches/@blocknote__core@0.46.2.patch @@ -1,8 +1,6 @@ -diff --git a/dist/defaultBlocks-DE5GNdJH.js b/dist/defaultBlocks-DE5GNdJH.js -index b2761001278486a8b2ac1d10c82420b4994e96d9..fbf4f2f450ed1351d64adeb16263ceacf9ee393c 100644 --- a/dist/defaultBlocks-DE5GNdJH.js +++ b/dist/defaultBlocks-DE5GNdJH.js -@@ -2761,7 +2761,7 @@ const B = new Ze("SuggestionMenuPlugin"), _n = k(({ editor: e }) => { +@@ -2761,7 +2761,7 @@ if (a === s) { const c = r.state.doc; for (const l of t) { @@ -11,10 +9,9 @@ index b2761001278486a8b2ac1d10c82420b4994e96d9..fbf4f2f450ed1351d64adeb16263ceac if (l === u) return r.dispatch(r.state.tr.insertText(i)), r.dispatch( r.state.tr.setMeta(B, { -diff --git a/src/editor/managers/ExtensionManager/extensions.ts b/src/editor/managers/ExtensionManager/extensions.ts --- a/src/editor/managers/ExtensionManager/extensions.ts +++ b/src/editor/managers/ExtensionManager/extensions.ts -@@ -84,7 +84,8 @@ export function getDefaultTiptapExtensions( +@@ -84,7 +84,8 @@ }).configure({ defaultProtocol: DEFAULT_LINK_PROTOCOL, // only call this once if we have multiple editors installed. Or fix https://github.com/ueberdosis/tiptap/issues/5450 @@ -24,10 +21,9 @@ diff --git a/src/editor/managers/ExtensionManager/extensions.ts b/src/editor/man }), ...(Object.values(editor.schema.styleSpecs).map((styleSpec) => { return styleSpec.implementation.mark.configure({ -diff --git a/dist/blocknote.js b/dist/blocknote.js --- a/dist/blocknote.js +++ b/dist/blocknote.js -@@ -2037,7 +2037,9 @@ const de = () => { +@@ -2037,7 +2037,9 @@ ] }) ); @@ -38,8 +34,7 @@ diff --git a/dist/blocknote.js b/dist/blocknote.js function mn(o, e) { const t = [ I.ClipboardTextSerializer, -@@ -2063,8 +2065,9 @@ function mn(o, e) { - inclusive: !1 +@@ -2062,7 +2064,8 @@ }).configure({ defaultProtocol: Ct, // only call this once if we have multiple editors installed. Or fix https://github.com/ueberdosis/tiptap/issues/5450 @@ -49,7 +44,7 @@ diff --git a/dist/blocknote.js b/dist/blocknote.js }), ...Object.values(o.schema.styleSpecs).map((n) => n.implementation.mark.configure({ editor: o -@@ -2112,7 +2115,7 @@ function mn(o, e) { +@@ -2112,7 +2115,7 @@ ), Do(o) ]; @@ -58,3 +53,40 @@ diff --git a/dist/blocknote.js b/dist/blocknote.js } function kn(o, e) { const t = [ +--- a/src/extensions/TableHandles/TableHandles.ts ++++ b/src/extensions/TableHandles/TableHandles.ts +@@ -572,9 +572,14 @@ + const tableBody = this.tableElement!.querySelector("tbody"); + + if (!tableBody) { +- throw new Error( +- "Table block does not contain a 'tbody' HTML element. This should never happen.", +- ); ++ this.state.show = false; ++ this.state.showAddOrRemoveRowsButton = false; ++ this.state.showAddOrRemoveColumnsButton = false; ++ this.state.rowIndex = undefined; ++ this.state.colIndex = undefined; ++ this.state.referencePosCell = undefined; ++ this.emitUpdate(); ++ return; + } + + if ( +--- a/dist/TrailingNode-CxM966vN.js ++++ b/dist/TrailingNode-CxM966vN.js +@@ -1746,10 +1746,10 @@ + ); + this.state.rowIndex !== void 0 && this.state.colIndex !== void 0 && (this.state.rowIndex >= e && (this.state.rowIndex = e - 1), this.state.colIndex >= t && (this.state.colIndex = t - 1)); + const o = this.tableElement.querySelector("tbody"); +- if (!o) +- throw new Error( +- "Table block does not contain a 'tbody' HTML element. This should never happen." +- ); ++ if (!o) { ++ this.state.show = !1, this.state.showAddOrRemoveRowsButton = !1, this.state.showAddOrRemoveColumnsButton = !1, this.state.rowIndex = void 0, this.state.colIndex = void 0, this.state.referencePosCell = void 0, this.emitUpdate(); ++ return; ++ } + if (this.state.rowIndex !== void 0 && this.state.colIndex !== void 0) { + const i = o.children[this.state.rowIndex].children[this.state.colIndex]; + i ? this.state.referencePosCell = i.getBoundingClientRect() : (this.state.rowIndex = void 0, this.state.colIndex = void 0); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb574c6f..d71744a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: patchedDependencies: '@blocknote/core@0.46.2': - hash: 04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323 + hash: dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94 path: patches/@blocknote__core@0.46.2.patch '@blocknote/react@0.46.2': hash: e3726d75ed65e07adb1fd48262d6a15f8e52b1cc257177aaf632937830170c97 @@ -27,10 +27,10 @@ importers: version: 0.78.0(zod@4.3.6) '@blocknote/code-block': specifier: ^0.46.2 - version: 0.46.2(@blocknote/core@0.46.2(patch_hash=04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)) + version: 0.46.2(@blocknote/core@0.46.2(patch_hash=dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)) '@blocknote/core': specifier: ^0.46.2 - version: 0.46.2(patch_hash=04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) + version: 0.46.2(patch_hash=dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) '@blocknote/mantine': specifier: ^0.46.2 version: 0.46.2(@floating-ui/dom@1.7.5)(@mantine/core@8.3.14(@mantine/hooks@8.3.14(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.14(react@19.2.4))(@mantine/utils@6.0.22(react@19.2.4))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -4514,9 +4514,9 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@blocknote/code-block@0.46.2(@blocknote/core@0.46.2(patch_hash=04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0))': + '@blocknote/code-block@0.46.2(@blocknote/core@0.46.2(patch_hash=dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0))': dependencies: - '@blocknote/core': 0.46.2(patch_hash=04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) + '@blocknote/core': 0.46.2(patch_hash=dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) '@shikijs/core': 3.23.0 '@shikijs/engine-javascript': 3.23.0 '@shikijs/langs': 3.23.0 @@ -4524,7 +4524,7 @@ snapshots: '@shikijs/themes': 3.23.0 '@shikijs/types': 3.22.0 - '@blocknote/core@0.46.2(patch_hash=04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)': + '@blocknote/core@0.46.2(patch_hash=dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0)': dependencies: '@emoji-mart/data': 1.2.1 '@handlewithcare/prosemirror-inputrules': 0.1.4(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.6) @@ -4576,7 +4576,7 @@ snapshots: '@blocknote/mantine@0.46.2(@floating-ui/dom@1.7.5)(@mantine/core@8.3.14(@mantine/hooks@8.3.14(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.14(react@19.2.4))(@mantine/utils@6.0.22(react@19.2.4))(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@blocknote/core': 0.46.2(patch_hash=04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) + '@blocknote/core': 0.46.2(patch_hash=dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) '@blocknote/react': 0.46.2(patch_hash=e3726d75ed65e07adb1fd48262d6a15f8e52b1cc257177aaf632937830170c97)(@floating-ui/dom@1.7.5)(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@mantine/core': 8.3.14(@mantine/hooks@8.3.14(react@19.2.4))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@mantine/hooks': 8.3.14(react@19.2.4) @@ -4598,7 +4598,7 @@ snapshots: '@blocknote/react@0.46.2(patch_hash=e3726d75ed65e07adb1fd48262d6a15f8e52b1cc257177aaf632937830170c97)(@floating-ui/dom@1.7.5)(@types/hast@3.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@blocknote/core': 0.46.2(patch_hash=04a4b91896adec69aba5d9fedd91f76f7b54aefad70e093cbc0a18f216829323)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) + '@blocknote/core': 0.46.2(patch_hash=dc394d11ab419b36b8d306cbbb6ce39f0722e042b63b2b7d254c7d7f90782e94)(@types/hast@3.0.4)(highlight.js@11.11.1)(lowlight@3.3.0) '@emoji-mart/data': 1.2.1 '@floating-ui/react': 0.27.17(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@floating-ui/utils': 0.2.10 diff --git a/src/lib/blockNoteTableHandles.regression.test.ts b/src/lib/blockNoteTableHandles.regression.test.ts new file mode 100644 index 00000000..15430e21 --- /dev/null +++ b/src/lib/blockNoteTableHandles.regression.test.ts @@ -0,0 +1,63 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { TableHandlesView } from '../../node_modules/@blocknote/core/src/extensions/TableHandles/TableHandles' + +function createTableBlock() { + return { + id: 'table-block', + type: 'table', + content: { + type: 'tableContent', + rows: [ + { cells: ['Head 1', 'Head 2'] }, + { cells: ['A', 'B'] }, + ], + }, + } +} + +describe('BlockNote table handles regression', () => { + afterEach(() => { + document.body.innerHTML = '' + }) + + it('hides stale table handles instead of throwing when tbody is missing during update', () => { + const block = createTableBlock() + const editorRoot = document.createElement('div') + document.body.appendChild(editorRoot) + + const editor = { + getBlock: vi.fn(() => block), + } + const emitUpdate = vi.fn() + + const view = new TableHandlesView( + editor as never, + { + dom: editorRoot, + root: document, + } as never, + emitUpdate, + ) + + view.state = { + block, + show: true, + showAddOrRemoveRowsButton: true, + showAddOrRemoveColumnsButton: true, + rowIndex: 0, + colIndex: 0, + } as never + + const staleTableWrapper = document.createElement('div') + editorRoot.appendChild(staleTableWrapper) + view.tableElement = staleTableWrapper + + expect(() => view.update()).not.toThrow() + expect(view.state?.show).toBe(false) + expect(view.state?.showAddOrRemoveRowsButton).toBe(false) + expect(view.state?.showAddOrRemoveColumnsButton).toBe(false) + expect(emitUpdate).toHaveBeenCalled() + + view.destroy() + }) +}) diff --git a/tests/smoke/table-focus-regression.spec.ts b/tests/smoke/table-focus-regression.spec.ts new file mode 100644 index 00000000..5c184b4d --- /dev/null +++ b/tests/smoke/table-focus-regression.spec.ts @@ -0,0 +1,59 @@ +import { test, expect, type Page } from '@playwright/test' +import { createFixtureVaultCopy, openFixtureVaultTauri, removeFixtureVaultCopy } from '../helpers/fixtureVault' +import { seedBlockNoteTable, triggerMenuCommand } from './testBridge' + +let tempVaultDir: string + +function trackUnexpectedErrors(page: Page): string[] { + const errors: string[] = [] + + page.on('pageerror', (error) => { + errors.push(error.message) + }) + + page.on('console', (message) => { + if (message.type() !== 'error') return + const text = message.text() + if (text.includes('ws://localhost:9711')) return + errors.push(text) + }) + + return errors +} + +async function createUntitledNote(page: Page): Promise { + await triggerMenuCommand(page, 'file-new-note') + await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5_000 }) +} + +test.describe('table focus regression', () => { + test.beforeEach(({ page }, testInfo) => { + void page + testInfo.setTimeout(60_000) + tempVaultDir = createFixtureVaultCopy() + }) + + test.afterEach(() => { + removeFixtureVaultCopy(tempVaultDir) + }) + + test('table headers stay editable after moving focus back to paragraph content', async ({ page }) => { + const errors = trackUnexpectedErrors(page) + const editor = page.getByRole('textbox').last() + + await openFixtureVaultTauri(page, tempVaultDir) + await createUntitledNote(page) + await seedBlockNoteTable(page, [180, 120, 120]) + + await expect(page.locator('table th')).toHaveCount(3, { timeout: 5_000 }) + await page.locator('table th').first().click() + + const trailingParagraph = page.locator('.bn-editor [data-content-type="paragraph"]').last() + await trailingParagraph.click() + await page.keyboard.type('typed after table focus') + + await expect(editor).toContainText('typed after table focus') + await expect(page.locator('table')).toHaveCount(1) + expect(errors).toEqual([]) + }) +})