UNPKG

@progress/kendo-angular-toolbar

Version:

Kendo UI Angular Toolbar component - a single UI element that organizes buttons and other navigation elements

1,161 lines (1,159 loc) 68.3 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ /* eslint-disable no-case-declarations */ import { Component, HostBinding, ViewChild, TemplateRef, ElementRef, QueryList, ContentChildren, Input, HostListener, Output, EventEmitter, ViewContainerRef, NgZone, Renderer2, ChangeDetectorRef } from '@angular/core'; import { PopupService } from '@progress/kendo-angular-popup'; import { guid, ResizeSensorComponent } from '@progress/kendo-angular-common'; import { LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from './package-metadata'; import { RefreshService } from './refresh.service'; import { NavigationService } from './navigation.service'; import { ToolBarToolComponent } from './tools/toolbar-tool.component'; import { innerWidth, closest, isPresent, getStylingClasses, SIZES, normalizeOverflowSettings } from './util'; import { Keys } from '@progress/kendo-angular-common'; import { PreventableEvent } from './common/preventable-event'; import { ToolBarRendererComponent } from './renderer.component'; import { Subscription, fromEvent, Subject, merge } from 'rxjs'; import { take, takeUntil } from 'rxjs/operators'; import { filter } from 'rxjs/operators'; import { isDocumentAvailable } from '@progress/kendo-angular-common'; import { caretAltLeftIcon, caretAltRightIcon, moreHorizontalIcon, moreVerticalIcon } from '@progress/kendo-svg-icons'; import { ButtonComponent } from '@progress/kendo-angular-buttons'; import { NgFor, NgIf, NgClass, NgTemplateOutlet } from '@angular/common'; import { LocalizedToolbarMessagesDirective } from './localization/localized-toolbar-messages.directive'; import { ToolbarToolsService } from './tools/tools.service'; import { ScrollService } from './scroll.service'; import { ToolbarScrollableButtonComponent } from './scrollable-button.component'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; import * as i2 from "@progress/kendo-angular-popup"; import * as i3 from "./refresh.service"; import * as i4 from "./navigation.service"; import * as i5 from "./tools/tools.service"; import * as i6 from "./scroll.service"; const DEFAULT_SIZE = 'medium'; const DEFAULT_FILL_MODE = 'solid'; const immediateResizeThreshold = 300; const getInitialPopupSettings = (isRtl) => ({ animate: true, anchorAlign: { horizontal: isRtl ? 'left' : 'right', vertical: 'bottom' }, popupAlign: { horizontal: isRtl ? 'left' : 'right', vertical: 'top' } }); /** * Represents the [Kendo UI ToolBar component for Angular]({% slug overview_toolbar %}). */ export class ToolBarComponent { localization; popupService; refreshService; navigationService; element; zone; renderer; _cdr; toolsService; scrollService; get overflowClass() { return `k-button-${SIZES[this.size]}`; } /** * Configures the overflow mode. Used to specify how tools will be rendered when the total size of all tools is greater than the size of the Toolbar container. * @default false */ set overflow(overflow) { if (this.isScrollMode) { this.removeSubscriptions(['scrollButtonStateChangeSub', 'scrollContainerScrollSub']); } this._overflow = overflow; if (this.isScrollMode) { this.handleScrollModeUpdates(); } this.setScrollableOverlayClasses(); this.zone.onStable.pipe(take(1)).subscribe(() => this.onResize()); } get overflow() { return this._overflow; } get showScrollButtons() { const buttonsVisibility = this.normalizedOverflow.scrollButtons; const showAuto = buttonsVisibility === 'auto' && this.showAutoButtons; const showAlways = buttonsVisibility === 'visible'; return this.isScrollMode && (showAuto || showAlways); } /** * @hidden */ set resizable(value) { this.overflow = value; } get resizable() { return this.showMenu; } /** * @hidden */ get hasScrollButtons() { const visible = this.normalizedOverflow.mode === 'scroll' && this.normalizedOverflow.scrollButtons !== 'hidden'; const position = this.normalizedOverflow.scrollButtonsPosition; return { visible, position }; } /** * @hidden */ get isScrollMode() { return this.normalizedOverflow.mode === 'scroll'; } /** * @hidden */ get showMenu() { return this.normalizedOverflow.mode === 'menu' || this.normalizedOverflow.mode === 'section'; } /** * @hidden */ get overflowEnabled() { return this.normalizedOverflow.mode !== 'none'; } /** * Configures the popup of the ToolBar overflow button ([see example](slug:responsive_toolbar#customizing-the-popup)). * * The available options are: * - `animate: Boolean`&mdash;Controls the popup animation. By default, the open and close animations are enabled. * - `popupClass: String`&mdash;Specifies a list of CSS classes that are used to style the popup. */ set popupSettings(settings) { this._popupSettings = Object.assign({}, getInitialPopupSettings(this.localization.rtl), settings); } get popupSettings() { return this._popupSettings || getInitialPopupSettings(this.localization.rtl); } /** * The fillMode property specifies the background and border styles of the Toolbar * ([see example](slug:appearance_toolbar#toc-fill-mode)). * * @default 'solid' */ set fillMode(fillMode) { const newFillMode = fillMode ? fillMode : DEFAULT_FILL_MODE; this.handleClasses(newFillMode, 'fillMode'); this._fillMode = newFillMode; } get fillMode() { return this._fillMode; } /** * Specifies the [`tabindex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) of the ToolBar. */ tabindex = 0; /** * Specifies the padding of all Toolbar elements. * * The possible values are: * * `small` * * `medium` (default) * * `large` * * `none` */ set size(size) { const newSize = size ? size : DEFAULT_SIZE; this.handleClasses(newSize, 'size'); this._size = newSize; } get size() { return this._size; } /** * @hidden */ set tabIndex(tabIndex) { this.tabindex = tabIndex; } get tabIndex() { return this.tabindex; } /** * Specifies the icons visibility for all tools in the ToolBar. * This can be overridden by the `showIcon` property of each tool. */ set showIcon(value) { if (this._showIcon === value) { return; } const normalizedValue = this.normalizeDisplayValue(value); this._showIcon = value; this.propertyChange.emit({ property: 'showIcon', value: normalizedValue }); } /** * Specifies the text visibility for all tools in the ToolBar. * This can be overridden by the `showText` property of each tool. */ set showText(value) { if (this._showText === value) { return; } const normalizedValue = this.normalizeDisplayValue(value); this._showText = value; this.propertyChange.emit({ property: 'showText', value: normalizedValue }); } /** * Fires when the overflow popup of the ToolBar is opened. */ open = new EventEmitter(); /** * Fires when the overflow popup of the ToolBar is closed. */ close = new EventEmitter(); allTools; overflowButton; popupTemplate; popupSectionTemplate; scrollContainer; resizeSensor; container; prevScrollButton; nextScrollButton; startButtonGroup; endButtonGroup; scrollSeparator; popupRef; direction; get appendTo() { const { appendTo } = this.popupSettings; if (!appendTo || appendTo === 'root') { return undefined; } return appendTo === 'component' ? this.container : appendTo; } set popupOpen(open) { if (this.popupOpen === open) { return; } const eventArgs = new PreventableEvent(); if (open) { this.open.emit(eventArgs); } else { this.close.emit(eventArgs); } if (eventArgs.isDefaultPrevented()) { return; } this.toggle(open); } get popupOpen() { return this._open; } /** * @hidden */ prevButtonIcon = caretAltLeftIcon; /** * @hidden */ nextButtonIcon = caretAltRightIcon; /** * @hidden */ propertyChange = new EventEmitter(); hostClass = true; get scrollableClass() { return this.isScrollMode; } get sectionClass() { return this.normalizedOverflow.mode === 'section'; } _overflow = false; _popupSettings; cachedOverflowAnchorWidth; _open; toolbarKeydownListener; overflowKeydownListener; sectionKeydownListener; cancelRenderedToolsSubscription$ = new Subject(); cachedGap; _size = DEFAULT_SIZE; _fillMode = DEFAULT_FILL_MODE; _showText = 'always'; _showIcon = 'always'; overflowButtonClickedTime = null; showAutoButtons = false; scrollButtonStateChangeSub; scrollContainerScrollSub; /** * @hidden */ get normalizedOverflow() { return normalizeOverflowSettings(this.overflow); } subscriptions = new Subscription(); popupSubs = new Subscription(); focusedByPointer = false; /** * @hidden */ onFocus(ev) { if (this.focusedByPointer) { this.focusedByPointer = false; return; } this.navigationService.resetNavigation(); this.navigationService.focusFirst(ev); // prevents ExpressionChangedAfterItHasBeenCheckedError when tools with popup are opened/closed asynchronously this.element.nativeElement.setAttribute('tabindex', '-1'); } /** * @hidden */ onFocusOut(event) { // prevents ExpressionChangedAfterItHasBeenCheckedError when tools with popup are opened/closed asynchronously if (closest(event.relatedTarget, (el) => el === this.element.nativeElement)) { this.element.nativeElement.setAttribute('tabindex', '-1'); return; } this.element.nativeElement.setAttribute('tabindex', this.tabindex.toString()); } role = 'toolbar'; get getDir() { return this.direction; } get resizableClass() { return this.resizable; } constructor(localization, popupService, refreshService, navigationService, // Needs to be public as it is being accessed in the Editor component element, zone, renderer, _cdr, toolsService, scrollService) { this.localization = localization; this.popupService = popupService; this.refreshService = refreshService; this.navigationService = navigationService; this.element = element; this.zone = zone; this.renderer = renderer; this._cdr = _cdr; this.toolsService = toolsService; this.scrollService = scrollService; validatePackage(packageMetadata); this.direction = localization.rtl ? 'rtl' : 'ltr'; this.scrollService.owner = this; } ngAfterContentInit() { this.toolsService.allTools = this.allTools.toArray(); this.subscriptions.add(this.allTools.changes.subscribe(() => { this.toolsService.reset(); this.toolsService.allTools = this.allTools.toArray(); this.allTools.forEach((tool) => { this.refreshService.refresh(tool); }); this.zone.onStable.pipe(take(1)).subscribe(() => this.onResize()); })); } ngAfterViewInit() { this.toolsService.renderedToolsChange.next(this.toolsService.renderedTools); this.toolsService.overflowToolsChange.next(this.toolsService.overflowTools); const element = this.element.nativeElement; if (!element.getAttribute('tabindex')) { this.element.nativeElement.setAttribute('tabindex', '0'); } this.zone.runOutsideAngular(() => { this.toolbarKeydownListener = this.renderer.listen(this.element.nativeElement, 'keydown', (ev) => { switch (ev.keyCode) { case Keys.ArrowLeft: this.zone.run(() => { ev.preventDefault(); if (this.direction === 'ltr') { this.navigationService.focusPrev(ev); } else { this.navigationService.focusNext(ev); } // prevents ExpressionChangedAfterItHasBeenCheckedError when tools with popup are opened/closed asynchronously this.element.nativeElement.setAttribute('tabindex', '-1'); }); break; case Keys.ArrowRight: this.zone.run(() => { ev.preventDefault(); if (this.direction === 'ltr') { this.navigationService.focusNext(ev); } else { this.navigationService.focusPrev(ev); } // prevents ExpressionChangedAfterItHasBeenCheckedError when tools with popup are opened/closed asynchronously this.element.nativeElement.setAttribute('tabindex', '-1'); }); break; case Keys.Tab: this.zone.run(() => this.navigationService.resetNavigation()); break; case Keys.Escape: this.zone.run(() => this.toggle(false)); break; case Keys.Home: this.zone.run(() => this.navigationService.focusFirst(ev)); break; case Keys.End: this.zone.run(() => this.navigationService.focusLast(ev)); break; default: break; } }); }); if (this.overflowEnabled) { this.subscriptions.add(merge(this.resizeSensor.resize, this.toolsService.renderedToolsChange).subscribe(() => this.onResize())); if (this.showMenu) { this.navigationService.overflowButton = this.overflowButton; } // because of https://github.com/telerik/kendo-angular-buttons/pull/276 // button icons are not rendered until onResize() is called this.zone.runOutsideAngular(() => { setTimeout(() => { this.zone.run(() => { this.onResize(); }); }); }); } if (this.isScrollMode) { this.handleScrollModeUpdates(); } this.subscriptions.add(this.renderer.listen(this.element.nativeElement, 'pointerdown', (ev) => { if (!ev.target.closest('.k-toolbar-items')) { this.focusedByPointer = true; } })); this.navigationService.setRenderedTools(this.toolsService.renderedTools); const stylingOptions = ['size', 'fillMode']; stylingOptions.forEach(option => { this.handleClasses(this[option], option); }); } ngOnInit() { this.subscriptions.add(this.localization.changes.subscribe(({ rtl }) => (this.direction = rtl ? 'rtl' : 'ltr'))); this.zone.onStable.pipe(take(1)).subscribe(() => this.onResize()); if (isDocumentAvailable()) { this.zone.runOutsideAngular(() => this.subscriptions.add(fromEvent(document, 'click') .pipe(filter(() => !!this.popupRef), filter((ev) => !this.popupRef.popup.instance.container.nativeElement.contains(ev.target)), filter((ev) => !this.overflowButton.nativeElement.contains(ev.target))) .subscribe(() => { this.zone.run(() => { this.popupOpen = false; }); }))); } } ngOnChanges(changes) { if (changes['tabindex']) { // prevents ExpressionChangedAfterItHasBeenCheckedError when tools with popup are opened/closed asynchronously this.element.nativeElement.setAttribute('tabindex', changes['tabindex'].currentValue.toString()); } } ngOnDestroy() { this.destroyPopup(); if (this.toolbarKeydownListener) { this.toolbarKeydownListener(); } this.cancelRenderedToolsSubscription$.next(); this.subscriptions.unsubscribe(); } /** * @hidden */ showOverflowSeparator = false; /** * @hidden */ get moreToolsTitle() { return this.localization.get('moreToolsTitle'); } /** * @hidden */ get cdr() { return this._cdr; } /** * @hidden */ get sectionSizeClass() { return this.size === 'none' ? '' : `k-toolbar-items-list-${SIZES[this.size]}`; } /** * @hidden */ getScrollButtonTitle(buttonType) { let currentButton; if (this.localization.rtl) { currentButton = buttonType === 'prev' ? 'nextToolButton' : 'previousToolButton'; } else { currentButton = buttonType === 'prev' ? 'previousToolButton' : 'nextToolButton'; } return this.localization.get(currentButton); } /** * @hidden */ scrollTools(dir) { this.scrollService.scrollTools(dir); } /** * @hidden */ onRendererClick(data) { this.navigationService.click(data); this.element.nativeElement.setAttribute('tabindex', '-1'); } /** * @hidden */ overflowButtonIcon(iconType) { if (iconType === 'svg') { return this.normalizedOverflow.mode === 'section' ? moreHorizontalIcon : moreVerticalIcon; } else { return this.normalizedOverflow.mode === 'section' ? 'more-horizontal' : 'more-vertical'; } } /** * @hidden */ showPopup() { this.popupOpen = !this.popupOpen; this.navigationService.click({ context: undefined, event: undefined }); this.overflowButtonClickedTime = Date.now(); } /** * Toggles the visibility of the overflow popup. */ toggle(popupOpen) { this._open = popupOpen !== undefined ? popupOpen : !this.popupOpen; if (this.popupRef) { this.popupRef.close(); this.popupRef = null; } if (this.popupOpen) { let settings; const isSection = this.normalizedOverflow.mode === 'section'; if (isSection) { settings = { anchor: this.element.nativeElement, content: this.popupSectionTemplate, popupClass: this.normalizePopupClasses(), positionMode: 'absolute' }; } else { settings = { anchor: this.overflowButton, anchorAlign: this.popupSettings.anchorAlign, popupAlign: this.popupSettings.popupAlign, content: this.popupTemplate, appendTo: this.appendTo, animate: this.popupSettings.animate, popupClass: this.normalizePopupClasses(this.popupSettings.popupClass), positionMode: 'absolute' }; } this.popupRef = this.popupService.open(settings); this.setPopupContentDimensions(isSection); this.popupSubs.add(this.popupRef.popupOpen.subscribe(this.onPopupOpen.bind(this))); this.popupSubs.add(this.popupRef.popupClose.subscribe(this.onPopupClose.bind(this))); } } /** * @hidden */ onResize() { if (isDocumentAvailable()) { if (this.showMenu) { const containerWidth = innerWidth(this.element.nativeElement) - this.overflowAnchorWidth; this.shrink(containerWidth, this.childrenWidth); this.stretch(containerWidth, this.childrenWidth); this.displayAnchor(); const isImmediateResize = (Date.now() - this.overflowButtonClickedTime) < immediateResizeThreshold; if (this.popupOpen && !isImmediateResize) { this.toggle(); } } else if (this.isScrollMode) { if (this.normalizedOverflow.scrollButtons === 'auto') { const containerWidth = innerWidth(this.element.nativeElement); let scrollButtonsWidth = 0; if (this.showAutoButtons) { const separatorWidth = this.scrollSeparator.nativeElement.getBoundingClientRect().width + 2 * this.gap; if (this.hasScrollButtons.position === 'split') { scrollButtonsWidth = innerWidth(this.prevScrollButton.nativeElement) + innerWidth(this.nextScrollButton.nativeElement) + 2 * separatorWidth; } else if (this.hasScrollButtons.position === 'end') { scrollButtonsWidth = innerWidth(this.endButtonGroup.nativeElement) + separatorWidth; } else { scrollButtonsWidth = innerWidth(this.startButtonGroup.nativeElement) + separatorWidth; } } const shouldShowButtons = (this.childrenWidth + scrollButtonsWidth) > containerWidth; if (shouldShowButtons !== this.showAutoButtons) { this.showAutoButtons = shouldShowButtons; this.cdr.detectChanges(); } this.scrollService.toggleScrollButtonsState(); } else if (!this.hasScrollButtons.visible) { this.setScrollableOverlayClasses(); } else if (!this.scrollService.toolsOverflow) { this.renderer.addClass(this.nextScrollButton.nativeElement, 'k-disabled'); this.renderer.addClass(this.prevScrollButton.nativeElement, 'k-disabled'); } else { this.scrollService.toggleScrollButtonsState(); } } this.resizeSensor?.acceptSize(); } } /** * @hidden */ onPopupOpen() { this.zone.runOutsideAngular(() => { if (this.normalizedOverflow.mode === 'section') { this.sectionKeydownListener = this.renderer.listen(this.popupRef.popupElement, 'keydown', (ev) => { switch (ev.keyCode) { case Keys.ArrowLeft: this.zone.run(() => { ev.preventDefault(); if (this.direction === 'ltr') { this.navigationService.focusPrev(ev); } else { this.navigationService.focusNext(ev); } // prevents ExpressionChangedAfterItHasBeenCheckedError when tools with popup are opened/closed asynchronously this.element.nativeElement.setAttribute('tabindex', '-1'); }); break; case Keys.ArrowRight: this.zone.run(() => { ev.preventDefault(); if (this.direction === 'ltr') { this.navigationService.focusNext(ev); } else { this.navigationService.focusPrev(ev); } // prevents ExpressionChangedAfterItHasBeenCheckedError when tools with popup are opened/closed asynchronously this.element.nativeElement.setAttribute('tabindex', '-1'); }); break; case Keys.Escape: { this.zone.run(() => this.toggle(false)); const eventArgs = new PreventableEvent(); this.close.emit(eventArgs); break; } case Keys.Tab: this.zone.run(() => { this.toggle(false); this.navigationService.resetNavigation(); }); break; default: break; } }); } else { this.overflowKeydownListener = this.renderer.listen(this.popupRef.popupElement, 'keydown', (ev) => { switch (ev.keyCode) { case Keys.ArrowUp: this.zone.run(() => { ev.preventDefault(); this.navigationService.focusPrev(ev); }); break; case Keys.ArrowDown: this.zone.run(() => { ev.preventDefault(); this.navigationService.focusNext(ev); }); break; case Keys.Escape: { this.zone.run(() => this.toggle(false)); const eventArgs = new PreventableEvent(); this.close.emit(eventArgs); break; } case Keys.Tab: this.zone.run(() => { this.toggle(false); this.navigationService.resetNavigation(); }); break; case Keys.Enter: case Keys.Space: this.zone.run(() => { if (ev.target.closest('.k-menu-item')) { ev.preventDefault(); ev.target.click(); ev.target.focus(); } }); break; default: break; } }); } }); this.cancelRenderedToolsSubscription$.next(); this.navigationService.setRenderedTools(this.toolsService.overflowTools); this.navigationService.moveFocusToPopup(); this.toolsService.overflowToolsChange .pipe(takeUntil(this.cancelRenderedToolsSubscription$)) .subscribe((rts) => this.navigationService.setRenderedTools(rts)); this.renderer.setAttribute(this.overflowButton.nativeElement, 'aria-controls', this.popupId); } /** * @hidden */ onPopupClose() { this.cancelRenderedToolsSubscription$.next(); this.navigationService.setRenderedTools(this.toolsService.renderedTools); this.toolsService.renderedToolsChange .pipe(takeUntil(this.cancelRenderedToolsSubscription$)) .subscribe((rts) => this.navigationService.setRenderedTools(rts)); this.navigationService.moveFocusToToolBar(); if (this.overflowKeydownListener) { this.overflowKeydownListener(); } if (this.sectionKeydownListener) { this.sectionKeydownListener(); } this.renderer.removeAttribute(this.overflowButton.nativeElement, 'aria-controls'); } /** * @hidden */ overflowBtnId = guid(); /** * @hidden */ popupId = guid(); displayAnchor() { const visibility = this.allTools.filter(t => t.overflows && t.responsive).length > 0 ? 'visible' : 'hidden'; this.overflowButton && this.renderer.setStyle(this.overflowButton.nativeElement, 'visibility', visibility); const isVisible = visibility === 'visible'; if (isVisible !== this.showOverflowSeparator) { this.showOverflowSeparator = isVisible; this.cdr.detectChanges(); } } get popupWidth() { if (!this.popupSettings || !this.popupSettings.width) { return 'auto'; } return isNaN(this.popupSettings.width) ? this.popupSettings.width : `${this.popupSettings.width}px`; } get popupHeight() { if (!this.popupSettings || !this.popupSettings.height) { return; } return isNaN(this.popupSettings.height) ? this.popupSettings.height : `${this.popupSettings.height}px`; } get overflowAnchorWidth() { if (!this.showMenu) { return 0; } if (!this.cachedOverflowAnchorWidth) { this.cachedOverflowAnchorWidth = this.overflowButton.nativeElement.offsetWidth; } return this.cachedOverflowAnchorWidth; } get gap() { if (isPresent(this.cachedGap)) { return this.cachedGap; } const computedGap = getComputedStyle(this.element.nativeElement).gap; this.cachedGap = isPresent(computedGap) ? parseInt(computedGap, 10) : 0; return this.cachedGap; } get childrenWidth() { const width = this.toolsService.renderedTools.reduce((totalWidth, tool) => tool.width + totalWidth + (tool.isDisplayed() ? this.gap : 0), 0); return Math.ceil(width); } get visibleTools() { return this.allTools.filter((tool) => { return tool.overflows === false; }); } get overflowTools() { return this.allTools.filter((tool) => { return tool.overflows === true; }); } shrink(containerWidth, childrenWidth) { let width; if (containerWidth < childrenWidth) { for (let i = this.visibleTools.length - 1; i >= 0; i--) { if (containerWidth > childrenWidth) { break; } else { width = this.hideLastVisibleTool(); childrenWidth -= width; } } } } stretch(containerWidth, childrenWidth) { let width; if (containerWidth > childrenWidth) { for (let i = this.overflowTools.length - 1; i >= 0; i--) { width = this.showFirstHiddenTool(containerWidth, childrenWidth); if (width > 0) { childrenWidth += width + this.gap; } else if (width === 0) { break; } } } } hideLastVisibleTool() { const tool = this.visibleTools[this.visibleTools.length - 1]; if (!tool) { return null; } const renderedElement = this.toolsService.renderedTools.find((r) => { return r.tool === tool; }); const width = renderedElement.width; tool.overflows = this.showMenu; this.refreshService.refresh(tool); return width; } showFirstHiddenTool(containerWidth, childrenWidth) { const tool = this.overflowTools[0]; if (!tool) { return null; } const renderedElement = this.toolsService.renderedTools.find((r) => r.tool === tool); tool.overflows = false; tool.visibility = 'hidden'; this.refreshService.refresh(tool); if (containerWidth > childrenWidth + renderedElement.width) { tool.visibility = 'visible'; } else { tool.overflows = true; } this.refreshService.refresh(tool); return tool.isHidden ? -1 : renderedElement.width; // returns 0 if `overflows` is true and -1 if the tool is hidden } setPopupContentDimensions(isSection) { const popupContentContainer = this.popupRef.popup.instance.contentContainer.nativeElement; if (isSection) { const toolbarWidth = this.element.nativeElement.getBoundingClientRect().width; popupContentContainer.style.width = `${toolbarWidth}px`; } else { popupContentContainer.style.width = this.popupWidth; popupContentContainer.style.height = this.popupHeight; popupContentContainer.style.overflow = 'auto'; } } destroyPopup() { if (this.popupRef) { this.popupSubs.unsubscribe(); this.popupRef.close(); this.popupRef = null; } } handleClasses(value, input) { const elem = this.element.nativeElement; const classes = getStylingClasses('toolbar', input, this[input], value); if (classes.toRemove) { this.renderer.removeClass(elem, classes.toRemove); } if (classes.toAdd) { this.renderer.addClass(elem, classes.toAdd); } } normalizePopupClasses(classList) { let classes = ['k-toolbar-popup']; if (this.normalizedOverflow.mode === 'menu') { classes.push('k-menu-popup'); } if (!classList) { return classes; } if (typeof classList === 'string') { classes.push(...classList.split(' ')); } else if (Array.isArray(classList)) { classes = [...classes, ...classList]; } else { for (const cl in classList) { classes.push(classList[cl]); } } return classes; } setScrollableOverlayClasses() { const wrapper = this.element.nativeElement; const container = this.scrollContainer?.nativeElement; if (!container) { return; } const scrollOffset = container.scrollLeft; const defaultOffset = 1; if (this.scrollService.toolsOverflow && !this.hasScrollButtons.visible) { this.renderer.addClass(wrapper, 'k-toolbar-scrollable-overlay'); if (scrollOffset === 0) { this.renderer.removeClass(wrapper, 'k-toolbar-scrollable-end'); this.renderer.addClass(wrapper, 'k-toolbar-scrollable-start'); } else if ((scrollOffset > 0 && scrollOffset < this.scrollService.scrollContainerOverflowSize - defaultOffset) || (scrollOffset < 0 && Math.abs(scrollOffset) < this.scrollService.scrollContainerOverflowSize - defaultOffset)) { this.renderer.removeClass(wrapper, 'k-toolbar-scrollable-end'); this.renderer.removeClass(wrapper, 'k-toolbar-scrollable-start'); } else { this.renderer.removeClass(wrapper, 'k-toolbar-scrollable-start'); this.renderer.addClass(wrapper, 'k-toolbar-scrollable-end'); } } else { this.renderer.removeClass(wrapper, 'k-toolbar-scrollable-overlay'); this.renderer.removeClass(wrapper, 'k-toolbar-scrollable-end'); this.renderer.removeClass(wrapper, 'k-toolbar-scrollable-start'); } } handleScrollModeUpdates() { if (isPresent(this.scrollContainer)) { this.scrollService.updateScrollPosition(this.scrollContainer.nativeElement); this.zone.runOutsideAngular(() => { this.scrollContainerScrollSub = this.renderer.listen(this.scrollContainer.nativeElement, 'scroll', (e) => { if (!this.hasScrollButtons.visible) { this.setScrollableOverlayClasses(); } else { this.scrollService.onScroll(e); } }); this.subscriptions.add(this.scrollContainerScrollSub); }); } if (this.showScrollButtons && this.nextScrollButton && this.prevScrollButton) { if (this.normalizedOverflow.scrollButtons === 'visible' && !this.scrollService.toolsOverflow) { this.renderer.addClass(this.nextScrollButton.nativeElement, 'k-disabled'); this.renderer.addClass(this.prevScrollButton.nativeElement, 'k-disabled'); } else { const buttonToDisable = this.direction === 'rtl' ? this.nextScrollButton : this.prevScrollButton; this.renderer.addClass(buttonToDisable.nativeElement, 'k-disabled'); } } if (this.hasScrollButtons.visible) { this.scrollButtonStateChangeSub = this.scrollService.scrollButtonActiveStateChange.subscribe((activeButtonSettings) => { if (this.showScrollButtons) { const action = activeButtonSettings.active ? 'remove' : 'add'; const scrollButton = this[`${activeButtonSettings.buttonType}ScrollButton`]?.nativeElement; scrollButton && this.renderer[`${action}Class`](scrollButton, 'k-disabled'); } }); this.subscriptions.add(this.scrollButtonStateChangeSub); this.scrollService.toggleScrollButtonsState(); } } removeSubscriptions(subsToRemove) { subsToRemove.forEach((sub) => { if (this[sub]) { this.subscriptions.remove(this[sub]); this[sub] = null; } }); } normalizeDisplayValue(value) { if (typeof value === 'boolean') { return value ? 'always' : 'never'; } return value; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolBarComponent, deps: [{ token: i1.LocalizationService }, { token: i2.PopupService }, { token: i3.RefreshService }, { token: i4.NavigationService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: i5.ToolbarToolsService }, { token: i6.ScrollService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ToolBarComponent, isStandalone: true, selector: "kendo-toolbar", inputs: { overflow: "overflow", resizable: "resizable", popupSettings: "popupSettings", fillMode: "fillMode", tabindex: "tabindex", size: "size", tabIndex: "tabIndex", showIcon: "showIcon", showText: "showText" }, outputs: { open: "open", close: "close" }, host: { listeners: { "focus": "onFocus($event)", "focusout": "onFocusOut($event)" }, properties: { "class.k-toolbar": "this.hostClass", "class.k-toolbar-scrollable": "this.scrollableClass", "class.k-toolbar-section": "this.sectionClass", "attr.role": "this.role", "attr.dir": "this.getDir", "class.k-toolbar-resizable": "this.resizableClass" } }, providers: [ RefreshService, NavigationService, LocalizationService, ToolbarToolsService, ScrollService, { provide: L10N_PREFIX, useValue: 'kendo.toolbar' } ], queries: [{ propertyName: "allTools", predicate: ToolBarToolComponent }], viewQueries: [{ propertyName: "overflowButton", first: true, predicate: ["overflowButton"], descendants: true, read: ElementRef }, { propertyName: "popupTemplate", first: true, predicate: ["popupTemplate"], descendants: true, static: true }, { propertyName: "popupSectionTemplate", first: true, predicate: ["popupSectionTemplate"], descendants: true, static: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, read: ElementRef }, { propertyName: "resizeSensor", first: true, predicate: ["resizeSensor"], descendants: true }, { propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "prevScrollButton", first: true, predicate: ["prevScrollButton"], descendants: true, read: ElementRef }, { propertyName: "nextScrollButton", first: true, predicate: ["nextScrollButton"], descendants: true, read: ElementRef }, { propertyName: "startButtonGroup", first: true, predicate: ["startButtonGroup"], descendants: true, read: ElementRef }, { propertyName: "endButtonGroup", first: true, predicate: ["endButtonGroup"], descendants: true, read: ElementRef }, { propertyName: "scrollSeparator", first: true, predicate: ["scrollSeparator"], descendants: true, read: ElementRef }], exportAs: ["kendoToolBar"], usesOnChanges: true, ngImport: i0, template: ` <ng-container kendoToolbarLocalizedMessages i18n-moreToolsTitle="kendo.toolbar.moreToolsTitle|The title of the **more tools** button in a responsive ToolBar" moreToolsTitle="More tools" i18n-previousToolButton="kendo.toolbar.previousToolButton|The title for the **Previous Tool** button when the Toolbar is scrollable." previousToolButton="Scroll left" i18n-nextToolButton="kendo.toolbar.nextToolButton|The title for the **Next Tool** button when the Toolbar is scrollable." nextToolButton="Scroll right" > </ng-container> <ng-container *ngIf="showScrollButtons && (hasScrollButtons.position === 'split' || hasScrollButtons.position === 'start')"> <span *ngIf="hasScrollButtons.position === 'split'" #prevScrollButton kendoToolbarScrollableButton [prev]="true" [overflow]="normalizedOverflow" [title]="getScrollButtonTitle('prev')" class="k-toolbar-prev k-icon-button k-button k-button-solid k-button-solid-base k-rounded-md" [ngClass]="{ 'k-button-sm': size === 'small', 'k-button-md': size === 'medium' || !size, 'k-button-lg': size === 'large' }" (onClick)="scrollTools($event)"> </span> <div class="k-button-group k-button-group-solid" *ngIf="hasScrollButtons.position === 'start'" #startButtonGroup> <span #prevScrollButton kendoToolbarScrollableButton [prev]="true" [overflow]="normalizedOverflow" [title]="getScrollButtonTitle('prev')" class="k-toolbar-prev k-icon-button k-button k-button-solid k-button-solid-base k-rounded-md" [ngClass]="{ 'k-button-sm': size === 'small', 'k-button-md': size === 'medium' || !size, 'k-button-lg': size === 'large' }" (onClick)="scrollTools($event)"> </span> <span #nextScrollButton kendoToolbarScrollableButton [prev]="false" [overflow]="normalizedOverflow" [title]="getScrollButtonTitle('next')" class="k-toolbar-next k-icon-button k-button k-button-solid k-button-solid-base k-rounded-md" [ngClass]="{ 'k-button-sm': size === 'small', 'k-button-md': size === 'medium' || !size, 'k-button-lg': size === 'large' }" (onClick)="scrollTools($event)"> </span> </div> <div class="k-toolbar-separator k-toolbar-button-separator k-separator" #scrollSeparator></div> </ng-container> <div class="k-toolbar-items k-toolbar-items-scroll" tabindex="-1" *ngIf="isScrollMode; else noScroll" #scrollContainer> <ng-container *ngFor="let tool of allTools; let index = index" kendoToolbarRenderer [tool]="tool" location="toolbar" [resizable]="resizable" (rendererClick)="onRendererClick($event)" [ngTemplateOutlet]="tool.isBuiltInTool ? tool.toolbarTemplate : wrapper"> <ng-template #wrapper> <div class="k-toolbar-item"> <ng-container [ngTemplateOutlet]="tool.toolbarTemplate"></ng-container> </div> </ng-template> </ng-container> </div> <ng-template #noScroll> <ng-container *ngFor="let tool of allTools; let index = index" kendoToolbarRenderer [tool]="tool" location="toolbar" [resizable]="resizable" (rendererClick)="onRendererClick($event)" [ngTemplateOutlet]="tool.isBuiltInTool ? tool.toolbarTemplate : wrapper"> <ng-template #wrapper> <div class="k-toolbar-item"> <ng-container [ngTemplateOutlet]="tool.toolbarTemplate"></ng-container> </div> </ng-template> </ng-container> </ng-template> <div class="k-toolbar-separator k-toolbar-button-separator k-separator" *ngIf="showOverflowSeparator"></div> <button kendoButton fillMode="flat" #overflowButton type="button" [icon]="overflowButtonIcon('font')" [svgIcon]="overflowButtonIcon('svg')" tabindex="-1" [title]="moreToolsTitle" [attr.aria-label]="moreToolsTitle" [attr.aria-expanded]="popupOpen" [id]="overflowBtnId" [attr.aria-haspopup]="normalizedOverflow.mode === 'section' ? null : 'menu'" *ngIf="showMenu" [style.visibility]="'hidden'" [style.position]="'relative'" class="k-toolbar-overflow-button" [ngClass]="overflowClass" (click)="showPopup()" > </button> <ng-container *ngIf="showScrollButtons && (hasScrollButtons.position === 'split' || hasScrollButtons.position === 'end')"> <div class="k-toolbar-separator k-toolbar-button-separator k-separator" #scrollSeparator></div> <span *ngIf="hasScrollButtons.position === 'split'" #nextScrollButton kendoToolbarScrollableButton [prev]="false" [overflow]="normalizedOverflow" [title]="getScrollButtonTitle('next')" class="k-toolbar-next k-icon-button k-button k-button-solid k-button-solid-base k-rounded-md" [ngClass]="{ 'k-button-sm': size === 'small', 'k-button-md': size === 'medium' || !size, 'k-button-lg': size === 'large' }" (onClick)="scrollTools($event)"> </span> <div class="k-button-group k-button-group-solid" *ngIf="hasScrollButtons.position === 'end'" #endButtonGroup> <span #prevScrollButton kendoToolbarScrollableButton [prev]="true" [overflow]="normalizedOverflow" [title]="getScrollButtonTitle('prev')" class="k-toolbar-prev k-icon-button k-button k-button-solid k-button-solid-base k-rounded-md" [ngClass]="{ 'k-button-sm': size === 'small', 'k-button-md': size === 'medium' || !size, 'k-button-lg': size === 'large' }" (onClick)="scrollTools($event)"> </span> <span #nextScrollButton kendoToolbarScrollableButton [prev]="false" [overflow]="normalizedOverflow" [title]="getScrollButtonTitle('next')" class="k-toolbar-next k-icon-button k-button k-button-solid k-button-solid-base k-rounded-md" [ngClass]="{ 'k-button-sm': size === 'small', 'k-button-md': size === 'medium' || !size, 'k-button-lg': size === 'large' }" (onClick)="scrollTools($event)"> </span> </div> </ng-container> <ng-template #popu