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

Add search support to dropdown menus #16314

Open
wants to merge 3 commits into
base: ck/16311-core
Choose a base branch
from
Open
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
56 changes: 56 additions & 0 deletions packages/ckeditor5-ui/src/button/buttonlabelwithhighlightview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module ui/button/buttonlabelwithhighlightview
*/

import type ButtonLabel from './buttonlabel.js';
import HighlightedTextView from '../highlightedtext/highlightedtextview.js';

/**
* A button label view that can highlight a text fragment.
*/
export default class ButtonLabelWithHighlightView extends HighlightedTextView implements ButtonLabel {
/**
* @inheritDoc
*/
declare public style: string | undefined;

/**
* @inheritDoc
*/
declare public text: string | undefined;

/**
* @inheritDoc
*/
declare public id: string | undefined;

/**
* @inheritDoc
*/
constructor() {
super();

this.set( {
style: undefined,
text: undefined,
id: undefined
} );

const bind = this.bindTemplate;

this.extendTemplate( {
attributes: {
class: [
'ck-button__label'
],
style: bind.to( 'style' ),
id: bind.to( 'id' )
}
} );
}
}
10 changes: 10 additions & 0 deletions packages/ckeditor5-ui/src/dropdown/menu/dropdownmenulistview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { DropdownMenuViewsRootTree } from './tree/dropdownmenutreetypings.j

import ListView from '../../list/listview.js';
import { createTreeFromDropdownMenuView } from './tree/dropdownmenutreecreateutils.js';
import { walkOverDropdownMenuTreeItems, type DropdownMenuViewsTreeWalkers } from './tree/dropdownmenutreewalker.js';

/**
* Represents a dropdown menu list view.
Expand Down Expand Up @@ -55,4 +56,13 @@ export default class DropdownMenuListView extends ListView {
menuItems: [ ...this.items ]
} );
}

/**
* Walks over the dropdown menu views using the specified walkers.
*
* @param walkers The walkers to use.
*/
public walk( walkers: DropdownMenuViewsTreeWalkers ): void {
walkOverDropdownMenuTreeItems( walkers, this.tree );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { DropdownRootMenuBehaviors } from './utils/dropdownmenubehaviors.js';
import DropdownMenuView from './dropdownmenuview.js';
import DropdownMenuListView from './dropdownmenulistview.js';

import { walkOverDropdownMenuTreeItems } from './tree/dropdownmenutreewalker.js';
import { flattenDropdownMenuTree } from './tree/dropdownmenutreeflattenutils.js';

/**
* Represents the root list view of a dropdown menu.
Expand Down Expand Up @@ -154,13 +154,15 @@ export default class DropdownMenuRootListView extends DropdownMenuListView {
const { tree, _cachedMenus } = this;

if ( !_cachedMenus ) {
this._cachedMenus = [];

walkOverDropdownMenuTreeItems( node => {
const result = flattenDropdownMenuTree( tree ).flatMap( ( { node } ) => {
if ( node.type === 'Menu' ) {
this._cachedMenus!.push( node.menu );
return [ node.menu ];
}
}, tree );

return [];
} );

this._cachedMenus = result;
}

return this._cachedMenus!;
Expand All @@ -175,6 +177,17 @@ export default class DropdownMenuRootListView extends DropdownMenuListView {
} );
}

/**
* Ensures that all menus are preloaded.
*
* * It's helpful used together with some search functions where menu must be preloaded before searching.
*/
public preloadAllMenus(): void {
this.menus.forEach( menuView => {
menuView.isPendingLazyInitialization = false;
} );
}

/**
* Watches the root menu events.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/**
* @module ui/dropdown/menu/filterview/dropdownmenulistfilteredview
*/

import type { Editor } from '@ckeditor/ckeditor5-core';
import type { DropdownMenuDefinition } from '../dropdownmenufactory.js';
import type FilteredView from '../../../search/filteredview.js';

import { filterDropdownMenuTreeByRegExp, type DropdownMenuSearchResult } from '../tree/dropdownmenutreefilterutils.js';

import View from '../../../view.js';
import DropdownMenuListFoundItemsView from './dropdownmenulistfounditemsview.js';
import DropdownMenuRootListView, { type DropdownMenuRootListViewAttributes } from '../dropdownmenurootlistview.js';

/**
* Represents a filtered view for a dropdown menu list.
* This class extends the `View` class and implements the `FilteredView` interface.
*/
export default class DropdownMenuListFilteredView extends View implements FilteredView {
/**
* The root list view of the dropdown menu.
*/
protected _menuView: DropdownMenuRootListView;

/**
* The found list view of the dropdown menu.
*/
protected _foundListView: DropdownMenuListFoundItemsView | null = null;

/**
* Represents a filtered view for the dropdown menu list.
*/
constructor(
editor: Editor,
definitions: Array<DropdownMenuDefinition>,
menuAttributes?: DropdownMenuRootListViewAttributes
) {
super( editor.locale );

this._menuView = new DropdownMenuRootListView( editor, definitions, menuAttributes );
this.setTemplate( {
tag: 'div',

attributes: {
class: [
'ck',
'ck-dropdown-menu-filter'
],
tabindex: -1
},

children: [
this._menuView
]
} );
}

/**
* The root list view of the dropdown menu.
*/
public get menuView(): DropdownMenuRootListView {
return this._menuView;
}

/**
* Gets the found list view of the dropdown menu.
*/
public get foundListView(): DropdownMenuListFoundItemsView | null {
return this._foundListView;
}

/**
* Filters the dropdown menu list based on the provided regular expression.
*
* @param regExp The regular expression to filter the list.
* @returns An object containing the number of filtered results and the total number of items in the list.
*/
public filter( regExp: RegExp | null ): DropdownMenuSearchResult {
const { element } = this;

if ( regExp ) {
// Preload all menus to ensure that all items are available for filtering.
this._menuView.preloadAllMenus();
}

const { filteredTree, resultsCount, totalItemsCount } = filterDropdownMenuTreeByRegExp(
regExp,
this._menuView.tree
);

element!.innerHTML = '';

if ( this._foundListView ) {
this._foundListView.destroy();
this._foundListView = null;
}

if ( resultsCount !== totalItemsCount ) {
this._foundListView = new DropdownMenuListFoundItemsView( this.locale!, filteredTree, {
highlightRegex: regExp,
limitFoundItemsCount: 25
} );

this._menuView.close();
this._foundListView.render();

element!.appendChild( this._foundListView.element! );
} else {
element!.appendChild( this._menuView.element! );
}

return {
filteredTree,
resultsCount,
totalItemsCount
};
}

/**
* Sets the focus on the dropdown menu list.
*/
public focus(): void {
const { _menuView, _foundListView } = this;

if ( _foundListView ) {
_foundListView.focus();
} else {
_menuView.focus();
}
}
}
Loading