Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

### 2.32.0

- `Improvement` - Removed "Narrow" ("Thin") mode. On non-mobile viewports, Plus and Block Tunes controls now use a responsive action rail beside the editor content.

### 2.31.7

- `Fix` - Trigger `onChange` for native `<select>` changes
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@editorjs/editorjs",
"version": "2.31.7",
"version": "2.32.0-rc.1",
"description": "Editor.js — open source block-style WYSIWYG editor with JSON output",
"main": "dist/editorjs.umd.js",
"module": "dist/editorjs.mjs",
Expand Down
5 changes: 3 additions & 2 deletions src/components/modules/toolbar/inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,13 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
};

const realRightCoord = newCoords.x + popoverWidth + wrapperOffset.x;
const contentRect = this.Editor.UI.contentRect;

/**
* Prevent InlineToolbar from overflowing the content zone on the right side
*/
if (realRightCoord > this.Editor.UI.contentRect.right) {
newCoords.x = this.Editor.UI.contentRect.right -popoverWidth - wrapperOffset.x;
if (realRightCoord > contentRect.right) {
newCoords.x = contentRect.right -popoverWidth - wrapperOffset.x;
}

this.nodes.wrapper!.style.left = Math.floor(newCoords.x) + 'px';
Expand Down
36 changes: 3 additions & 33 deletions src/components/modules/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,11 @@ export default class UI extends Module<UINodes> {
* @returns {{editorWrapper: string, editorZone: string}}
*/
public get CSS(): {
editorWrapper: string; editorWrapperNarrow: string; editorZone: string; editorZoneHidden: string;
editorWrapper: string; editorZone: string; editorZoneHidden: string;
editorEmpty: string; editorRtlFix: string;
} {
return {
editorWrapper: 'codex-editor',
editorWrapperNarrow: 'codex-editor--narrow',
editorZone: 'codex-editor__redactor',
editorZoneHidden: 'codex-editor__redactor--hidden',
editorEmpty: 'codex-editor--empty',
Expand All @@ -63,15 +62,11 @@ export default class UI extends Module<UINodes> {
}

/**
* Return Width of center column of Editor
* Return the current bounds of the editor's content column
*
* @returns {DOMRect}
*/
public get contentRect(): DOMRect {
if (this.contentRectCache !== null) {
return this.contentRectCache;
}

const someBlock = this.nodes.wrapper.querySelector(`.${Block.CSS.content}`);

/**
Expand All @@ -85,9 +80,7 @@ export default class UI extends Module<UINodes> {
} as DOMRect;
}

this.contentRectCache = someBlock.getBoundingClientRect();

return this.contentRectCache;
return someBlock.getBoundingClientRect();
}

/**
Expand All @@ -97,15 +90,6 @@ export default class UI extends Module<UINodes> {
*/
public isMobile = false;


/**
* Cache for center column rectangle info
* Invalidates on window resize
*
* @type {DOMRect}
*/
private contentRectCache: DOMRect | null = null;

/**
* Handle window resize only when it finished
*
Expand Down Expand Up @@ -290,15 +274,6 @@ export default class UI extends Module<UINodes> {
]);
this.nodes.redactor = $.make('div', this.CSS.editorZone);

/**
* If Editor has injected into the narrow container, enable Narrow Mode
*
* @todo Forced layout. Get rid of this feature
*/
if (this.nodes.holder.offsetWidth < this.contentRect.width) {
this.nodes.wrapper.classList.add(this.CSS.editorWrapperNarrow);
}

/**
* Set customizable bottom zone height
*/
Expand Down Expand Up @@ -460,11 +435,6 @@ export default class UI extends Module<UINodes> {
* Resize window handler
*/
private windowResize(): void {
/**
* Invalidate content zone size cached, because it may be changed
*/
this.contentRectCache = null;

/**
* Detect mobile version
*/
Expand Down
18 changes: 18 additions & 0 deletions src/styles/block.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,31 @@

&--stretched &__content {
max-width: none;

@media (--not-mobile) {
width: max(0px, calc(100% - var(--toolbar-actions-space)));
margin-left: var(--toolbar-actions-space);
}
}

&__content {
position: relative;
max-width: var(--content-width);
margin: 0 auto;
transition: background-color 150ms ease;

@media (--not-mobile) {
width: min(
var(--content-width),
max(0px, calc(100% - var(--toolbar-actions-space)))
);
Comment on lines +52 to +55

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@boyam01 please, take a look

@boyam01 boyam01 Sep 18, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@neSpecc Fixed in ee4c018.

Removed the persistent UI.contentRect cache. The getter now measures the current content bounds on demand, and InlineToolbar.move() reads the result once per positioning operation. No ResizeObserver, holder-width mode, or additional resize listener is introduced.

Added four regressions: LTR and RTL, each shrinking the holder from 700px to 350px and expanding it from 350px to 700px. Each case opens the toolbar first, closes it, changes only the holder width, then reopens it and checks the actual right edge against the current content column. The viewport stays fixed throughout. All four fail against 48ee794 without the fix and pass with it.

Local verification with Node 18.20.1 / Cypress 13.13.3 / headless Electron 118:

  • Focused InlineToolbar + Ui specs: 19/19 passed, retries disabled.
  • Full Cypress suite: 367/367 passed across 40 specs; the existing Flipper test used one configured retry.
  • Source and test lint: no errors; changed test file: no warnings.
  • Test/production builds and git diff --check: passed.

The maintainer's version/changelog changes are preserved. This fixes positioning on reopening; it does not add live repositioning while a toolbar remains open. The new upstream CI run is separate from these local results.

Current upstream CI status: both Cypress and ESLint are action_required with zero jobs started. Could a maintainer approve these workflow runs? They have not run or passed on this new head yet.

max-width: none;
margin-left: max(
var(--toolbar-actions-space),
calc((100% - var(--content-width)) / 2)
);
margin-right: 0;
}
}

&--drop-target &__content {
Expand Down
39 changes: 25 additions & 14 deletions src/styles/rtl.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@
}

&__actions {
right: auto;
left: calc(var(--toolbox-buttons-size) * -1);

@media (--mobile){
right: auto;
left: calc(var(--toolbox-buttons-size) * -1);
margin-left: 0;
margin-right: auto;
padding-right: 0;
Expand Down Expand Up @@ -64,19 +63,31 @@

}

.codex-editor--narrow.codex-editor--rtl {
.ce-toolbar__plus {
@media (--not-mobile) {
left: 0px;
right: 5px;
@media (--not-mobile) {
.codex-editor.codex-editor--rtl {
.ce-block__content,
.ce-toolbar__content {
margin-left: 0;
margin-right: max(
var(--toolbar-actions-space),
calc((100% - var(--content-width)) / 2)
);
}
}

.ce-toolbar__actions {
@media (--not-mobile) {
left: -5px;
.ce-block--stretched .ce-block__content {
margin-right: var(--toolbar-actions-space);
}
}
}

.ce-toolbar__actions {
right: auto;
left: 100%;
padding-right: 0;
padding-left: 5px;
}

.ce-toolbar__settings-btn {
margin-left: 0;
margin-right: 3px;
}
}
}
27 changes: 18 additions & 9 deletions src/styles/toolbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@
max-width: var(--content-width);
margin: 0 auto;
position: relative;

@media (--not-mobile) {
width: min(
var(--content-width),
max(0px, calc(100% - var(--toolbar-actions-space)))
);
max-width: none;
margin-left: max(
var(--toolbar-actions-space),
calc((100% - var(--content-width)) / 2)
);
margin-right: 0;
}
}

&__plus {
Expand Down Expand Up @@ -52,6 +65,11 @@
@media (--mobile){
right: auto;
}

@media (--not-mobile){
width: var(--toolbar-actions-space);
box-sizing: border-box;
}
}

&__settings-btn {
Expand Down Expand Up @@ -83,12 +101,3 @@
}
}
}

/**
* Styles for Narrow mode
*/
.codex-editor--narrow .ce-toolbar__plus {
@media (--not-mobile) {
left: 5px;
}
}
9 changes: 0 additions & 9 deletions src/styles/toolbox.css
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
.ce-toolbox {

}

.codex-editor--narrow .ce-toolbox {
@media (--not-mobile){
.ce-popover {
right: 0;
left: unset;
}
}
}
23 changes: 0 additions & 23 deletions src/styles/ui.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,6 @@
}
}

/**
* Styles for narrow holder
*/
&--narrow &__redactor {
@media (--not-mobile) {
margin-right: var(--narrow-mode-right-padding);
}
}

&--narrow&--rtl &__redactor {
@media (--not-mobile) {
margin-left: var(--narrow-mode-right-padding);
margin-right: 0;
}
}

&--narrow .ce-toolbar__actions {
@media (--not-mobile) {
right: -5px;
}
}

&-copyable {
position: absolute;
height: 1px;
Expand Down Expand Up @@ -91,7 +69,6 @@
}
}


.codex-editor--toolbox-opened [contentEditable=true][data-placeholder]:focus::before {
opacity: 0 !important;
}
Expand Down
4 changes: 2 additions & 2 deletions src/styles/variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
--content-width: 650px;

/**
* In narrow mode, we increase right zone contained Block Actions button
* Width reserved for Block Actions beside editor content
*/
--narrow-mode-right-padding: 50px;
--toolbar-actions-space: 58px;

/**
* Toolbar Plus Button and Toolbox buttons height and width
Expand Down
43 changes: 43 additions & 0 deletions test/cypress/tests/modules/InlineToolbar.cy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,51 @@
import Header from '@editorjs/header';
import NestedEditor, { NESTED_EDITOR_ID } from '../../support/utils/nestedEditorInstance';
import type { MenuConfig } from '@/types/tools';
import { createEditorWithTextBlocks } from '../../support/utils/createEditorWithTextBlocks';

describe('Inline Toolbar', () => {
(['ltr', 'rtl'] as const).forEach((direction) => {
[ [700, 350], [350, 700] ].forEach(([initialWidth, resizedWidth]) => {
it(`should use the current content edge when reopening after a ${direction} holder resizes from ${initialWidth} to ${resizedWidth}`, {
viewportWidth: 1000,
viewportHeight: 800,
}, () => {
createEditorWithTextBlocks([ 'target' ], {
i18n: { direction },
});

// Open before resizing to exercise geometry cached by an earlier selection.
[initialWidth, resizedWidth].forEach((width) => {
cy.document().then((document) => {
document.getSelection().removeAllRanges();
});
cy.get('[data-cy=inline-toolbar] .ce-popover__container')
.should('not.exist');

cy.get('[data-cy=editorjs]')
.invoke('css', 'width', `${width}px`)
.find('.ce-paragraph')
.invoke('css', 'text-align', 'right')
.selectText('target');

cy.get('[data-cy=inline-toolbar] .ce-popover__container')
.should('be.visible')
.should(($toolbar) => {
const document = $toolbar[0].ownerDocument;
const contentRect = document.querySelector('.ce-block__content').getBoundingClientRect();
const toolbarRect = $toolbar[0].getBoundingClientRect();
const selectionRect = document.getSelection().getRangeAt(0)
.getBoundingClientRect();

expect(document.defaultView.innerWidth).to.equal(1000);
expect(selectionRect.left + toolbarRect.width).to.be.greaterThan(contentRect.right);
expect(toolbarRect.right).to.be.closeTo(contentRect.right, 1);
});
});
});
});
});

it('should appear aligned with left coord of selection rect', () => {
cy.createEditor({
data: {
Expand Down
Loading