diff --git a/docs/usage.md b/docs/usage.md index f1fb4dd98..dcc734a9b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -71,6 +71,24 @@ var editor = new EditorJS({ ``` +## Align InlineToolbar + +If you want to align the inline toolbar, you can set the option `alignInlineToolbar` to `'left'`, `'center'`, or `'right'`. +The default behavior is `'left'`. + +**Possible values:** +- `'left'`: aligns the toolbar to the left edge of the selection (default) +- `'center'`: centers the toolbar +- `'right'`: aligns the toolbar to the right edge of the selection + +```js +var editor = new EditorJS({ + //... + alignInlineToolbar: 'right', + //... +}); +``` + ## Holder The `holder` property supports an id or a reference to dom element. diff --git a/index.html b/index.html index 6f8ad4bb7..7ac09dbe3 100644 --- a/index.html +++ b/index.html @@ -206,6 +206,11 @@ placeholder: 'Write something or press / to select a tool', autofocus: true, + /** + * Inline toolbar alignment + */ + alignInlineToolbar:"right", + /** * Initial Editor data */ diff --git a/src/components/modules/toolbar/inline.ts b/src/components/modules/toolbar/inline.ts index 5aa5f7dab..a67703843 100644 --- a/src/components/modules/toolbar/inline.ts +++ b/src/components/modules/toolbar/inline.ts @@ -57,6 +57,11 @@ export default class InlineToolbar extends Module { */ private tools: Map = new Map(); + /** + * Inline toolbar alignment + */ + private align: 'left' | 'center' | 'right' = 'left'; + /** * @param moduleConfiguration - Module Configuration * @param moduleConfiguration.config - Editor's config @@ -68,6 +73,9 @@ export default class InlineToolbar extends Module { eventsDispatcher, }); + // Get the value from the config + this.align = config.alignInlineToolbar ?? 'left'; + window.requestIdleCallback(() => { this.make(); }, { timeout: 2000 }); @@ -218,11 +226,27 @@ export default class InlineToolbar extends Module { private move(popoverWidth: number): void { const selectionRect = SelectionUtils.rect as DOMRect; const wrapperOffset = this.Editor.UI.nodes.wrapper.getBoundingClientRect(); + + + let newX: number; + + switch (this.align) { + default: + case 'left': + newX = selectionRect.x - wrapperOffset.x; + break; + case 'right': + newX = selectionRect.x + selectionRect.width - popoverWidth - wrapperOffset.x; + break; + case 'center': + newX = selectionRect.x + selectionRect.width / 2 - popoverWidth / 2 - wrapperOffset.x; + break; + } + const newCoords = { - x: selectionRect.x - wrapperOffset.x, + x: newX, y: selectionRect.y + selectionRect.height - - // + window.scrollY wrapperOffset.top + this.toolbarVerticalMargin, }; @@ -233,7 +257,14 @@ export default class InlineToolbar extends Module { * 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; + newCoords.x = this.Editor.UI.contentRect.right - popoverWidth - wrapperOffset.x; + } + + /** + * Prevent InlineToolbar from overflowing the content zone on the left side + */ + if (newCoords.x < 0) { + newCoords.x = 0; } this.nodes.wrapper!.style.left = Math.floor(newCoords.x) + 'px'; diff --git a/test/cypress/tests/ui/InlineToolbar.cy.ts b/test/cypress/tests/ui/InlineToolbar.cy.ts index 5c337b196..9c8683226 100644 --- a/test/cypress/tests/ui/InlineToolbar.cy.ts +++ b/test/cypress/tests/ui/InlineToolbar.cy.ts @@ -1,6 +1,7 @@ import Header from '@editorjs/header'; import type { InlineTool, MenuConfig } from '../../../../types/tools'; import { createEditorWithTextBlocks } from '../../support/utils/createEditorWithTextBlocks'; +import Paragraph from '@editorjs/paragraph'; describe('Inline Toolbar', () => { describe('Separators', () => { @@ -227,5 +228,90 @@ describe('Inline Toolbar', () => { cy.get('@toolSurround').should('have.been.called'); }); }); + + describe('Default align to left', () => { + it('Should align the InlineToolbar to the left as default', () => { + cy.createEditor({ + tools: { + block: Paragraph, + }, + data: { + blocks: [ + { + type: 'paragraph', + data: { + text: 'Test inline toolbar alignment', + }, + }, + ], + }, + }); + + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .first() + .selectText('inline toolbar'); + + cy.get('[data-cy="inline-toolbar"] .ce-popover__container').should('be.visible'); + + cy.window().then((win) => { + cy.get('[data-cy="inline-toolbar"] .ce-popover__container').then(($toolbar) => { + const toolbarRect = $toolbar[0].getBoundingClientRect(); + const selection = win.getSelection(); + + if (!selection || selection.rangeCount === 0) { + throw new Error('No selection found'); + } + const rangeRect = selection.getRangeAt(0).getBoundingClientRect(); + + // Assert toolbar left is approximately equal to selection left + expect(Math.abs(toolbarRect.left - rangeRect.left)).to.be.lessThan(5); + }); + }); + }); + }); + + describe('Align to rigth', () => { + it('Should align the InlineToolbar to the right', () => { + cy.createEditor({ + alignInlineToolbar: 'right', + tools: { + block: Paragraph, + }, + data: { + blocks: [ + { + type: 'paragraph', + data: { + text: 'Test inline toolbar alignment', + }, + }, + ], + }, + }); + + cy.get('[data-cy=editorjs]') + .find('.ce-paragraph') + .first() + .selectText('inline toolbar'); + + cy.get('[data-cy="inline-toolbar"] .ce-popover__container').should('be.visible'); + + cy.window().then((win) => { + cy.get('[data-cy="inline-toolbar"] .ce-popover__container').then(($toolbar) => { + const toolbarRect = $toolbar[0].getBoundingClientRect(); + const selection = win.getSelection(); + + if (!selection || selection.rangeCount === 0) { + throw new Error('No selection found'); + } + const rangeRect = selection.getRangeAt(0).getBoundingClientRect(); + + // Assert toolbar right is approximately equal to selection right + expect(Math.abs(toolbarRect.right - rangeRect.right)).to.be.lessThan(5); + }); + }); + }); + }); }); diff --git a/types/configs/editor-config.d.ts b/types/configs/editor-config.d.ts index 0f60a3fd2..314139e2d 100644 --- a/types/configs/editor-config.d.ts +++ b/types/configs/editor-config.d.ts @@ -100,6 +100,12 @@ export interface EditorConfig { */ inlineToolbar?: string[]|boolean; + /** + * Inline Toolbar alignment config + */ + alignInlineToolbar?: 'left' | 'center' | 'right'; + + /** * Common Block Tunes list. Will be added to all the blocks which do not specify their own 'tunes' set */