editor.js/src/components/modules/toolbar/blockSettings.ts
Tatiana Fomina 581289c03e
Block tunes as a popover (#2091)
* Default tunes to popover

* Add the rest of default tunes

* Add popover

* Cleanup

* Rename custom content

* Cleanup

* Add ability to open block settings upwards

* Fix tests

* Cleanup default tunes

* Rename and cleanup

* Add ability to display rendered custom tunes

* cleanup

* Rename

* Add flag to close tunes popover

* Cleanup

* i18n

* Cleanup

* Fix build and tests

* Fix for iframe

* Add comments

* Display active item, move closeOnActivate to popover

* Add confirmation support to popover

* Handle boolean value in confirmation param

* Clarify flippable logic in popover

* Comments

* Pass editor element as a param of popover constructor

* Fix readability

* Tests

* Fix flipper for confirmation element

* Update confirmation config structure

* Rename onClick to onActivate

* Fix tests and build

* Make confirmation props optional

* Simplify processing tunes

* Renamings

* Fix text block tunes

* Docs

* Update event type

* Move enabling confirmation state to separate method

* move popover types

* Unhardcode color

* Support toggling

* Add support of disabled items

* Fix tab in empty block leading to selecting second item in popover

* Remove margins for styles api settings button class

* Fix arrow navigation between blocks after opening block tunes

* Cleaup in default tunes code

* Fix chaining confirmations

* Colors

* Types

* Change the way flippable elements of popover custom area are set

* Remove borders around popover icons

* Fix untabbable inline toolbar

* Fix locked scroll after closing tunes popover on mobile

* Cleanup

* Set max popover width

* Make popover icon's border outside

* Fix tab issue

* Fix focus/hover issue

* Reformat

* Cleanup

* Fix opening block tunes via keyboard

* Add disableSpecialHoverAndFocusBehavior

* Add deprecated comment

* Cleanup

* Fix popover active state

* Fix checklist deletion with confirmation

* Fix checklist deletion 2

* Fix popover focus

* Fix popover items being impossible to flip after searching

* Fix popover item highlighting issue

* Update flipper.spec.ts

* Fixes after review

* Add Tunes Api tests

* Fix multiple popover entries configured by one tune

* Add tool's renderSettings() tests

* Add popover confirmation state test

* Fix popover width on mobile

* Add popover tests

* Add changelog and update version

* Update changelog

* Fix block tunes being unable to open after tune activation

Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
2022-11-03 20:52:33 +03:00

197 lines
5 KiB
TypeScript

import Module from '../../__module';
import $ from '../../dom';
import * as _ from '../../utils';
import SelectionUtils from '../../selection';
import Block from '../../block';
import Popover, { PopoverEvent } from '../../utils/popover';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
import Flipper from '../../flipper';
/**
* HTML Elements that used for BlockSettings
*/
interface BlockSettingsNodes {
wrapper: HTMLElement;
}
/**
* Block Settings
*
* @todo Make Block Settings no-module but a standalone class, like Toolbox
*/
export default class BlockSettings extends Module<BlockSettingsNodes> {
/**
* Module Events
*
* @returns {{opened: string, closed: string}}
*/
public get events(): { opened: string; closed: string } {
return {
opened: 'block-settings-opened',
closed: 'block-settings-closed',
};
}
/**
* Block Settings CSS
*/
public get CSS(): { [name: string]: string } {
return {
settings: 'ce-settings',
};
}
/**
* Opened state
*/
public opened = false;
/**
* Getter for inner popover's flipper instance
*
* @todo remove once BlockSettings becomes standalone non-module class
*/
public get flipper(): Flipper {
return this.popover.flipper;
}
/**
* Page selection utils
*/
private selection: SelectionUtils = new SelectionUtils();
/**
* Popover instance. There is a util for vertical lists.
*/
private popover: Popover;
/**
* Panel with block settings with 2 sections:
* - Tool's Settings
* - Default Settings [Move, Remove, etc]
*/
public make(): void {
this.nodes.wrapper = $.make('div');
}
/**
* Destroys module
*/
public destroy(): void {
this.removeAllNodes();
}
/**
* Open Block Settings pane
*
* @param targetBlock - near which Block we should open BlockSettings
*/
public open(targetBlock: Block = this.Editor.BlockManager.currentBlock): void {
this.opened = true;
/**
* If block settings contains any inputs, focus will be set there,
* so we need to save current selection to restore it after block settings is closed
*/
this.selection.save();
/**
* Highlight content of a Block we are working with
*/
targetBlock.selected = true;
this.Editor.BlockSelection.clearCache();
/**
* Fill Tool's settings
*/
const [tunesItems, customHtmlTunesContainer] = targetBlock.getTunes();
/** Tell to subscribers that block settings is opened */
this.eventsDispatcher.emit(this.events.opened);
this.popover = new Popover({
className: this.CSS.settings,
searchable: true,
filterLabel: I18n.ui(I18nInternalNS.ui.popover, 'Filter'),
nothingFoundLabel: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
items: tunesItems,
customContent: customHtmlTunesContainer,
customContentFlippableItems: this.getControls(customHtmlTunesContainer),
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
});
this.popover.on(PopoverEvent.OverlayClicked, this.onOverlayClicked);
this.popover.on(PopoverEvent.Close, () => this.close());
this.nodes.wrapper.append(this.popover.getElement());
this.popover.show();
}
/**
* Returns root block settings element
*/
public getElement(): HTMLElement {
return this.nodes.wrapper;
}
/**
* Close Block Settings pane
*/
public close(): void {
this.opened = false;
/**
* If selection is at editor on Block Settings closing,
* it means that caret placed at some editable element inside the Block Settings.
* Previously we have saved the selection, then open the Block Settings and set caret to the input
*
* So, we need to restore selection back to Block after closing the Block Settings
*/
if (!SelectionUtils.isAtEditor) {
this.selection.restore();
}
this.selection.clearSaved();
/**
* Remove highlighted content of a Block we are working with
*/
if (!this.Editor.CrossBlockSelection.isCrossBlockSelectionStarted && this.Editor.BlockManager.currentBlock) {
this.Editor.BlockManager.currentBlock.selected = false;
}
/** Tell to subscribers that block settings is closed */
this.eventsDispatcher.emit(this.events.closed);
if (this.popover) {
this.popover.off(PopoverEvent.OverlayClicked, this.onOverlayClicked);
this.popover.destroy();
this.popover.getElement().remove();
this.popover = null;
}
}
/**
* Returns list of buttons and inputs inside specified container
*
* @param container - container to query controls inside of
*/
private getControls(container: HTMLElement): HTMLElement[] {
const { StylesAPI } = this.Editor;
/** Query buttons and inputs inside tunes html */
const controls = container.querySelectorAll<HTMLElement>(
`.${StylesAPI.classes.settingsButton}, ${$.allInputsSelector}`
);
return Array.from(controls);
}
/**
* Handles overlay click
*/
private onOverlayClicked = (): void => {
this.close();
}
}