Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WiP introducing ContextualToolbarRepository. #17255

Draft
wants to merge 2 commits into
base: ck/epic/17230-linking-experience
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/ckeditor5-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export { default as ToolbarLineBreakView } from './toolbar/toolbarlinebreakview.
export { default as ToolbarSeparatorView } from './toolbar/toolbarseparatorview.js';
export { default as normalizeToolbarConfig } from './toolbar/normalizetoolbarconfig.js';
export { default as BalloonToolbar, type BalloonToolbarShowEvent } from './toolbar/balloon/balloontoolbar.js';
export { default as ContextualToolbarRepository } from './toolbar/balloon/contextualtoolbarrepository.js';
export { default as BlockToolbar } from './toolbar/block/blocktoolbar.js';

export { default as ViewCollection } from './viewcollection.js';
Expand Down
142 changes: 82 additions & 60 deletions packages/ckeditor5-ui/src/toolbar/balloon/balloontoolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ import {
Observer,
type DocumentSelection,
type DocumentSelectionChangeRangeEvent,
type Schema
type Schema, type ViewDocumentSelection
} from '@ckeditor/ckeditor5-engine';

import { debounce, type DebouncedFunc } from 'lodash-es';
import { ContextualToolbarRepository } from '../../index';

const toPx = /* #__PURE__ */ toUnit( 'px' );

