This commit is contained in:
Tanya Fomina 2024-04-30 19:51:01 +03:00
parent 4daf8f9fa6
commit 53a6cbdce5
11 changed files with 910 additions and 32 deletions

View file

@ -0,0 +1,742 @@
import Module from '../../__module';
import $ from '../../dom';
import SelectionUtils from '../../selection';
import * as _ from '../../utils';
import { InlineTool as IInlineTool } from '../../../../types';
import Flipper from '../../flipper';
import I18n from '../../i18n';
import { I18nInternalNS } from '../../i18n/namespace-internal';
import Shortcuts from '../../utils/shortcuts';
import * as tooltip from '../../utils/tooltip';
import { ModuleConfig } from '../../../types-internal/module-config';
import InlineTool from '../../tools/inline';
import { CommonInternalSettings } from '../../tools/base';
import { Popover, PopoverEvent, PopoverItemParams } from '../../utils/popover';
import { PopoverInline } from '../../utils/popover/popover-inline';
/**
* Inline Toolbar elements
*/
interface InlineToolbarNodes {
wrapper: HTMLElement | undefined;
togglerAndButtonsWrapper: HTMLElement | undefined;
buttons: HTMLElement | undefined;
conversionToggler: HTMLElement | undefined;
conversionTogglerContent: HTMLElement | undefined;
/**
* Zone below the buttons where Tools can create additional actions by 'renderActions()' method
* For example, input for the 'link' tool or textarea for the 'comment' tool
*/
actions: HTMLElement | undefined;
}
/**
* Inline toolbar with actions that modifies selected text fragment
*
* |¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|
* | B i [link] [mark] |
* |________________________|
*/
export default class InlineToolbar extends Module<InlineToolbarNodes> {
/**
* CSS styles
*/
public CSS = {
inlineToolbar: 'ce-inline-toolbar',
inlineToolbarShowed: 'ce-inline-toolbar--showed',
inlineToolbarLeftOriented: 'ce-inline-toolbar--left-oriented',
inlineToolbarRightOriented: 'ce-inline-toolbar--right-oriented',
inlineToolbarShortcut: 'ce-inline-toolbar__shortcut',
buttonsWrapper: 'ce-inline-toolbar__buttons',
actionsWrapper: 'ce-inline-toolbar__actions',
inlineToolButton: 'ce-inline-tool',
inputField: 'cdx-input',
focusedButton: 'ce-inline-tool--focused',
conversionToggler: 'ce-inline-toolbar__dropdown',
conversionTogglerArrow: 'ce-inline-toolbar__dropdown-arrow',
conversionTogglerHidden: 'ce-inline-toolbar__dropdown--hidden',
conversionTogglerContent: 'ce-inline-toolbar__dropdown-content',
togglerAndButtonsWrapper: 'ce-inline-toolbar__toggler-and-button-wrapper',
};
/**
* State of inline toolbar
*
* @type {boolean}
*/
public opened = false;
private popover: Popover | null = null;
/**
* Margin above/below the Toolbar
*/
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
private readonly toolbarVerticalMargin: number = _.isMobileScreen() ? 20 : 6;
/**
* TODO: Get rid of this
*
* Currently visible tools instances
*/
private toolsInstances: Map<string, IInlineTool>;
/**
* Cache for Inline Toolbar width
*
* @type {number}
*/
private width = 0;
private selection = new SelectionUtils();
private actionsOpen = false;
/**
* Instance of class that responses for leafing buttons by arrows/tab
*/
// private flipper: Flipper = null;
/**
* @class
* @param moduleConfiguration - Module Configuration
* @param moduleConfiguration.config - Editor's config
* @param moduleConfiguration.eventsDispatcher - Editor's event dispatcher
*/
constructor({ config, eventsDispatcher }: ModuleConfig) {
super({
config,
eventsDispatcher,
});
}
/**
* Toggles read-only mode
*
* @param {boolean} readOnlyEnabled - read-only mode
*/
public toggleReadOnly(readOnlyEnabled: boolean): void {
if (!readOnlyEnabled) {
window.requestIdleCallback(() => {
this.make();
}, { timeout: 2000 });
} else {
this.destroy();
this.Editor.ConversionToolbar.destroy();
}
}
/**
* Moving / appearance
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
* Shows Inline Toolbar if something is selected
*
* @param [needToClose] - pass true to close toolbar if it is not allowed.
* Avoid to use it just for closing IT, better call .close() clearly.
* @param [needToShowConversionToolbar] - pass false to not to show Conversion Toolbar
*/
public async tryToShow(needToClose = false, needToShowConversionToolbar = true): Promise<void> {
if (this.actionsOpen) {
return;
}
if (needToClose) {
this.close();
}
if (!this.allowedToShow()) {
return;
}
this.move();
this.open(needToShowConversionToolbar);
this.Editor.Toolbar.close();
}
/**
* Hides Inline Toolbar
*/
public close(): void {
debugger;
if (!this.opened || this.doNotClose) {
return;
}
if (this.Editor.ReadOnly.isEnabled) {
return;
}
this.nodes.wrapper.classList.remove(this.CSS.inlineToolbarShowed);
Array.from(this.toolsInstances.entries()).forEach(([name, toolInstance]) => {
const shortcut = this.getToolShortcut(name);
if (shortcut) {
Shortcuts.remove(this.Editor.UI.nodes.redactor, shortcut);
}
/**
* @todo replace 'clear' with 'destroy'
*/
if (_.isFunction(toolInstance.clear)) {
toolInstance.clear();
}
});
this.reset();
this.opened = false;
// this.flipper.deactivate();
this.Editor.ConversionToolbar.close();
if (this.selection.isFakeBackgroundEnabled) {
this.selection.restore();
this.selection.removeFakeBackground();
}
this.popover?.hide();
this.popover?.off(PopoverEvent.OpenNestedPopover, this.onActionsOpen);
this.popover?.off(PopoverEvent.Close, this.onActionsClose);
this.popover?.destroy();
this.popover = null;
}
/**
* Check if node is contained by Inline Toolbar
*
* @param {Node} node node to check
*/
public containsNode(node: Node): boolean {
if (this.nodes.wrapper === undefined) {
return false;
}
return this.nodes.wrapper.contains(node);
}
/**
* Removes UI and its components
*/
public destroy(): void {
/**
* Sometimes (in read-only mode) there is no Flipper
*/
// if (this.flipper) {
// this.flipper.deactivate();
// this.flipper = null;
// }
this.removeAllNodes();
this.popover?.destroy();
}
/**
* Making DOM
*/
private make(): void {
this.nodes.wrapper = $.make('div', [
this.CSS.inlineToolbar,
...(this.isRtl ? [ this.Editor.UI.CSS.editorRtlFix ] : []),
]);
if (import.meta.env.MODE === 'test') {
this.nodes.wrapper.setAttribute('data-cy', 'inline-toolbar');
}
// /**
// * Creates a different wrapper for toggler and buttons.
// */
// this.nodes.togglerAndButtonsWrapper = $.make('div', this.CSS.togglerAndButtonsWrapper);
// this.nodes.buttons = $.make('div', this.CSS.buttonsWrapper);
// this.nodes.actions = $.make('div', this.CSS.actionsWrapper);
// To prevent reset of a selection when click on the wrapper
this.listeners.on(this.nodes.wrapper, 'mousedown', (event) => {
const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`);
// If click is on actions wrapper,
// do not prevent default behavior because actions might include interactive elements
if (!isClickedOnActionsWrapper) {
event.preventDefault();
}
});
/**
* Append the intermediary wrapper which contains toggler and buttons and button actions.
*/
// $.append(this.nodes.wrapper, [this.nodes.togglerAndButtonsWrapper, this.nodes.actions]);
/**
* Append the inline toolbar to the editor.
*/
$.append(this.Editor.UI.nodes.wrapper, this.nodes.wrapper);
/**
* Recalculate initial width with all buttons
* We use RIC to prevent forced layout during editor initialization to make it faster
*/
window.requestAnimationFrame(() => {
this.recalculateWidth();
});
/**
* Allow to leaf buttons by arrows / tab
* Buttons will be filled on opening
*/
// this.enableFlipper();
}
/**
* Shows Inline Toolbar
*/
private open(): void {
if (this.opened) {
return;
}
/**
* Show Inline Toolbar
*/
this.nodes.wrapper.classList.add(this.CSS.inlineToolbarShowed);
this.opened = true;
if (this.popover !== null) {
this.popover.destroy();
}
this.toolsInstances = new Map();
const { htmlElements, popoverItems } = this.getInlineTools();
const container = document.createElement('div');
htmlElements.forEach((element) => container.appendChild(element));
this.popover = new PopoverInline({
items: popoverItems,
customContent: container,
// customContentFlippableItems: this.getControls(customHtmlTunes),
scopeElement: this.Editor.API.methods.ui.nodes.redactor,
messages: {
nothingFound: I18n.ui(I18nInternalNS.ui.popover, 'Nothing found'),
search: I18n.ui(I18nInternalNS.ui.popover, 'Filter'),
},
});
this.popover.on(PopoverEvent.OpenNestedPopover, this.onActionsOpen);
this.popover.on(PopoverEvent.Close, this.onActionsClose);
this.nodes.wrapper?.append(this.popover.getElement());
this.popover.show();
}
private onActionsOpen = () => {
// this.actionsOpen = true;
// this.Editor.UI.disableSelectionChangeEvents();
// this.selection.setFakeBackground();
// this.selection.save();
// this.listeners.on(document, 'click', this.onClick);
// setTimeout(() => {
// this.Editor.UI.enableSelectionChangeEvents();
// }, 200);
};
private onClick = (event) => {
const target = event.target as HTMLElement;
if (!this.nodes.wrapper?.contains(target) && !this.popover?.getElement().contains(target)) {
console.log('clickaway');
// this.selection.restore();
// this.selection.removeFakeBackground();
// this.selection.removeFakeBackground();
// this.selection.restore();
// this.selection.removeFakeBackground();
// this.popover?.cl();
this.close();
// this.actionsOpen = false;
this.listeners.off(document, 'click', this.onClick);
// this.actionsOpen = false;
}
};
private onActionsClose = () => { // / this is not called!!!
console.log('onActionsClose');
// this.Editor.UI.disableSelectionChangeEvents();
// this.selection.restore();
// this.selection.removeFakeBackground();
// this.actionsOpen = false;
// this.Editor.UI.enableSelectionChangeEvents();
};
/**
* Move Toolbar to the selected text
*/
private move(): void {
const selectionRect = SelectionUtils.rect as DOMRect;
const wrapperOffset = this.Editor.UI.nodes.wrapper.getBoundingClientRect();
const newCoords = {
x: selectionRect.x - wrapperOffset.x,
y: selectionRect.y +
selectionRect.height -
// + window.scrollY
wrapperOffset.top +
this.toolbarVerticalMargin,
};
const realRightCoord = newCoords.x + this.width + wrapperOffset.x;
/**
* 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 - this.width - wrapperOffset.x;
}
this.nodes.wrapper.style.left = Math.floor(newCoords.x) + 'px';
this.nodes.wrapper.style.top = Math.floor(newCoords.y) + 'px';
}
/**
* Clear orientation classes and reset position
*/
private reset(): void {
this.nodes.wrapper.classList.remove(
this.CSS.inlineToolbarLeftOriented,
this.CSS.inlineToolbarRightOriented
);
this.nodes.wrapper.style.left = '0';
this.nodes.wrapper.style.top = '0';
}
/**
* Need to show Inline Toolbar or not
*/
private allowedToShow(): boolean {
/**
* Tags conflicts with window.selection function.
* Ex. IMG tag returns null (Firefox) or Redactors wrapper (Chrome)
*/
const tagsConflictsWithSelection = ['IMG', 'INPUT'];
const currentSelection = SelectionUtils.get();
const selectedText = SelectionUtils.text;
// old browsers
if (!currentSelection || !currentSelection.anchorNode) {
return false;
}
// empty selection
if (currentSelection.isCollapsed || selectedText.length < 1) {
return false;
}
const target = !$.isElement(currentSelection.anchorNode)
? currentSelection.anchorNode.parentElement
: currentSelection.anchorNode;
if (currentSelection && tagsConflictsWithSelection.includes(target.tagName)) {
return false;
}
// The selection of the element only in contenteditable
const contenteditable = target.closest('[contenteditable="true"]');
if (contenteditable === null) {
return false;
}
// is enabled by current Block's Tool
const currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode as HTMLElement);
if (!currentBlock) {
return false;
}
return currentBlock.tool.inlineTools.size !== 0;
}
/**
* Recalculate inline toolbar width
*/
private recalculateWidth(): void {
this.width = this.nodes.wrapper.offsetWidth;
}
/**
* Working with Tools
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
/**
*
*/
private getInlineTools() {
const currentSelection = SelectionUtils.get();
const currentBlock = this.Editor.BlockManager.getBlock(currentSelection.anchorNode as HTMLElement);
const inlineTools = Array.from(currentBlock.tool.inlineTools.values());
const popoverItems = [] as PopoverItemParams[];
const htmlElements = [] as HTMLElement[];
inlineTools.forEach(tool => {
const instance = tool.create();
const controlData = instance.render();
this.toolsInstances.set(tool.name, instance);
/** Enable tool shortcut */
const shortcut = this.getToolShortcut(tool.name);
if (shortcut) {
try {
this.enableShortcuts(instance, shortcut);
} catch (e) {}
}
const shortcutBeautified = shortcut !== undefined ? _.beautifyShortcut(shortcut) : undefined;
const toolTitle = I18n.t(
I18nInternalNS.toolNames,
tool.title || _.capitalize(tool.name)
);
if ($.isElement(controlData)) {
htmlElements.push(
this.prepareInlineToolHtml(controlData, instance, toolTitle, shortcutBeautified)
);
} else if (Array.isArray(controlData)) {
popoverItems.push(...controlData.map(item => this.prepareInlineToolItem(item, instance, toolTitle, shortcutBeautified)));
} else {
popoverItems.push(this.prepareInlineToolItem(controlData, instance, toolTitle, shortcutBeautified));
}
// if (_.isFunction(instance.renderActions)) {
// const actions = instance.renderActions();
// // this.nodes.actions.appendChild(actions);
// }
});
return {
popoverItems,
htmlElements,
};
}
/**
*
* @param item
* @param toolInstance
* @param shortcut
* @param toolTitle
*/
private prepareInlineToolItem(item: PopoverItemParams, toolInstance: IInlineTool, toolTitle: string, shortcut: string | undefined): PopoverItemParams {
const result = {
...item,
onActivate: (activatedItem: PopoverItemParams, event?: PointerEvent) => {
// @todo proper check
if ('children' in activatedItem) {
return;
}
this.toolClicked(toolInstance);
},
hint: {
title: toolTitle,
description: shortcut,
},
isActive: toolInstance.checkState(SelectionUtils.get()),
};
if (_.isFunction(toolInstance.renderActions)) {
const actions = toolInstance.renderActions();
result.children = {
customHtml: actions,
};
}
return result;
}
/**
*
* @param html
* @param htmlElement
* @param toolInstance
* @param toolData
* @param toolTitle
* @param shortcut
*/
private prepareInlineToolHtml(htmlElement: HTMLElement, toolInstance: IInlineTool, toolTitle: string, shortcut: string | undefined): HTMLElement {
/** Set click handler */
this.listeners.on(htmlElement, 'click', (event) => {
this.toolClicked(toolInstance);
event.preventDefault();
});
/**
* Enable tooltip module on button
*/
const tooltipContent = $.make('div');
tooltipContent.appendChild($.text(toolTitle));
if (shortcut !== undefined) {
tooltipContent.appendChild($.make('div', this.CSS.inlineToolbarShortcut, {
textContent: _.beautifyShortcut(shortcut),
}));
}
if (_.isMobileScreen() === false ) {
tooltip.onHover(htmlElement, tooltipContent, {
placement: 'top',
hidingDelay: 100,
});
}
toolInstance.checkState(SelectionUtils.get());
return htmlElement;
}
/**
* Get shortcut name for tool
*
* @param toolName Tool name
*/
private getToolShortcut(toolName: string): string | undefined {
const { Tools } = this.Editor;
/**
* Enable shortcuts
* Ignore tool that doesn't have shortcut or empty string
*/
const tool = Tools.inlineTools.get(toolName);
/**
* 1) For internal tools, check public getter 'shortcut'
* 2) For external tools, check tool's settings
* 3) If shortcut is not set in settings, check Tool's public property
*/
const internalTools = Tools.internal.inlineTools;
if (Array.from(internalTools.keys()).includes(toolName)) {
return this.inlineTools[toolName][CommonInternalSettings.Shortcut];
}
return tool?.shortcut;
}
/**
* Enable Tool shortcut with Editor Shortcuts Module
*
* @param {InlineTool} tool - Tool instance
* @param {string} shortcut - shortcut according to the ShortcutData Module format
*/
private enableShortcuts(tool: IInlineTool, shortcut: string): void {
Shortcuts.add({
name: shortcut,
handler: (event) => {
const { currentBlock } = this.Editor.BlockManager;
/**
* Editor is not focused
*/
if (!currentBlock) {
return;
}
/**
* We allow to fire shortcut with empty selection (isCollapsed=true)
* it can be used by tools like «Mention» that works without selection:
* Example: by SHIFT+@ show dropdown and insert selected username
*/
// if (SelectionUtils.isCollapsed) return;
if (!currentBlock.tool.enabledInlineTools) {
return;
}
event.preventDefault();
this.toolClicked(tool);
},
on: this.Editor.UI.nodes.redactor,
});
}
/**
* Inline Tool button clicks
*
* @param {InlineTool} tool - Tool's instance
*/
private toolClicked(tool: IInlineTool): void {
const range = SelectionUtils.range;
tool.surround(range);
this.checkToolsState();
/**
* If tool has "actions", so after click it will probably toggle them on.
* For example, the Inline Link Tool will show the URL-input.
* So we disable the Flipper for that case to allow Tool bind own Enter listener
*/
if (tool.renderActions !== undefined) {
// this.flipper.deactivate();
}
}
/**
* Check Tools` state by selection
*/
private checkToolsState(): void {
this.toolsInstances.forEach((toolInstance) => {
toolInstance.checkState(SelectionUtils.get());
});
}
/**
* Get inline tools tools
* Tools that has isInline is true
*/
private get inlineTools(): { [name: string]: IInlineTool } {
const result = {};
Array
.from(this.Editor.Tools.inlineTools.entries())
.forEach(([name, tool]) => {
result[name] = tool.create();
});
return result;
}
/**
* Allow to leaf buttons by arrows / tab
* Buttons will be filled on opening
*/
private enableFlipper(): void {
// this.flipper = new Flipper({
// focusedItemClass: this.CSS.focusedButton,
// allowedKeys: [
// _.keyCodes.ENTER,
// _.keyCodes.TAB,
// ],
// });
}
}

View file

@ -152,9 +152,31 @@ export class PopoverItemDefault extends PopoverItem {
* Returns list of item children
*/
public get children(): PopoverItemParams[] {
// if (!('children' in this.params)) {
// return [];
// }
return 'children' in this.params && this.params.children?.items !== undefined ? this.params.children.items : [];
}
/**
* Returns list of item children
*/
public get childrenHTML(): HTMLElement | undefined {
if (!('children' in this.params)) {
return undefined;
}
return this.params.children?.customHtml;
}
/**
*
*/
public get hasChildren(): boolean {
return this.children.length > 0 || this.childrenHTML !== undefined;
}
/**
* Constructs HTML element corresponding to popover item params
*
@ -184,7 +206,7 @@ export class PopoverItemDefault extends PopoverItem {
}));
}
if (this.children.length > 0) {
if (this.hasChildren) {
el.appendChild(Dom.make('div', [css.icon, css.iconChevronRight], {
innerHTML: IconChevronRight,
}));

View file

@ -146,6 +146,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
* Clears memory
*/
public destroy(): void {
this.nodes.popover.remove();
this.listeners.removeAll();
}
@ -244,7 +245,7 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
return;
}
if (item.children.length > 0) {
if (item.hasChildren) {
this.showNestedItems(item);
return;

View file

@ -1,7 +1,7 @@
import Flipper from '../../flipper';
import { PopoverAbstract } from './popover-abstract';
import { PopoverItem, css as popoverItemCls } from './components/popover-item';
import { PopoverParams } from './popover.types';
import { PopoverEvent, PopoverParams } from './popover.types';
import { keyCodes } from '../../utils';
import { css } from './popover.const';
import { SearchInputEvent, SearchableItem } from './components/search-input';
@ -163,12 +163,41 @@ export class PopoverDesktop extends PopoverAbstract {
* @param item item to show nested popover for
*/
protected override showNestedItems(item: PopoverItemDefault): void {
this.emit(PopoverEvent.OpenNestedPopover);
if (this.nestedPopover !== null && this.nestedPopover !== undefined) {
return;
}
this.showNestedPopoverForItem(item);
}
/**
* Handles hover events inside popover items container
*
* @param event - hover event data
*/
protected handleHover(event: Event): void {
const item = this.getTargetItem(event);
if (item === undefined) {
return;
}
if (this.previouslyHoveredItem === item) {
return;
}
this.destroyNestedPopoverIfExists();
this.previouslyHoveredItem = item;
if (!item.hasChildren) {
return;
}
this.showNestedPopoverForItem(item);
}
/**
* Additionaly handles input inside search field.
* Updates flipper items considering search query applied.
@ -311,6 +340,7 @@ export class PopoverDesktop extends PopoverAbstract {
*/
private showNestedPopoverForItem(item: PopoverItemDefault): void {
this.nestedPopover = new PopoverDesktop({
customContent: item.childrenHTML,
items: item.children,
nestingLevel: this.nestingLevel + 1,
});
@ -324,35 +354,9 @@ export class PopoverDesktop extends PopoverAbstract {
nestedPopoverEl.style.setProperty('--trigger-item-top', topOffset + 'px');
nestedPopoverEl.style.setProperty('--nesting-level', this.nestedPopover.nestingLevel.toString());
nestedPopoverEl.classList.add(css.getPopoverNestedClass(this.nestedPopover.nestingLevel));
this.nestedPopover.show();
this.flipper.deactivate();
}
/**
* Handles hover events inside popover items container
*
* @param event - hover event data
*/
private handleHover(event: Event): void {
const item = this.getTargetItem(event);
if (item === undefined) {
return;
}
if (this.previouslyHoveredItem === item) {
return;
}
this.destroyNestedPopoverIfExists();
this.previouslyHoveredItem = item;
if (item.children.length === 0) {
return;
}
this.showNestedPopoverForItem(item);
}
}

View file

@ -0,0 +1,28 @@
import { PopoverItem, PopoverItemDefault, PopoverItemParams } from './components/popover-item';
import { PopoverDesktop } from './popover-desktop';
import { css } from './popover.const';
import { PopoverEvent, PopoverParams } from './popover.types';
/**
*
*/
export class PopoverInline extends PopoverDesktop {
/**
*
* @param params
*/
constructor(params: PopoverParams) {
super({
...params,
class: css.popoverInline,
});
}
/**
*
* @param event
*/
protected override handleHover(event: Event): void {
// do nothing
}
}

View file

@ -21,5 +21,7 @@ export const css = {
overlay: className('overlay'),
overlayHidden: className('overlay', 'hidden'),
popoverNested: className(null, 'nested'),
getPopoverNestedClass: (level: number) => className(null, `nested-level-${level.toString()}` ),
popoverInline: className(null, 'inline'),
popoverHeader: className('header'),
};

View file

@ -36,6 +36,7 @@ export interface PopoverParams {
nestingLevel?: number;
}
/**
* Texts used inside popover
*/
@ -54,7 +55,12 @@ export enum PopoverEvent {
/**
* When popover closes
*/
Close = 'close'
Close = 'close',
/**
* When nested popover opens
*/
OpenNestedPopover = 'open-nested-popover'
}
/**
@ -65,6 +71,11 @@ export interface PopoverEventMap {
* Fired when popover closes
*/
[PopoverEvent.Close]: undefined;
/**
* Fired when nested popover opens
*/
[PopoverEvent.OpenNestedPopover]: undefined;
}
/**

View file

@ -102,7 +102,7 @@
box-sizing: border-box;
display: none;
font-weight: 500;
border-top: 1px solid rgba(201,201,204,.48);
/* border-top: 1px solid rgba(201,201,204,.48); */
-webkit-appearance: none;
font-family: inherit;

View file

@ -12,4 +12,5 @@
@import './rtl.css';
@import './input.css';
@import './popover.css';
@import './popover-inline.css';

View file

@ -0,0 +1,66 @@
/**
* Styles overrides for inline popover
*/
.ce-popover--inline {
--height: 32px;
position: relative;
.ce-popover__custom-content {
margin-bottom: 0;
}
.ce-popover__items {
display: flex;
}
.ce-popover__container {
flex-direction: row;
padding: 0;
padding: 0 6px;
height: var(--height);
top: 0;
min-width: max-content;
width: max-content;
animation: none;
}
/**
* Popover item styles
*/
.ce-popover-item-separator {
padding: 4px 3px;
&__line {
height: 100%;
width: 1px;
}
}
.ce-popover-item {
border-radius: 0;
&__icon--tool {
box-shadow: none;
background: transparent;
margin-right: 0;
}
}
.ce-popover-item__icon--chevron-right {
transform: rotate(90deg);
}
.ce-popover--nested-level-1 {
.ce-popover__container {
right: 0;
top: var(--height);
}
}
.ce-popover--nested {
}
}

1
src/tools/paragraph Submodule

@ -0,0 +1 @@
Subproject commit 6e45413ccdfd021f1800eb6e5bf7440184d5ab7c