@ckeditor/ckeditor5-ui
Version:
The UI framework and standard UI library of CKEditor 5.
655 lines (654 loc) • 28.3 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
* @module ui/dropdown/utils
*/
import DropdownPanelView from './dropdownpanelview.js';
import DropdownView from './dropdownview.js';
import DropdownButtonView from './button/dropdownbuttonview.js';
import DropdownMenuRootListView from './menu/dropdownmenurootlistview.js';
import ToolbarView from '../toolbar/toolbarview.js';
import ListView from '../list/listview.js';
import ListItemView from '../list/listitemview.js';
import ListSeparatorView from '../list/listseparatorview.js';
import SplitButtonView from './button/splitbuttonview.js';
import SwitchButtonView from '../button/switchbuttonview.js';
import ViewCollection from '../viewcollection.js';
import clickOutsideHandler from '../bindings/clickoutsidehandler.js';
import { global, priorities, logWarning } from '@ckeditor/ckeditor5-utils';
import '../../theme/components/dropdown/toolbardropdown.css';
import '../../theme/components/dropdown/listdropdown.css';
import ListItemGroupView from '../list/listitemgroupview.js';
import ListItemButtonView from '../button/listitembuttonview.js';
/**
* A helper for creating dropdowns. It creates an instance of a {@link module:ui/dropdown/dropdownview~DropdownView dropdown},
* with a {@link module:ui/dropdown/button/dropdownbutton~DropdownButton button},
* {@link module:ui/dropdown/dropdownpanelview~DropdownPanelView panel} and all standard dropdown's behaviors.
*
* # Creating dropdowns
*
* By default, the default {@link module:ui/dropdown/button/dropdownbuttonview~DropdownButtonView} class is used as
* definition of the button:
*
* ```ts
* const dropdown = createDropdown( model );
*
* // Configure dropdown's button properties:
* dropdown.buttonView.set( {
* label: 'A dropdown',
* withText: true
* } );
*
* dropdown.render();
*
* // Will render a dropdown labeled "A dropdown" with an empty panel.
* document.body.appendChild( dropdown.element );
* ```
*
* You can also provide other button views (they need to implement the
* {@link module:ui/dropdown/button/dropdownbutton~DropdownButton} interface). For instance, you can use
* {@link module:ui/dropdown/button/splitbuttonview~SplitButtonView} to create a dropdown with a split button.
*
* ```ts
* const dropdown = createDropdown( locale, SplitButtonView );
*
* // Configure dropdown's button properties:
* dropdown.buttonView.set( {
* label: 'A dropdown',
* withText: true
* } );
*
* dropdown.buttonView.on( 'execute', () => {
* // Add the behavior of the "action part" of the split button.
* // Split button consists of the "action part" and "arrow part".
* // The arrow opens the dropdown while the action part can have some other behavior.
* } );
*
* dropdown.render();
*
* // Will render a dropdown labeled "A dropdown" with an empty panel.
* document.body.appendChild( dropdown.element );
* ```
*
* # Adding content to the dropdown's panel
*
* The content of the panel can be inserted directly into the `dropdown.panelView.element`:
*
* ```ts
* dropdown.panelView.element.textContent = 'Content of the panel';
* ```
*
* However, most of the time you will want to add there either a {@link module:ui/list/listview~ListView list of options}
* or a list of buttons (i.e. a {@link module:ui/toolbar/toolbarview~ToolbarView toolbar}).
* To simplify the task, you can use, respectively, {@link module:ui/dropdown/utils~addListToDropdown} or
* {@link module:ui/dropdown/utils~addToolbarToDropdown} utils.
*
* @param locale The locale instance.
* @param ButtonClassOrInstance The dropdown button view class. Needs to implement the
* {@link module:ui/dropdown/button/dropdownbutton~DropdownButton} interface.
* @returns The dropdown view instance.
*/
export function createDropdown(locale, ButtonClassOrInstance = DropdownButtonView) {
const buttonView = typeof ButtonClassOrInstance == 'function' ? new ButtonClassOrInstance(locale) : ButtonClassOrInstance;
const panelView = new DropdownPanelView(locale);
const dropdownView = new DropdownView(locale, buttonView, panelView);
buttonView.bind('isEnabled').to(dropdownView);
if (buttonView instanceof SplitButtonView) {
buttonView.arrowView.bind('isOn').to(dropdownView, 'isOpen');
}
else {
buttonView.bind('isOn').to(dropdownView, 'isOpen');
}
addDefaultBehaviors(dropdownView);
return dropdownView;
}
/**
* Adds a menu UI component to a dropdown and sets all common behaviors and interactions between the dropdown and the menu.
*
* Use this helper to create multi-level dropdown menus that are displayed in a toolbar.
*
* Internally, it creates an instance of {@link module:ui/dropdown/menu/dropdownmenurootlistview~DropdownMenuRootListView}.
*
* Example:
*
* ```ts
* const definitions = [
* {
* id: 'menu_1',
* menu: 'Menu 1',
* children: [
* {
* id: 'menu_1_a',
* label: 'Item A'
* },
* {
* id: 'menu_1_b',
* label: 'Item B'
* }
* ]
* },
* {
* id: 'top_a',
* label: 'Top Item A'
* },
* {
* id: 'top_b',
* label: 'Top Item B'
* }
* ];
*
* const dropdownView = createDropdown( editor.locale );
*
* addMenuToDropdown( dropdownView, editor.ui.view.body, definitions );
* ```
*
* After using this helper, the `dropdown` will fire {@link module:ui/dropdown/dropdownview~DropdownViewEvent `execute`} event when
* a nested menu button is pressed.
*
* The helper will make sure that the `dropdownMenuRootListView` is lazy loaded, i.e., the menu component structure will be initialized
* and rendered only after the `dropdown` is opened for the first time.
*
* @param dropdownView A dropdown instance to which the menu component will be added.
* @param body Body collection to which floating menu panels will be added.
* @param definition The menu component definition.
* @param options.ariaLabel Label used by assistive technologies to describe the top-level menu.
*/
export function addMenuToDropdown(dropdownView, body, definition, options = {}) {
dropdownView.menuView = new DropdownMenuRootListView(dropdownView.locale, body, definition);
dropdownView.focusTracker.add(dropdownView.menuView);
if (dropdownView.isOpen) {
addMenuToOpenDropdown(dropdownView, options);
}
else {
// Load the UI elements only after the dropdown is opened for the first time - lazy loading.
dropdownView.once('change:isOpen', () => {
addMenuToOpenDropdown(dropdownView, options);
}, { priority: 'highest' });
}
}
function addMenuToOpenDropdown(dropdownView, options) {
const dropdownMenuRootListView = dropdownView.menuView;
const t = dropdownView.locale.t;
dropdownMenuRootListView.delegate('menu:execute').to(dropdownView, 'execute');
dropdownMenuRootListView.listenTo(dropdownView, 'change:isOpen', (evt, name, isOpen) => {
if (!isOpen) {
dropdownMenuRootListView.closeMenus();
}
}, { priority: 'low' }); // Make sure this is fired after `focusDropdownButtonOnClose` behavior.
// When `dropdownMenuRootListView` is added as a `panelView` child, it becomes rendered (`panelView` is rendered at this point).
dropdownView.panelView.children.add(dropdownMenuRootListView);
// Nested menu panels are added to body collection, so they are not children of the `dropdownView` from DOM perspective.
// Add these panels to `dropdownView` focus tracker, so they are treated like part of the `dropdownView` for focus-related purposes.
for (const menu of dropdownMenuRootListView.menus) {
dropdownView.focusTracker.add(menu);
}
dropdownMenuRootListView.ariaLabel = options.ariaLabel || t('Dropdown menu');
}
/**
* Adds an instance of {@link module:ui/toolbar/toolbarview~ToolbarView} to a dropdown.
*
* ```ts
* const buttonsCreator = () => {
* const buttons = [];
*
* // Either create a new ButtonView instance or create existing.
* buttons.push( new ButtonView() );
* buttons.push( editor.ui.componentFactory.create( 'someButton' ) );
* };
*
* const dropdown = createDropdown( locale );
*
* addToolbarToDropdown( dropdown, buttonsCreator, { isVertical: true } );
*
* // Will render a vertical button dropdown labeled "A button dropdown"
* // with a button group in the panel containing two buttons.
* // Buttons inside the dropdown will be created on first dropdown panel open.
* dropdown.render()
* document.body.appendChild( dropdown.element );
* ```
*
* **Note:** To improve the accessibility, you can tell the dropdown to focus the first active button of the toolbar when the dropdown
* {@link module:ui/dropdown/dropdownview~DropdownView#isOpen gets open}. See the documentation of `options` to learn more.
*
* **Note:** Toolbar view will be created on first open of the dropdown.
*
* See {@link module:ui/dropdown/utils~createDropdown} and {@link module:ui/toolbar/toolbarview~ToolbarView}.
*
* @param dropdownView A dropdown instance to which `ToolbarView` will be added.
* @param options.enableActiveItemFocusOnDropdownOpen When set `true`, the focus will automatically move to the first
* active {@link module:ui/toolbar/toolbarview~ToolbarView#items item} of the toolbar upon
* {@link module:ui/dropdown/dropdownview~DropdownView#isOpen opening} the dropdown. Active items are those with the `isOn` property set
* `true` (for instance {@link module:ui/button/buttonview~ButtonView buttons}). If no active items is found, the toolbar will be focused
* as a whole resulting in the focus moving to its first focusable item (default behavior of
* {@link module:ui/dropdown/dropdownview~DropdownView}).
* @param options.ariaLabel Label used by assistive technologies to describe toolbar element.
* @param options.maxWidth The maximum width of the toolbar element.
* Details: {@link module:ui/toolbar/toolbarview~ToolbarView#maxWidth}.
* @param options.class An additional CSS class added to the toolbar element.
* @param options.isCompact When set true, makes the toolbar look compact with toolbar element.
* @param options.isVertical Controls the orientation of toolbar items.
*/
export function addToolbarToDropdown(dropdownView, buttonsOrCallback, options = {}) {
dropdownView.extendTemplate({
attributes: {
class: ['ck-toolbar-dropdown']
}
});
if (dropdownView.isOpen) {
addToolbarToOpenDropdown(dropdownView, buttonsOrCallback, options);
}
else {
dropdownView.once('change:isOpen', () => addToolbarToOpenDropdown(dropdownView, buttonsOrCallback, options), { priority: 'highest' });
}
if (options.enableActiveItemFocusOnDropdownOpen) {
// Accessibility: Focus the first active button in the toolbar when the dropdown gets open.
focusChildOnDropdownOpen(dropdownView, () => dropdownView.toolbarView.items.find((item) => item.isOn));
}
}
/**
* Adds an instance of {@link module:ui/toolbar/toolbarview~ToolbarView} to a dropdown.
*/
function addToolbarToOpenDropdown(dropdownView, buttonsOrCallback, options) {
const locale = dropdownView.locale;
const t = locale.t;
const toolbarView = dropdownView.toolbarView = new ToolbarView(locale);
const buttons = typeof buttonsOrCallback == 'function' ? buttonsOrCallback() : buttonsOrCallback;
toolbarView.ariaLabel = options.ariaLabel || t('Dropdown toolbar');
if (options.maxWidth) {
toolbarView.maxWidth = options.maxWidth;
}
if (options.class) {
toolbarView.class = options.class;
}
if (options.isCompact) {
toolbarView.isCompact = options.isCompact;
}
if (options.isVertical) {
toolbarView.isVertical = true;
}
if (buttons instanceof ViewCollection) {
toolbarView.items.bindTo(buttons).using(item => item);
}
else {
toolbarView.items.addMany(buttons);
}
dropdownView.panelView.children.add(toolbarView);
dropdownView.focusTracker.add(toolbarView);
toolbarView.items.delegate('execute').to(dropdownView);
}
/**
* Adds an instance of {@link module:ui/list/listview~ListView} to a dropdown.
*
* ```ts
* const items = new Collection<ListDropdownItemDefinition>();
*
* items.add( {
* type: 'button',
* model: new Model( {
* withText: true,
* label: 'First item',
* labelStyle: 'color: red'
* } )
* } );
*
* items.add( {
* type: 'button',
* model: new Model( {
* withText: true,
* label: 'Second item',
* labelStyle: 'color: green',
* class: 'foo'
* } )
* } );
*
* const dropdown = createDropdown( locale );
*
* addListToDropdown( dropdown, items );
*
* // Will render a dropdown with a list in the panel containing two items.
* dropdown.render()
* document.body.appendChild( dropdown.element );
* ```
*
* The `items` collection passed to this methods controls the presence and attributes of respective
* {@link module:ui/list/listitemview~ListItemView list items}.
*
* **Note:** To improve the accessibility, when a list is added to the dropdown using this helper the dropdown will automatically attempt
* to focus the first active item (a host to a {@link module:ui/button/buttonview~ButtonView} with
* {@link module:ui/button/buttonview~ButtonView#isOn} set `true`) or the very first item when none are active.
*
* **Note:** List view will be created on first open of the dropdown.
*
* See {@link module:ui/dropdown/utils~createDropdown} and {@link module:list/list~List}.
*
* @param dropdownView A dropdown instance to which `ListVIew` will be added.
* @param itemsOrCallback A collection of the list item definitions or a callback returning a list item definitions to populate the list.
* @param options.ariaLabel Label used by assistive technologies to describe list element.
* @param options.role Will be reflected by the `role` DOM attribute in `ListVIew` and used by assistive technologies.
*/
export function addListToDropdown(dropdownView, itemsOrCallback, options = {}) {
if (dropdownView.isOpen) {
addListToOpenDropdown(dropdownView, itemsOrCallback, options);
}
else {
dropdownView.once('change:isOpen', () => addListToOpenDropdown(dropdownView, itemsOrCallback, options), { priority: 'highest' });
}
// Accessibility: Focus the first active button in the list when the dropdown gets open.
focusChildOnDropdownOpen(dropdownView, () => dropdownView.listView.items.find(item => {
if (item instanceof ListItemView) {
return item.children.first.isOn;
}
return false;
}));
}
/**
* Adds an instance of {@link module:ui/list/listview~ListView} to a dropdown.
*/
function addListToOpenDropdown(dropdownView, itemsOrCallback, options) {
const locale = dropdownView.locale;
const listView = dropdownView.listView = new ListView(locale);
const items = typeof itemsOrCallback == 'function' ? itemsOrCallback() : itemsOrCallback;
listView.ariaLabel = options.ariaLabel;
listView.role = options.role;
bindViewCollectionItemsToDefinitions(dropdownView, listView.items, items, locale);
dropdownView.panelView.children.add(listView);
listView.items.delegate('execute').to(dropdownView);
}
/**
* A helper to be used on an existing {@link module:ui/dropdown/dropdownview~DropdownView} that focuses
* a specific child in DOM when the dropdown {@link module:ui/dropdown/dropdownview~DropdownView#isOpen gets open}.
*
* @param dropdownView A dropdown instance to which the focus behavior will be added.
* @param childSelectorCallback A callback executed when the dropdown gets open. It should return a {@link module:ui/view~View}
* instance (child of {@link module:ui/dropdown/dropdownview~DropdownView#panelView}) that will get focused or a falsy value.
* If falsy value is returned, a default behavior of the dropdown will engage focusing the first focusable child in
* the {@link module:ui/dropdown/dropdownview~DropdownView#panelView}.
*/
export function focusChildOnDropdownOpen(dropdownView, childSelectorCallback) {
dropdownView.on('change:isOpen', () => {
if (!dropdownView.isOpen) {
return;
}
const childToFocus = childSelectorCallback();
if (!childToFocus) {
return;
}
if (typeof childToFocus.focus === 'function') {
childToFocus.focus();
}
else {
/**
* The child view of a {@link module:ui/dropdown/dropdownview~DropdownView dropdown} is missing the `focus()` method
* and could not be focused when the dropdown got {@link module:ui/dropdown/dropdownview~DropdownView#isOpen open}.
*
* Making the content of a dropdown focusable in this case greatly improves the accessibility. Please make the view instance
* implements the {@link module:ui/dropdown/dropdownpanelfocusable~DropdownPanelFocusable focusable interface} for the best user
* experience.
*
* @error ui-dropdown-focus-child-on-open-child-missing-focus
* @param {module:ui/view~View} view Child to focus.
*/
logWarning('ui-dropdown-focus-child-on-open-child-missing-focus', { view: childToFocus });
}
// * Let the panel show up first (do not focus an invisible element).
// * Execute after focusDropdownPanelOnOpen(). See focusDropdownPanelOnOpen() to learn more.
}, { priority: priorities.low - 10 });
}
/**
* Add a set of default behaviors to dropdown view.
*/
function addDefaultBehaviors(dropdownView) {
closeDropdownOnClickOutside(dropdownView);
closeDropdownOnExecute(dropdownView);
closeDropdownOnBlur(dropdownView);
focusDropdownContentsOnArrows(dropdownView);
focusDropdownButtonOnClose(dropdownView);
focusDropdownPanelOnOpen(dropdownView);
}
/**
* Adds a behavior to a dropdownView that closes opened dropdown when user clicks outside the dropdown.
*/
function closeDropdownOnClickOutside(dropdownView) {
clickOutsideHandler({
emitter: dropdownView,
activator: () => dropdownView.isRendered && dropdownView.isOpen,
callback: () => {
dropdownView.isOpen = false;
},
contextElements: () => [
dropdownView.element,
// Include all elements connected to the dropdown's focus tracker, but exclude those that are direct children
// of DropdownView#element. They would be identified as descendants of #element anyway upon clicking and would
// not contribute to the logic.
...getFocusTrackerTreeElements(dropdownView.focusTracker).filter(element => !dropdownView.element.contains(element))
]
});
}
/**
* Returns all DOM elements connected to a DropdownView's focus tracker, either directly (same DOM sub-tree)
* or indirectly (external views registered in the focus tracker).
*/
function getFocusTrackerTreeElements(focusTracker) {
return [
...focusTracker.elements,
...focusTracker.externalViews.flatMap(view => getFocusTrackerTreeElements(view.focusTracker))
];
}
/**
* Adds a behavior to a dropdownView that closes the dropdown view on "execute" event.
*/
function closeDropdownOnExecute(dropdownView) {
// Close the dropdown when one of the list items has been executed.
dropdownView.on('execute', evt => {
// Toggling a switch button view should not close the dropdown.
if (evt.source instanceof SwitchButtonView) {
return;
}
dropdownView.isOpen = false;
});
}
/**
* Adds a behavior to a dropdown view that closes opened dropdown when it loses focus.
*/
function closeDropdownOnBlur(dropdownView) {
dropdownView.focusTracker.on('change:isFocused', (evt, name, isFocused) => {
if (isFocused || !dropdownView.isOpen) {
return;
}
dropdownView.isOpen = false;
});
}
/**
* Adds a behavior to a dropdownView that focuses the dropdown's panel view contents on keystrokes.
*/
function focusDropdownContentsOnArrows(dropdownView) {
// If the dropdown panel is already open, the arrow down key should focus the first child of the #panelView.
dropdownView.keystrokes.set('arrowdown', (data, cancel) => {
if (dropdownView.isOpen) {
dropdownView.panelView.focus();
cancel();
}
});
// If the dropdown panel is already open, the arrow up key should focus the last child of the #panelView.
dropdownView.keystrokes.set('arrowup', (data, cancel) => {
if (dropdownView.isOpen) {
dropdownView.panelView.focusLast();
cancel();
}
});
}
/**
* Adds a behavior that focuses the #buttonView when the dropdown was closed but focus was within the #panelView element.
* This makes sure the focus is never lost.
*/
function focusDropdownButtonOnClose(dropdownView) {
dropdownView.on('change:isOpen', (evt, name, isOpen) => {
if (isOpen) {
return;
}
const elements = dropdownView.focusTracker.elements;
// If the dropdown was closed, move the focus back to the button (#12125).
// Don't touch the focus, if it moved somewhere else (e.g. moved to the editing root on #execute) (#12178).
// Note: Don't use the state of the DropdownView#focusTracker here. It fires #blur with the timeout.
if (elements.some(element => element.contains(global.document.activeElement))) {
dropdownView.buttonView.focus();
}
});
}
/**
* Adds a behavior that focuses the #panelView when dropdown gets open (accessibility).
*/
function focusDropdownPanelOnOpen(dropdownView) {
dropdownView.on('change:isOpen', (evt, name, isOpen) => {
if (!isOpen) {
return;
}
// Focus the first item in the dropdown when the dropdown opened.
dropdownView.panelView.focus();
// * Let the panel show up first (do not focus an invisible element).
// * Also, execute before focusChildOnDropdownOpen() to make sure this helper does not break the
// focus of a specific child by kicking in too late and resetting the focus in the panel.
}, { priority: 'low' });
}
/**
* This helper populates a dropdown list with items and groups according to the
* collection of item definitions. A permanent binding is created in this process allowing
* dynamic management of the dropdown list content.
*
* @param dropdownView
* @param listItems
* @param definitions
* @param locale
*/
function bindViewCollectionItemsToDefinitions(dropdownView, listItems, definitions, locale) {
bindDropdownToggleableButtonsAlignment(listItems);
listItems.bindTo(definitions).using(def => {
if (def.type === 'separator') {
return new ListSeparatorView(locale);
}
else if (def.type === 'group') {
const groupView = new ListItemGroupView(locale);
groupView.set({ label: def.label });
bindViewCollectionItemsToDefinitions(dropdownView, groupView.items, def.items, locale);
groupView.items.delegate('execute').to(dropdownView);
return groupView;
}
else if (def.type === 'button' || def.type === 'switchbutton') {
const isToggleable = def.model.role === 'menuitemcheckbox' || def.model.role === 'menuitemradio';
const listItemView = new ListItemView(locale);
let buttonView;
if (def.type === 'button') {
buttonView = new ListItemButtonView(locale);
buttonView.set({
isToggleable
});
}
else {
buttonView = new SwitchButtonView(locale);
}
// Bind all model properties to the button view.
buttonView.bind(...Object.keys(def.model)).to(def.model);
buttonView.delegate('execute').to(listItemView);
listItemView.children.add(buttonView);
return listItemView;
}
return null;
});
}
/**
* Sets up alignment handling for toggleable buttons in a dropdown list.
*
* Buttons in dropdowns have reserved space for a check icon when they are toggleable.
* When at least one button in the list is toggleable, all other buttons (even non-toggleable ones)
* will have space on their left side to align with toggleable buttons.
*
* This function handles a special case where a new toggleable button is added (or removed) to a list
* where previous buttons weren't toggleable. In that case, those previous buttons will
* automatically allocate space to align with the new toggleable button.
*
* Example:
* ```
* Before adding toggleable button:
* +----------------+
* | Normal Button |
* +----------------+
* | Another Button |
* +----------------+
*
* After adding toggleable button:
* +-------------------+
* | Normal Button |
* +-------------------+
* | Another Button |
* +-------------------+
* | ✓ Toggle Button |
* +-------------------+
* ```
*
* @param listItems Collection of list items to observe for toggleable buttons.
*/
function bindDropdownToggleableButtonsAlignment(listItems) {
// Keep track of how many toggleable buttons are in the list.
let toggleableButtonsCount = 0;
// Helper function that checks if a view item is a list item button.
const pickListItemButtonIfPresent = (item) => {
// Check if the item is a ListItemView with a ListItemButtonView as its first child.
if (!(item instanceof ListItemView) || !(item.children.first instanceof ListItemButtonView)) {
return null;
}
return item.children.first;
};
// Helper function that checks if a view item is a toggleable button.
// Returns the button if it's toggleable - otherwise, returns null.
const pickListItemToggleableButtonIfPresent = (item) => {
const listItemButtonView = pickListItemButtonIfPresent(item);
// Only return buttons that are configured as toggleable.
if (!listItemButtonView || !listItemButtonView.isToggleable) {
return null;
}
return listItemButtonView;
};
// Updates all buttons in the list to either allocate space for check marks or not.
// This ensures all buttons are properly aligned regardless of their toggleable state.
const updateAllButtonsCheckSpace = (hasSpace) => {
for (const listItem of listItems) {
const listItemButton = pickListItemButtonIfPresent(listItem);
if (listItemButton) {
listItemButton.hasCheckSpace = hasSpace;
}
}
};
// Listen for changes in the list items collection.
listItems.on('change', (evt, data) => {
// Remember the current state - whether we have any toggleable buttons.
const prevToggleable = toggleableButtonsCount > 0;
// Process removed items - decrease count for each toggleable button removed.
for (const item of data.removed) {
if (pickListItemToggleableButtonIfPresent(item)) {
toggleableButtonsCount--;
}
}
// Process added items - increase count for each toggleable button added.
for (const item of data.added) {
const button = pickListItemButtonIfPresent(item);
if (!button) {
continue;
}
if (button.isToggleable) {
// Check if the button is toggleable and increase the count.
toggleableButtonsCount++;
}
// Depending on the current state, set the check space for the button.
button.hasCheckSpace = toggleableButtonsCount > 0;
}
// Check if the current state has changed.
const currentToggleable = toggleableButtonsCount > 0;
// Only update button alignment if we've crossed the threshold between
// having no toggleable buttons and having at least one.
if (prevToggleable !== currentToggleable) {
updateAllButtonsCheckSpace(currentToggleable);
}
});
}