From fb3575cbfffeb3bc76b14b17a5db1e8d96fcb615 Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Fri, 16 Oct 2026 21:07:16 +0100 Subject: [PATCH 1/3] fix(helpers): don't create the tooltip just to hide it tooltip.hide() called prepare(), which constructs the CodeX Tooltips singleton when none exists. Constructing it registers a window scroll listener, and the singleton is only released by an explicit destroy(). ui-kit's popover items call tooltip.hide() from their destroy(), so tearing down a popover whose hints were never shown created a tooltip and left that listener on the page. hide() now does nothing when no tooltip exists. show() and onHover() still create it on first use. Co-Authored-By: Claude Opus 5.5 --- packages/helpers/src/tooltip/tooltip.test.ts | 70 ++++++++++++++++++++ packages/helpers/src/tooltip/tooltip.ts | 6 +- 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 packages/helpers/src/tooltip/tooltip.test.ts diff --git a/packages/helpers/src/tooltip/tooltip.test.ts b/packages/helpers/src/tooltip/tooltip.test.ts new file mode 100644 index 0000000..e71beb2 --- /dev/null +++ b/packages/helpers/src/tooltip/tooltip.test.ts @@ -0,0 +1,70 @@ +import { afterEach, describe, expect, test, vi } from 'vitest'; + +/** + * Stand-in for codex-tooltip: records construction, since constructing it is what registers + * its window-level scroll listener + */ +const { Tooltip } = vi.hoisted(() => ({ + Tooltip: vi.fn(function (this: Record) { + this.show = vi.fn(); + this.hide = vi.fn(); + this.onHover = vi.fn(); + this.destroy = vi.fn(); + }), +})); + +vi.mock('codex-tooltip', () => ({ default: Tooltip })); + +/** + * The parts of a stand-in instance the tests inspect + */ +interface TooltipInstance { + /** + * Recorded hide() calls + */ + hide: ReturnType; +} + +const tooltip = await import('./tooltip'); + +describe('tooltip', () => { + afterEach(() => { + tooltip.destroy(); + Tooltip.mockClear(); + }); + + test('hide() does not create the tooltip when none has been shown', () => { + tooltip.hide(); + tooltip.hide(true); + + expect(Tooltip).not.toHaveBeenCalled(); + }); + + test('hide() hides an existing tooltip', () => { + const element = {} as HTMLElement; + + tooltip.show(element, 'Hint'); + tooltip.hide(true); + + const instance = Tooltip.mock.instances[0] as unknown as TooltipInstance; + + expect(Tooltip).toHaveBeenCalledTimes(1); + expect(instance.hide).toHaveBeenCalledWith(true); + }); + + test('show() creates the tooltip on first use', () => { + tooltip.show(({} as HTMLElement), 'Hint'); + + expect(Tooltip).toHaveBeenCalledTimes(1); + }); + + test('hide() after destroy() does not create a new tooltip', () => { + tooltip.show(({} as HTMLElement), 'Hint'); + tooltip.destroy(); + Tooltip.mockClear(); + + tooltip.hide(); + + expect(Tooltip).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/helpers/src/tooltip/tooltip.ts b/packages/helpers/src/tooltip/tooltip.ts index 34cfbf3..4170e48 100644 --- a/packages/helpers/src/tooltip/tooltip.ts +++ b/packages/helpers/src/tooltip/tooltip.ts @@ -37,11 +37,13 @@ export function show(element: HTMLElement, content: TooltipContent, options?: To /** * Hides tooltip + * + * Does nothing when no tooltip has been created: there is nothing to hide, and creating one here + * would register its window-level listeners on teardown paths (e.g. destroying a popover item) + * where nothing was ever shown * @param skipHidingDelay — pass true to immediately hide the tooltip */ export function hide(skipHidingDelay = false): void { - prepare(); - lib?.hide(skipHidingDelay); } From 880b4f1d9d53b4a9772384c1e49f1e75d4895494 Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Fri, 16 Oct 2026 21:11:43 +0100 Subject: [PATCH 2/3] Bump @editorjs/helpers to 1.2.3 and @editorjs/ui-kit to 2.0.1 ui-kit bundles the helpers tooltip wrapper into its dist, so it needs a release of its own to ship the tooltip hide() fix. Co-Authored-By: Claude Opus 5.5 --- packages/helpers/package.json | 2 +- packages/ui-kit/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/helpers/package.json b/packages/helpers/package.json index 8ff2a48..8ccb3c7 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -3,7 +3,7 @@ "description": "Utils useful for Editor.js tools development", "repository": "https://github.com/editor-js/utils/tree/main/packages/helpers", "link": "https://github.com/editor-js/utils/tree/main/packages/helpers", - "version": "1.2.2", + "version": "1.2.3", "main": "dist/index.js", "type": "module", "license": "MIT", diff --git a/packages/ui-kit/package.json b/packages/ui-kit/package.json index e1374d1..7557f18 100644 --- a/packages/ui-kit/package.json +++ b/packages/ui-kit/package.json @@ -2,7 +2,7 @@ "name": "@editorjs/ui-kit", "description": "UI Components for Editor.js", "packageManager": "yarn@4.4.0", - "version": "2.0.0", + "version": "2.0.1", "type": "module", "main": "dist/index.js", "types": "dist/src/index.d.ts", From d6066106459cc48b7f8a2c57d7e202c9e6da8118 Mon Sep 17 00:00:00 2001 From: George Berezhnoy Date: Mon, 19 Oct 2026 18:19:59 +0100 Subject: [PATCH 3/3] Bump codex-tooltip to 1.1.0 1.1.0 only keeps the tooltip's element and window scroll listener while it is shown. ui-kit bundles the tooltip, so this is what ui-kit 2.0.1 ships with. Co-Authored-By: Claude Opus 5.5 --- packages/helpers/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/helpers/package.json b/packages/helpers/package.json index 8ccb3c7..44bdf50 100644 --- a/packages/helpers/package.json +++ b/packages/helpers/package.json @@ -14,6 +14,6 @@ "access": "public" }, "dependencies": { - "codex-tooltip": "^1.0.6" + "codex-tooltip": "^1.1.0" } } diff --git a/yarn.lock b/yarn.lock index 1d589c8..8a1df9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -738,7 +738,7 @@ __metadata: version: 0.0.0-use.local resolution: "@editorjs/helpers@workspace:packages/helpers" dependencies: - codex-tooltip: "npm:^1.0.6" + codex-tooltip: "npm:^1.1.0" languageName: unknown linkType: soft @@ -2643,10 +2643,10 @@ __metadata: languageName: node linkType: hard -"codex-tooltip@npm:^1.0.6": - version: 1.0.6 - resolution: "codex-tooltip@npm:1.0.6" - checksum: 10c0/ec7488795aafbb004382899b51170190fc13ec2723004355a44e0bc50402325585108b9ef9825a7e6a6dff30e4e9edab8c93a3fe95cb2583e15f89630528676f +"codex-tooltip@npm:^1.1.0": + version: 1.1.0 + resolution: "codex-tooltip@npm:1.1.0" + checksum: 10c0/e9f7904f22a604b39d51d17cb92ea35eaa3e8b3902d1e4b4fb9dc30a39591b1d013bbc4c01a965700569031cbcb78325d82ee831a9ec5060ddfbdb5771772ab2 languageName: node linkType: hard