diff --git a/src/components/modules/ui.ts b/src/components/modules/ui.ts index d8dc37989..603ca9e7f 100644 --- a/src/components/modules/ui.ts +++ b/src/components/modules/ui.ts @@ -49,12 +49,11 @@ export default class UI extends Module { * @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', @@ -290,15 +289,6 @@ export default class UI extends Module { ]); 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 */ diff --git a/src/styles/block.css b/src/styles/block.css index d4288aae6..6921cb52c 100644 --- a/src/styles/block.css +++ b/src/styles/block.css @@ -35,6 +35,11 @@ &--stretched &__content { max-width: none; + + @media (--not-mobile) { + width: max(0px, calc(100% - var(--toolbar-actions-space))); + margin-left: var(--toolbar-actions-space); + } } &__content { @@ -42,6 +47,19 @@ 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))) + ); + max-width: none; + margin-left: max( + var(--toolbar-actions-space), + calc((100% - var(--content-width)) / 2) + ); + margin-right: 0; + } } &--drop-target &__content { diff --git a/src/styles/rtl.css b/src/styles/rtl.css index f65cce74a..060693313 100644 --- a/src/styles/rtl.css +++ b/src/styles/rtl.css @@ -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; @@ -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; + } + } +} diff --git a/src/styles/toolbar.css b/src/styles/toolbar.css index 0358eab5b..1d337893d 100644 --- a/src/styles/toolbar.css +++ b/src/styles/toolbar.css @@ -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 { @@ -52,6 +65,11 @@ @media (--mobile){ right: auto; } + + @media (--not-mobile){ + width: var(--toolbar-actions-space); + box-sizing: border-box; + } } &__settings-btn { @@ -83,12 +101,3 @@ } } } - -/** - * Styles for Narrow mode - */ -.codex-editor--narrow .ce-toolbar__plus { - @media (--not-mobile) { - left: 5px; - } -} diff --git a/src/styles/toolbox.css b/src/styles/toolbox.css index e0c548308..4abea4567 100644 --- a/src/styles/toolbox.css +++ b/src/styles/toolbox.css @@ -1,12 +1,3 @@ .ce-toolbox { } - -.codex-editor--narrow .ce-toolbox { - @media (--not-mobile){ - .ce-popover { - right: 0; - left: unset; - } - } -} diff --git a/src/styles/ui.css b/src/styles/ui.css index d84237b5c..a01f03ad6 100644 --- a/src/styles/ui.css +++ b/src/styles/ui.css @@ -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; @@ -91,7 +69,6 @@ } } - .codex-editor--toolbox-opened [contentEditable=true][data-placeholder]:focus::before { opacity: 0 !important; } diff --git a/src/styles/variables.css b/src/styles/variables.css index 61d8ab2b3..ff2bdc378 100644 --- a/src/styles/variables.css +++ b/src/styles/variables.css @@ -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 diff --git a/test/cypress/tests/modules/Ui.cy.ts b/test/cypress/tests/modules/Ui.cy.ts index eaf2246a8..fbd769aae 100644 --- a/test/cypress/tests/modules/Ui.cy.ts +++ b/test/cypress/tests/modules/Ui.cy.ts @@ -2,6 +2,140 @@ import { createEditorWithTextBlocks } from '../../support/utils/createEditorWith import type EditorJS from '../../../../types/index'; describe('Ui module', function () { + describe('responsive layout', function () { + it('should not read holder width during initialization', function () { + cy.window() + .then(async (window) => { + const holder = window.document.createElement('div'); + let holderWidthRead = false; + + holder.id = 'editorjs'; + Object.defineProperty(holder, 'offsetWidth', { + configurable: true, + get: () => { + holderWidthRead = true; + + return 700; + }, + }); + window.document.body.appendChild(holder); + + const editor = new window.EditorJS({ + holder, + }); + + await editor.isReady; + + expect(holderWidthRead).to.be.false; + + editor.destroy(); + }); + }); + + it('should keep the plus button left of the content in a thin holder on a wide screen', function () { + cy.viewport(1000, 800); + cy.createEditor(); + + cy.get('[data-cy=editorjs]') + .invoke('css', 'width', '500px') + .find('.ce-paragraph') + .click(); + + cy.get('[data-cy=editorjs]') + .then(($holder) => { + const holderRect = $holder[0].getBoundingClientRect(); + const holderContentLeft = holderRect.left + $holder[0].clientLeft; + const plusButtonRect = $holder.find('.ce-toolbar__plus')[0].getBoundingClientRect(); + const blockContentRect = $holder.find('.ce-block__content')[0].getBoundingClientRect(); + + expect(plusButtonRect.width).to.be.greaterThan(0); + expect(plusButtonRect.left).to.equal(holderContentLeft); + expect(plusButtonRect.right).to.be.at.most(blockContentRect.left); + }); + }); + + it('should preserve the mobile toolbar and toolbox layout', function () { + cy.viewport(375, 667); + cy.createEditor(); + + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .click(); + + cy.get('[data-cy=editorjs]') + .then(($holder) => { + const holderRect = $holder[0].getBoundingClientRect(); + const plusButtonRect = $holder.find('.ce-toolbar__plus')[0].getBoundingClientRect(); + + expect(plusButtonRect.left).to.be.at.least(holderRect.left); + expect(plusButtonRect.right).to.be.at.most(holderRect.right); + }) + .find('.ce-toolbar__plus') + .click(); + + cy.get('[data-cy=toolbox]') + .find('.ce-popover__container') + .should('be.visible') + .should(($popover) => { + const popoverRect = $popover[0].getBoundingClientRect(); + const viewport = $popover[0].ownerDocument.defaultView; + + expect(viewport).not.to.be.null; + expect(popoverRect.left).to.be.at.least(0); + expect(popoverRect.right).to.be.at.most(viewport?.innerWidth); + expect(popoverRect.top).to.be.at.least(0); + expect(popoverRect.bottom).to.be.at.most(viewport?.innerHeight); + }); + }); + + it('should mirror the responsive toolbar in a thin RTL holder', function () { + cy.viewport(1000, 800); + cy.createEditor({ + i18n: { + direction: 'rtl', + }, + }); + + cy.get('[data-cy=editorjs]') + .invoke('css', 'width', '500px') + .find('.ce-paragraph') + .click(); + + cy.get('[data-cy=editorjs]') + .then(($holder) => { + const holder = $holder[0]; + const holderRect = holder.getBoundingClientRect(); + const holderRightBorder = holder.offsetWidth - holder.clientWidth - holder.clientLeft; + const holderContentRight = holderRect.right - holderRightBorder; + const plusButtonRect = $holder.find('.ce-toolbar__plus')[0].getBoundingClientRect(); + const blockContentRect = $holder.find('.ce-block__content')[0].getBoundingClientRect(); + + expect(plusButtonRect.right).to.equal(holderContentRight); + expect(plusButtonRect.left).to.be.at.least(blockContentRect.right); + }); + }); + + it('should keep the rectangle selection overlay fixed to the viewport', function () { + cy.viewport(1000, 800); + cy.createEditor(); + + cy.get('[data-cy=editorjs]') + .invoke('css', { + marginLeft: '40px', + width: '500px', + }) + .find('.codex-editor-overlay') + .then(($overlay) => { + const overlayRect = $overlay[0].getBoundingClientRect(); + + expect(overlayRect.left).to.equal(0); + expect(overlayRect.top).to.equal(0); + expect(overlayRect.right).to.equal(1000); + expect(overlayRect.bottom).to.equal(800); + }); + }); + }); + describe('documentKeydown', function () { describe('Backspace', function () { it('should remove selected blocks', function () {