Expand All @@ -53,7 +54,7 @@ export default class BalloonToolbar extends Plugin {
/**
* The toolbar view displayed in the balloon.
*/
public readonly toolbarView: ToolbarView;
public readonly toolbarView: ToolbarView; // TODO is this needed here?

/**
* Tracks the focus of the {@link module:ui/editorui/editorui~EditorUI#getEditableElement editable element}
Expand All @@ -80,7 +81,12 @@ export default class BalloonToolbar extends Plugin {
/**
* The contextual balloon plugin instance.
*/
private readonly _balloon: ContextualBalloon;
// private readonly _balloon: ContextualBalloon; // TODO this should not be needed

/**
* TODO
*/
private _shouldBeVisible: boolean = false;

/**
* Fires `_selectionChangeDebounced` event using `lodash#debounce`.
Expand All @@ -104,7 +110,7 @@ export default class BalloonToolbar extends Plugin {
* @inheritDoc
*/
public static get requires() {
return [ ContextualBalloon ] as const;
return [ ContextualToolbarRepository ] as const;
}

/**
Expand All @@ -121,19 +127,20 @@ export default class BalloonToolbar extends Plugin {
this._trackFocusableEditableElements();
this.focusTracker.add( this.toolbarView.element! );

// Register the toolbar so it becomes available for Alt+F10 and Esc navigation.
editor.ui.addToolbar( this.toolbarView, {
beforeFocus: () => this.show( true ),
afterBlur: () => this.hide(),
isContextual: true
} );
// // TODO this should be handled by toolbar repository
// // Register the toolbar so it becomes available for Alt+F10 and Esc navigation.
// editor.ui.addToolbar( this.toolbarView, {
// beforeFocus: () => this.show( true ),
// afterBlur: () => this.hide(),
// isContextual: true
// } );

this._balloon = editor.plugins.get( ContextualBalloon );
// this._balloon = editor.plugins.get( ContextualBalloon );
this._fireSelectionChangeDebounced = debounce( () => this.fire( '_selectionChangeDebounced' ), 200 );

// The appearance of the BalloonToolbar method is event–driven.
// It is possible to stop the #show event and this prevent the toolbar from showing up.
this.decorate( 'show' );
this.decorate( 'show' ); // TODO remove
}

/**
Expand All @@ -143,16 +150,16 @@ export default class BalloonToolbar extends Plugin {
const editor = this.editor;
const selection = editor.model.document.selection;

// Show/hide the toolbar on editable focus/blur.
this.listenTo<ObservableChangeEvent<boolean>>( this.focusTracker, 'change:isFocused', ( evt, name, isFocused ) => {
const isToolbarVisible = this._balloon.visibleView === this.toolbarView;

if ( !isFocused && isToolbarVisible ) {
this.hide();
} else if ( isFocused ) {
this.show();
}
} );
// // Show/hide the toolbar on editable focus/blur.
// this.listenTo<ObservableChangeEvent<boolean>>( this.focusTracker, 'change:isFocused', ( evt, name, isFocused ) => {
// const isToolbarVisible = this._balloon.visibleView === this.toolbarView;
//
// if ( !isFocused && isToolbarVisible ) {
// this.hide();
// } else if ( isFocused ) {
// this.show();
// }
// } );

// Hide the toolbar when the selection is changed by a direct change or has changed to collapsed.
this.listenTo<DocumentSelectionChangeRangeEvent>( selection, 'change:range', ( evt, data ) => {
Expand Down Expand Up @@ -192,14 +199,15 @@ export default class BalloonToolbar extends Plugin {
// at the right selection in the content again.
// https://github.com/ckeditor/ckeditor5/issues/6444
this.listenTo<ToolbarViewGroupedItemsUpdateEvent>( this.toolbarView, 'groupedItemsUpdate', () => {
this._updatePosition();
// this._updatePosition();
editor.ui.update();
} );

// Creates toolbar components based on given configuration.
// This needs to be done when all plugins are ready.
editor.ui.once<EditorUIReadyEvent>( 'ready', () => {
this.toolbarView.fillFromConfig( this._balloonConfig, this.editor.ui.componentFactory );
} );
// editor.ui.once<EditorUIReadyEvent>( 'ready', () => {
// this.toolbarView.fillFromConfig( this._balloonConfig, this.editor.ui.componentFactory );
// } );
}

/**
Expand All @@ -208,12 +216,23 @@ export default class BalloonToolbar extends Plugin {
private _createToolbarView() {
const t = this.editor.locale.t;
const shouldGroupWhenFull = !this._balloonConfig.shouldNotGroupWhenFull;
const toolbarView = new ToolbarView( this.editor.locale, {
shouldGroupWhenFull,
isFloating: true
const contextualToolbarRepository = this.editor.plugins.get( ContextualToolbarRepository );

const toolbarView = contextualToolbarRepository.register( 'balloon', {
items: this._balloonConfig.items,
removeItems: this._balloonConfig.removeItems,
ariaLabel: t( 'Editor contextual toolbar' ),
toolbarOptions: {
shouldGroupWhenFull,
isFloating: true
},
initializeOnReady: true,
balloonClassName: 'ck-toolbar-container',
getRelatedTarget: () => {
return this._shouldBeVisible ? this._getBalloonPositionData().target : null;
}
} );

toolbarView.ariaLabel = t( 'Editor contextual toolbar' );
toolbarView.render();

return toolbarView;
Expand All @@ -232,10 +251,10 @@ export default class BalloonToolbar extends Plugin {
const selection = editor.model.document.selection;
const schema = editor.model.schema;

// Do not add the toolbar to the balloon stack twice.
if ( this._balloon.hasView( this.toolbarView ) ) {
return;
}
// // Do not add the toolbar to the balloon stack twice.
// if ( this._balloon.hasView( this.toolbarView ) ) {
// return;
// }

// Do not show the toolbar when the selection is collapsed.
if ( selection.isCollapsed && !showForCollapsedSelection ) {
Expand All @@ -244,7 +263,8 @@ export default class BalloonToolbar extends Plugin {

// Do not show the toolbar when there is more than one range in the selection and they fully contain selectable elements.
// See https://github.com/ckeditor/ckeditor5/issues/6443.
if ( selectionContainsOnlyMultipleSelectables( selection, schema ) ) {
// TODO do not show when widget is selected (it is an object in schema so also a selectable)
if ( selectionContainsOnlySelectables( selection, schema ) ) {
return;
}

Expand All @@ -254,27 +274,34 @@ export default class BalloonToolbar extends Plugin {
return;
}

// Update the toolbar position when the editor ui should be refreshed.
this.listenTo<EditorUIUpdateEvent>( this.editor.ui, 'update', () => {
this._updatePosition();
} );

// Add the toolbar to the common editor contextual balloon.
this._balloon.add( {
view: this.toolbarView,
position: this._getBalloonPositionData(),
balloonClassName: 'ck-toolbar-container'
} );
// // Update the toolbar position when the editor ui should be refreshed.
// this.listenTo<EditorUIUpdateEvent>( this.editor.ui, 'update', () => {
// this._updatePosition();
// } );
//
// // Add the toolbar to the common editor contextual balloon.
// this._balloon.add( {
// view: this.toolbarView,
// position: this._getBalloonPositionData(),
// balloonClassName: 'ck-toolbar-container'
// } );

this._shouldBeVisible = true;

editor.ui.update();
}

/**
* Hides the toolbar.
*/
public hide(): void {
if ( this._balloon.hasView( this.toolbarView ) ) {
this.stopListening( this.editor.ui, 'update' );
this._balloon.remove( this.toolbarView );
}
this._shouldBeVisible = false;
this.editor.ui.update();

// if ( this._balloon.hasView( this.toolbarView ) ) {
// this.stopListening( this.editor.ui, 'update' );
// this._balloon.remove( this.toolbarView );
// }
}

/**
Expand Down Expand Up @@ -348,9 +375,9 @@ export default class BalloonToolbar extends Plugin {
* * in the geometry of the selection it is attached to (e.g. the selection moved in the viewport or expanded or shrunk),
* * or the geometry of the balloon toolbar itself (e.g. the toolbar has grouped or ungrouped some items and it is shorter or longer).
*/
private _updatePosition() {
this._balloon.updatePosition( this._getBalloonPositionData() );
}
// private _updatePosition() {
// this._balloon.updatePosition( this._getBalloonPositionData() );
// }

/**
* @inheritDoc
Expand Down Expand Up @@ -414,13 +441,8 @@ export default class BalloonToolbar extends Plugin {
* Returns "true" when the selection has multiple ranges and each range contains a selectable element
* and nothing else.
*/
function selectionContainsOnlyMultipleSelectables( selection: DocumentSelection, schema: Schema ) {
// It doesn't contain multiple objects if there is only one range.
if ( selection.rangeCount === 1 ) {
return false;
}

return [ ...selection.getRanges() ].every( range => {
function selectionContainsOnlySelectables( selection: DocumentSelection, schema: Schema ) {
return Array.from( selection.getRanges() ).every( range => {
const element = range.getContainedElement();

return element && schema.isSelectable( element );
Expand Down
Loading