mirror of
https://github.com/codex-team/editor.js
synced 2026-03-14 22:55:47 +01:00
chore(toolbar): improve aligning with headings (#2748)
* chore(toolbar): improve aligning with headings * fix eslint * Update index.ts * stash * toolbar aligning improved * improve case 2.1 * close toolbar after conversion * rm submodules change * Update index.html * improve util method * Update index.ts
This commit is contained in:
parent
e631597ee9
commit
1c88d526de
5 changed files with 115 additions and 12 deletions
|
|
@ -27,6 +27,7 @@
|
|||
- `New` – *BlocksAPI* – Exposed `getBlockByElement()` method that helps find block by any child html element
|
||||
– `Fix` — Deleting whitespaces at the start/end of the block
|
||||
– `Improvement` — *Types* — `BlockToolConstructorOptions` type improved, `block` and `config` are not optional anymore
|
||||
- `Improvement` - The Plus button and Block Tunes toggler are now better aligned with large line-height blocks, such as Headings
|
||||
|
||||
### 2.29.1
|
||||
|
||||
|
|
|
|||
|
|
@ -617,3 +617,48 @@ export function isCollapsedWhitespaces(textContent: string): boolean {
|
|||
*/
|
||||
return !/[^\t\n\r ]/.test(textContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the Y coordinate of the text baseline from the top of the element's margin box,
|
||||
*
|
||||
* The calculation formula is as follows:
|
||||
*
|
||||
* 1. Calculate the baseline offset:
|
||||
* - Typically, the baseline is about 80% of the `fontSize` from the top of the text, as this is a common average for many fonts.
|
||||
*
|
||||
* 2. Calculate the additional space due to `lineHeight`:
|
||||
* - If the `lineHeight` is greater than the `fontSize`, the extra space is evenly distributed above and below the text. This extra space is `(lineHeight - fontSize) / 2`.
|
||||
*
|
||||
* 3. Calculate the total baseline Y coordinate:
|
||||
* - Sum of `marginTop`, `borderTopWidth`, `paddingTop`, the extra space due to `lineHeight`, and the baseline offset.
|
||||
*
|
||||
* @param element - The element to calculate the baseline for.
|
||||
* @returns {number} - The Y coordinate of the text baseline from the top of the element's margin box.
|
||||
*/
|
||||
export function calculateBaseline(element: Element): number {
|
||||
const style = window.getComputedStyle(element);
|
||||
const fontSize = parseFloat(style.fontSize);
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
const lineHeight = parseFloat(style.lineHeight) || fontSize * 1.2; // default line-height if not set
|
||||
const paddingTop = parseFloat(style.paddingTop);
|
||||
const borderTopWidth = parseFloat(style.borderTopWidth);
|
||||
const marginTop = parseFloat(style.marginTop);
|
||||
|
||||
/**
|
||||
* Typically, the baseline is about 80% of the `fontSize` from the top of the text, as this is a common average for many fonts.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
const baselineOffset = fontSize * 0.8;
|
||||
|
||||
/**
|
||||
* If the `lineHeight` is greater than the `fontSize`, the extra space is evenly distributed above and below the text. This extra space is `(lineHeight - fontSize) / 2`.
|
||||
*/
|
||||
const extraLineHeight = (lineHeight - fontSize) / 2;
|
||||
|
||||
/**
|
||||
* Calculate the total baseline Y coordinate from the top of the margin box
|
||||
*/
|
||||
const baselineY = marginTop + borderTopWidth + paddingTop + extraLineHeight + baselineOffset;
|
||||
|
||||
return baselineY;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,10 +223,12 @@ export default class BlockSettings extends Module<BlockSettingsNodes> {
|
|||
name: tool.name,
|
||||
closeOnActivate: true,
|
||||
onActivate: async () => {
|
||||
const { BlockManager, Caret } = this.Editor;
|
||||
const { BlockManager, Caret, Toolbar } = this.Editor;
|
||||
|
||||
const newBlock = await BlockManager.convert(currentBlock, tool.name, toolboxItem.data);
|
||||
|
||||
Toolbar.close();
|
||||
|
||||
Caret.setToBlock(newBlock, Caret.positions.END);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Module from '../../__module';
|
||||
import $ from '../../dom';
|
||||
import $, { calculateBaseline } from '../../dom';
|
||||
import * as _ from '../../utils';
|
||||
import I18n from '../../i18n';
|
||||
import { I18nInternalNS } from '../../i18n/namespace-internal';
|
||||
|
|
@ -276,28 +276,83 @@ export default class Toolbar extends Module<ToolbarNodes> {
|
|||
|
||||
const targetBlockHolder = block.holder;
|
||||
const { isMobile } = this.Editor.UI;
|
||||
const renderedContent = block.pluginsContent;
|
||||
const renderedContentStyle = window.getComputedStyle(renderedContent);
|
||||
const blockRenderedElementPaddingTop = parseInt(renderedContentStyle.paddingTop, 10);
|
||||
const blockHeight = targetBlockHolder.offsetHeight;
|
||||
|
||||
let toolbarY;
|
||||
|
||||
/**
|
||||
* 1. Mobile:
|
||||
* - Toolbar at the bottom of the block
|
||||
*
|
||||
* 2. Desktop:
|
||||
* There are two cases of a toolbar position:
|
||||
* 2.1 Toolbar is moved to the top of the block (+ padding top of the block)
|
||||
* - when the first input is far from the top of the block, for example in Image tool
|
||||
* - when block has no inputs
|
||||
* 2.2 Toolbar is moved to the baseline of the first input
|
||||
* - when the first input is close to the top of the block
|
||||
*/
|
||||
let toolbarY;
|
||||
const MAX_OFFSET = 20;
|
||||
|
||||
/**
|
||||
* Compute first input position
|
||||
*/
|
||||
const firstInput = block.firstInput;
|
||||
const targetBlockHolderRect = targetBlockHolder.getBoundingClientRect();
|
||||
const firstInputRect = firstInput !== undefined ? firstInput.getBoundingClientRect() : null;
|
||||
|
||||
/**
|
||||
* Compute the offset of the first input from the top of the block
|
||||
*/
|
||||
const firstInputOffset = firstInputRect !== null ? firstInputRect.top - targetBlockHolderRect.top : null;
|
||||
|
||||
/**
|
||||
* Check if the first input is far from the top of the block
|
||||
*/
|
||||
const isFirstInputFarFromTop = firstInputOffset !== null ? firstInputOffset > MAX_OFFSET : undefined;
|
||||
|
||||
/**
|
||||
* Case 1.
|
||||
* On mobile — Toolbar at the bottom of Block
|
||||
* On Desktop — Toolbar should be moved to the first line of block text
|
||||
* To do that, we compute the block offset and the padding-top of the plugin content
|
||||
*/
|
||||
if (isMobile) {
|
||||
toolbarY = targetBlockHolder.offsetTop + blockHeight;
|
||||
toolbarY = targetBlockHolder.offsetTop + targetBlockHolder.offsetHeight;
|
||||
|
||||
/**
|
||||
* Case 2.1
|
||||
* On Desktop — without inputs or with the first input far from the top of the block
|
||||
* Toolbar should be moved to the top of the block
|
||||
*/
|
||||
} else if (firstInput === undefined || isFirstInputFarFromTop) {
|
||||
const pluginContentOffset = parseInt(window.getComputedStyle(block.pluginsContent).paddingTop);
|
||||
|
||||
const paddingTopBasedY = targetBlockHolder.offsetTop + pluginContentOffset;
|
||||
|
||||
toolbarY = paddingTopBasedY;
|
||||
|
||||
/**
|
||||
* Case 2.2
|
||||
* On Desktop — Toolbar should be moved to the baseline of the first input
|
||||
*/
|
||||
} else {
|
||||
toolbarY = targetBlockHolder.offsetTop + blockRenderedElementPaddingTop;
|
||||
const baseline = calculateBaseline(firstInput);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const toolbarActionsHeight = parseInt(window.getComputedStyle(this.nodes.plusButton!).height, 10);
|
||||
/**
|
||||
* Visual padding inside the SVG icon
|
||||
*/
|
||||
const toolbarActionsPaddingBottom = 8;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const baselineBasedY = targetBlockHolder.offsetTop + baseline - toolbarActionsHeight + toolbarActionsPaddingBottom + firstInputOffset!;
|
||||
|
||||
toolbarY = baselineBasedY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move Toolbar to the Top coordinate of Block
|
||||
*/
|
||||
this.nodes.wrapper.style.top = `${Math.floor(toolbarY)}px`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.nodes.wrapper!.style.top = `${Math.floor(toolbarY)}px`;
|
||||
|
||||
/**
|
||||
* Do not show Block Tunes Toggler near single and empty block
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { BlockTool } from "../../../../types";
|
||||
import { BlockTool } from '../../../../types';
|
||||
|
||||
/**
|
||||
* In the simplest Contentless Tool (eg. Delimiter) there is no data to save
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue