UNPKG

@progress/kendo-angular-toolbar

Version:

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

1,377 lines (1,362 loc) 211 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import * as i0 from '@angular/core'; import { EventEmitter, Injectable, inject, ElementRef, Directive, ViewChild, Input, Output, forwardRef, Component, HostBinding, ViewContainerRef, ContentChildren, HostListener, isDevMode, ViewChildren, NgModule } from '@angular/core'; import * as i2 from '@progress/kendo-angular-popup'; import { PopupService } from '@progress/kendo-angular-popup'; import { Keys, isPresent as isPresent$1, isDocumentAvailable, guid, ResizeSensorComponent, ResizeBatchService } from '@progress/kendo-angular-common'; import * as i1 from '@progress/kendo-angular-l10n'; import { ComponentMessages, LocalizationService, L10N_PREFIX } from '@progress/kendo-angular-l10n'; import { validatePackage } from '@progress/kendo-licensing'; import { take, filter, takeUntil } from 'rxjs/operators'; import { Subject, Subscription, merge, fromEvent } from 'rxjs'; import { caretAltLeftIcon, caretAltRightIcon, moreHorizontalIcon, moreVerticalIcon, caretAltDownIcon } from '@progress/kendo-svg-icons'; import { ButtonComponent, ButtonGroupComponent, DropDownButtonComponent, SplitButtonComponent } from '@progress/kendo-angular-buttons'; import { NgTemplateOutlet, NgFor, NgIf, NgClass, NgStyle } from '@angular/common'; import { IconWrapperComponent, IconsService } from '@progress/kendo-angular-icons'; import { BadgeComponent, BadgeContainerComponent } from '@progress/kendo-angular-indicators'; /** * @hidden */ const packageMetadata = { name: '@progress/kendo-angular-toolbar', productName: 'Kendo UI for Angular', productCode: 'KENDOUIANGULAR', productCodes: ['KENDOUIANGULAR'], publishDate: 1748538588, version: '19.1.0', licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/' }; /** * @hidden */ class RefreshService { onRefresh = new EventEmitter(); refresh(tool) { this.onRefresh.emit(tool); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RefreshService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RefreshService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RefreshService, decorators: [{ type: Injectable }] }); /** * @hidden */ const focusableRegex = /^(?:a|input|select|textarea|button|object)$/i; /** * @hidden */ function outerWidth(element) { let width = element.offsetWidth; const style = getComputedStyle(element); width += parseFloat(style.marginLeft) || 0 + parseFloat(style.marginRight) || 0; return width; } /** * @hidden */ function innerWidth(element) { let width = element.offsetWidth; const style = getComputedStyle(element); width -= parseFloat(style.paddingLeft) || 0 + parseFloat(style.borderLeftWidth) || 0; width -= parseFloat(style.paddingRight) || 0 + parseFloat(style.borderRightWidth) || 0; return width; } /** * @hidden */ function outerHeight(element) { let width = element.offsetHeight; const style = getComputedStyle(element); width += parseFloat(style.marginTop) || 0 + parseFloat(style.marginBottom) || 0; return width; } /** * @hidden */ const closest = (node, predicate) => { while (node && !predicate(node)) { node = node.parentNode; } return node; }; /** * @hidden */ const isVisible = (element) => { const rect = element.getBoundingClientRect(); const hasSize = rect.width > 0 && rect.height > 0; const hasPosition = rect.x !== 0 && rect.y !== 0; // Elements can have zero size due to styling, but they should still count as visible. // For example, the selection checkbox has no size, but is made visible through styling. return (hasSize || hasPosition) && window.getComputedStyle(element).visibility !== 'hidden'; }; /** * @hidden */ const findElement = (node, predicate, matchSelf = true) => { if (!node) { return; } if (matchSelf && predicate(node)) { return node; } node = node.firstChild; while (node) { if (node.nodeType === 1) { const element = findElement(node, predicate); if (element) { return element; } } node = node.nextSibling; } }; /** * @hidden */ const isFocusable = (element, checkVisibility = true) => { if (element.tagName) { const tagName = element.tagName.toLowerCase(); const tabIndex = element.getAttribute('tabIndex'); let focusable = tabIndex !== null; if (focusableRegex.test(tagName)) { focusable = !element.disabled; } return focusable && (!checkVisibility || isVisible(element)); } return false; }; /** * @hidden */ const findFocusable = (element, checkVisibility = true) => { return findElement(element, node => isFocusable(node, checkVisibility)); }; /** * @hidden */ const findFocusableChild = (element, checkVisibility = true) => { return findElement(element, node => isFocusable(node, checkVisibility), false); }; /** * @hidden */ const findFocusableSibling = (element, checkVisibility = true, reverse) => { let node = reverse ? element.prevSibling : element.nextSibling; while (node) { if (node.nodeType === 1) { const result = findElement(node, el => isFocusable(el, checkVisibility)); if (result) { return result; } } node = reverse ? node.prevSibling : node.nextSibling; } }; /** * @hidden */ const isPresent = (value) => value !== null && value !== undefined; /** * @hidden */ const makePeeker = (collection) => (index) => isPresent(collection[index]); /** * @hidden */ const getIndexOfFocused = (prevKeyCode, nextKeyCode, collection) => (ev) => { switch (ev.type) { case 'keydown': if (ev.keyCode === prevKeyCode) { return collection.length - 1; } if (ev.keyCode === nextKeyCode) { return 0; } break; case 'click': return collection.findIndex(be => be === ev.target || be.contains(ev.target)); case 'focus': return 0; default: return 0; } }; /** * @hidden */ const seekFocusedIndex = (prevKeyCode, nextKeyCode, seeker) => (startIndex, ev) => { switch (ev.keyCode) { case prevKeyCode: return seeker(startIndex - 1) ? startIndex - 1 : startIndex; case nextKeyCode: return seeker(startIndex + 1) ? startIndex + 1 : startIndex; default: return startIndex; } }; /** * @hidden */ const areEqual = (first) => (second) => first === second; /** * @hidden */ const getNextKey = (rtl = false) => (overflows = true) => overflows ? Keys.ArrowDown : rtl ? Keys.ArrowLeft : Keys.ArrowRight; /** * @hidden */ const getPrevKey = (rtl = false) => (overflows = true) => overflows ? Keys.ArrowUp : rtl ? Keys.ArrowRight : Keys.ArrowLeft; /** * @hidden */ const getValueForLocation = (property, displayMode, overflows) => { switch (displayMode) { case 'toolbar': return overflows ? undefined : property; case 'menu': return overflows ? property : undefined; case 'never': return; default: return property; } }; /** * @hidden */ const SIZES = { small: 'sm', medium: 'md', large: 'lg' }; /** * @hidden * * Returns the styling classes to be added and removed */ const getStylingClasses = (componentType, stylingOption, previousValue, newValue) => { switch (stylingOption) { case 'size': return { toRemove: `k-${componentType}-${SIZES[previousValue]}`, toAdd: newValue !== 'none' ? `k-${componentType}-${SIZES[newValue]}` : '' }; case 'fillMode': return { toRemove: `k-${componentType}-${previousValue}`, toAdd: newValue !== 'none' ? `k-${componentType}-${newValue}` : '' }; default: break; } }; /** * @hidden * * Checks whether a Node is Text or Element node. * nodeType 1 is Element, nodeType 3 is Text */ const isElementOrTextNode = n => n.nodeType === 1 || n.nodeType === 3; /** * @hidden */ const normalizeOverflowSettings = (overflow) => { const defaultOverflowSettings = { mode: 'none', scrollButtons: 'auto', scrollButtonsPosition: 'split' }; let normalizedSettings = {}; if (typeof overflow === 'object') { normalizedSettings = Object.assign(defaultOverflowSettings, overflow); } else if (typeof overflow === 'boolean') { normalizedSettings = overflow ? Object.assign(defaultOverflowSettings, { mode: 'menu' }) : defaultOverflowSettings; } else { normalizedSettings = Object.assign(defaultOverflowSettings, { mode: overflow }); } return normalizedSettings; }; /** * @hidden */ class NavigationService { zone; overflowButton; focused = { renderedTool: null, index: -1 }; renderedTools = []; isPopupFocused = false; isOverflowButtonFocused = false; constructor(zone) { this.zone = zone; } setRenderedTools(rts) { this.renderedTools = rts; } click({ context, event: ev }) { if (this.focused.renderedTool !== context && ev) { this.focus(context, ev); } } moveFocusToToolBar() { this.isPopupFocused = false; this.focusOverflowButton(); } moveFocusToPopup() { this.isPopupFocused = true; this.resetNavigation(); this.focus(); } focusNext(ev) { if (this.isOverflowButtonFocused) { const firstFocusableRT = this.getFocusableTools()[0]; this.focus(firstFocusableRT, ev); } else if (!this.isOverflowButtonFocused && this.focused.renderedTool && !this.focused.renderedTool.tool.handleKey(ev)) { const nextRT = this.getFocusableTools().slice(this.focused.index + 1)[0]; if (nextRT) { this.focus(nextRT, ev); } else { if (this.isOverflowButtonVisible() && !this.isPopupFocused) { this.focusOverflowButton(); } else { const firstRT = this.getFocusableTools()[0]; this.focus(firstRT, ev); } } } } focusPrev(ev) { if (this.isOverflowButtonFocused) { const lastFocusableRT = this.getFocusableTools().reverse()[0]; this.focus(lastFocusableRT, ev); } else if (!this.isOverflowButtonFocused && this.focused.renderedTool && !this.focused.renderedTool.tool.handleKey(ev)) { const prevRT = this.getFocusableTools() .slice(0, this.focused.index) .reverse()[0]; if (prevRT) { this.focus(prevRT, ev); } else { if (this.isOverflowButtonVisible() && !this.isPopupFocused) { this.focusOverflowButton(); } else { const lastRT = this.getFocusableTools().reverse()[0]; this.focus(lastRT, ev); } } } } resetNavigation() { this.blurOverflowButton(); this.focused.renderedTool = null; this.focused.index = -1; } focusFirst(ev) { const firstTool = this.getFocusableTools()[0]; const overFlowButton = this.overflowButton; if (firstTool) { this.focused.renderedTool = firstTool; this.focused.index = this.getFocusableTools().findIndex(rt => rt === firstTool); this.focus(firstTool, ev); } else if (overFlowButton) { overFlowButton.nativeElement.focus(); } } focusLast(ev) { const lastTool = this.getFocusableTools().reverse()[0]; const overFlowButton = this.overflowButton; if (lastTool) { this.focused.renderedTool = lastTool; this.focused.index = this.getFocusableTools().findIndex(rt => rt === lastTool); this.focus(lastTool, ev); } else if (overFlowButton) { overFlowButton.nativeElement.focus(); } } getFocusableTools() { return this.renderedTools.filter(rt => (rt.tool.overflows === this.isPopupFocused) && rt.tool.canFocus()); } focus(renderedTool, ev) { // running the code below in onStable fixes issue #2939 this.zone.onStable.pipe(take(1)).subscribe(() => { if (!renderedTool) { const focusableRTs = this.getFocusableTools(); const lastFocusedRT = focusableRTs.find(rt => rt === this.focused.renderedTool) || focusableRTs[0]; // guard against only disabled tools if (lastFocusedRT) { this.focused.renderedTool = lastFocusedRT; this.focused.index = this.getFocusableTools().findIndex(rt => rt === lastFocusedRT); // if event is undefined, then this means that the tool is the first one in the overflow popup lastFocusedRT.tool.focus(ev); } } else if (renderedTool.tool.canFocus && renderedTool.tool.canFocus()) { this.focused.renderedTool = renderedTool; this.focused.index = this.getFocusableTools().findIndex(rt => rt === renderedTool); renderedTool.tool.focus(ev); this.blurOverflowButton(); } }); } blurOverflowButton() { if (this.overflowButton) { this.isOverflowButtonFocused = false; this.overflowButton.nativeElement.tabIndex = -1; } } focusOverflowButton() { if (this.overflowButton) { this.isOverflowButtonFocused = true; this.overflowButton.nativeElement.tabIndex = 0; this.overflowButton.nativeElement.focus(); } } isOverflowButtonVisible() { return (isPresent(this.overflowButton) && window.getComputedStyle(this.overflowButton.nativeElement).getPropertyValue('visibility') === 'visible'); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavigationService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavigationService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NavigationService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i0.NgZone }]; } }); /** * Represents the Base ToolBar Tool component for Angular. * Extend this class to create custom tools. */ class ToolBarToolComponent { toolbarTemplate; sectionTemplate; popupTemplate; tabIndex = -1; //Focus movement inside the toolbar is managed using roving tabindex. overflows = true; visibility; element; isBuiltInTool = false; /** * @hidden */ isHidden = false; /** * @hidden */ location; constructor() { this.element = inject(ElementRef); } // this should be replaced with showTool: DisplayMode = 'both'; /** * @hidden */ responsive = true; get toolbarDisplay() { return this.overflows ? 'none' : 'inline-flex'; } get overflowDisplay() { return this.overflows ? 'block' : 'none'; } /** * Determines if the tool can be focused. * If the returned value is `false`, the tool will not be part of the keyboard navigation. * @returns `true` if the tool should take part in keyboard navigation. */ canFocus() { return false; } /** * Called when the tool is focused. * The method accepts as argument the original browser event, which can be a `KeyboardEvent`, `MouseEvent` or `FocusEvent`. * @param {Event} _ev - This is the event that caused the tool to be focused. */ focus(_ev) { /* noop */ } /** * Called when the tool is focused and one of the arrow keys is pressed. * The returned boolean value determines whether the `ToolBarComponent` will move the focus to the next/previous `ToolBarToolComponent` * ([see example]({% slug customcontroltypes_toolbar %}#toc-adding-keyboard-navigation)). * @param {KeyboardEvent} _ev - The last pressed arrow key * @returns a boolean value determines whether the focus will move to the next/previous component. */ handleKey(_ev) { return false; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolBarToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ToolBarToolComponent, isStandalone: true, inputs: { responsive: "responsive" }, viewQueries: [{ propertyName: "toolbarTemplate", first: true, predicate: ["toolbarTemplate"], descendants: true, static: true }, { propertyName: "sectionTemplate", first: true, predicate: ["sectionTemplate"], descendants: true, static: true }, { propertyName: "popupTemplate", first: true, predicate: ["popupTemplate"], descendants: true, static: true }], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolBarToolComponent, decorators: [{ type: Directive, args: [{ standalone: true }] }], ctorParameters: function () { return []; }, propDecorators: { toolbarTemplate: [{ type: ViewChild, args: ['toolbarTemplate', { static: true }] }], sectionTemplate: [{ type: ViewChild, args: ['sectionTemplate', { static: true }] }], popupTemplate: [{ type: ViewChild, args: ['popupTemplate', { static: true }] }], responsive: [{ type: Input }] } }); /** * @hidden */ class PreventableEvent { prevented = false; /** * Prevents the default action for a specified event. * In this way, the source component suppresses the built-in behavior that follows the event. */ preventDefault() { this.prevented = true; } /** * If the event is prevented by any of its subscribers, returns `true`. * * @returns `true` if the default action was prevented. Otherwise, returns `false`. */ isDefaultPrevented() { return this.prevented; } } /** * @hidden */ class RendererService { element; renderer; getElement() { return this.element.nativeElement; } querySelector(selector) { return this.element.nativeElement.querySelector(selector); } querySelectorAll(selector) { return this.element.nativeElement.querySelectorAll(selector); } findFocusable() { return findFocusable(this.element.nativeElement, false); } findFocusableChild(element) { if (!element) { element = this.findFocusable(); } return findFocusableChild(element, false); } findNextFocusableSibling(element) { if (!element) { element = this.findFocusable(); } return findFocusableSibling(element, false); } findPrevFocusableSibling(element) { if (!element) { element = this.findFocusable(); } return findFocusableSibling(element, false, true); } setAttribute(element, attr, value) { this.renderer.setAttribute(element, attr, value); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RendererService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RendererService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: RendererService, decorators: [{ type: Injectable }] }); /** * @hidden */ class ToolbarToolsService { renderedToolsChange = new Subject(); overflowToolsChange = new Subject(); renderedTools = []; overflowTools = []; allTools = []; reset() { this.renderedTools = this.overflowTools = this.allTools = []; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolbarToolsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolbarToolsService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolbarToolsService, decorators: [{ type: Injectable }] }); const MIN_SPACER_WIDTH = 18; /** * @hidden */ class ToolBarRendererComponent { renderer; rendererService; refreshService; toolsService; viewContainer; tool; location; resizable; rendererClick = new EventEmitter(); template; element; get isSpacer() { return this.tool && this.tool.__isSpacer; } refreshSubscription; internalComponentRef; constructor(renderer, rendererService, refreshService, toolsService, viewContainer) { this.renderer = renderer; this.rendererService = rendererService; this.refreshService = refreshService; this.toolsService = toolsService; this.viewContainer = viewContainer; } ngOnInit() { this.tool.location = this.location; this.element = this.tool.element; this.rendererService.element = this.element; this.rendererService.renderer = this; this.refreshSubscription = this.refreshService.onRefresh.subscribe((tool) => { if (this.tool === tool) { this.refresh(); } }); } ngOnDestroy() { this.refreshSubscription?.unsubscribe(); this.internalComponentRef?.removeEventListener('click', this.onClick); } ngAfterViewInit() { const viewContainerRootNodes = this.viewContainer.get(0)?.rootNodes?.filter(isElementOrTextNode); if (!viewContainerRootNodes || viewContainerRootNodes.length === 0) { return; } this.internalComponentRef = viewContainerRootNodes[0]; this.internalComponentRef.addEventListener('click', this.onClick); if (this.resizable) { if (this.location === 'toolbar') { this.template = this.tool.toolbarTemplate; this.hideTool(); } else if (this.location === 'section') { this.template = this.tool.toolbarTemplate; if (this.tool.isHidden) { this.hideTool(); } else { this.renderer.setStyle(this.internalComponentRef, 'visibility', 'visible'); this.renderer.setStyle(this.internalComponentRef, 'display', 'inline-flex'); } } else { this.template = this.tool.popupTemplate; if (this.tool.isHidden) { this.hideTool(); } else { this.renderer.setStyle(this.internalComponentRef, 'display', 'none'); } } } else { this.tool.overflows = false; this.template = this.tool.toolbarTemplate; if (this.tool.isHidden) { this.hideTool(); } else { this.renderer.setStyle(this.internalComponentRef, 'visibility', 'visible'); this.renderer.setStyle(this.internalComponentRef, 'display', 'inline-flex'); } } if (this.resizable) { this.refresh(); } this.updateTools(); } /** * @hidden */ get width() { if (this.isSpacer) { return MIN_SPACER_WIDTH; } if (!this.internalComponentRef) { return; } return this.tool.overflows ? 0 : outerWidth(this.internalComponentRef); } isDisplayed() { return this.internalComponentRef?.style?.display !== 'none'; } refresh() { this.tool.location = this.location; if (!isPresent$1(this.internalComponentRef)) { return; } if (this.tool.isHidden) { this.hideTool(); } else if (this.resizable) { if (this.location === 'toolbar') { this.renderer.setStyle(this.internalComponentRef, 'visibility', this.tool.visibility); this.renderer.setStyle(this.internalComponentRef, 'display', this.tool.toolbarDisplay); } else { this.renderer.setStyle(this.internalComponentRef, 'display', this.tool.overflowDisplay); } } else { this.renderer.setStyle(this.internalComponentRef, 'visibility', 'visible'); this.renderer.setStyle(this.internalComponentRef, 'display', 'inline-flex'); } this.updateTools(); } setAttribute(element, attr, value) { this.renderer.setAttribute(element, attr, value); } onClick = (ev) => { this.rendererClick.emit({ context: this, event: ev }); }; updateTools() { this.tool.location = this.location; const isInToolbar = this.toolsService.renderedTools.some(t => t.tool === this.tool); const isInPopup = this.toolsService.overflowTools.some(t => t.tool === this.tool); if (this.location === 'toolbar') { isInPopup && (this.toolsService.overflowTools = this.toolsService.overflowTools.filter(t => t.tool !== this.tool)); !isInToolbar && this.toolsService.renderedTools.push(this); } else { if (!isInPopup) { this.toolsService.overflowTools.push(this); this.toolsService.overflowTools.sort((t1, t2) => { // ensures correct navigation order in Popup return this.toolsService.allTools.indexOf(t1.tool) - this.toolsService.allTools.indexOf(t2.tool); }); } } } hideTool() { this.renderer.setStyle(this.internalComponentRef, 'visibility', 'hidden'); this.renderer.setStyle(this.internalComponentRef, 'display', 'none'); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolBarRendererComponent, deps: [{ token: i0.Renderer2 }, { token: RendererService }, { token: RefreshService }, { token: ToolbarToolsService }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ToolBarRendererComponent, isStandalone: true, selector: "[kendoToolbarRenderer]", inputs: { tool: "tool", location: "location", resizable: "resizable" }, outputs: { rendererClick: "rendererClick" }, providers: [RendererService], ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolBarRendererComponent, decorators: [{ type: Directive, args: [{ providers: [RendererService], standalone: true, selector: '[kendoToolbarRenderer]' }] }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: RendererService }, { type: RefreshService }, { type: ToolbarToolsService }, { type: i0.ViewContainerRef }]; }, propDecorators: { tool: [{ type: Input }], location: [{ type: Input }], resizable: [{ type: Input }], rendererClick: [{ type: Output }] } }); /** * @hidden */ class ToolbarMessages extends ComponentMessages { /** * The title of the **More Tools** button in a responsive ToolBar */ moreToolsTitle; /** * The title for the **Previous Tool** button when the Toolbar is scrollable. */ previousToolButton; /** * The title for the **Next Tool** button when the Toolbar is scrollable. */ nextToolButton; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolbarMessages, deps: null, target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: ToolbarMessages, selector: "kendo-toolbar-messages-base", inputs: { moreToolsTitle: "moreToolsTitle", previousToolButton: "previousToolButton", nextToolButton: "nextToolButton" }, usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolbarMessages, decorators: [{ type: Directive, args: [{ // eslint-disable-next-line @angular-eslint/directive-selector selector: 'kendo-toolbar-messages-base' }] }], propDecorators: { moreToolsTitle: [{ type: Input }], previousToolButton: [{ type: Input }], nextToolButton: [{ type: Input }] } }); /** * @hidden */ class LocalizedToolbarMessagesDirective extends ToolbarMessages { service; constructor(service) { super(); this.service = service; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: LocalizedToolbarMessagesDirective, deps: [{ token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.12", type: LocalizedToolbarMessagesDirective, isStandalone: true, selector: "[kendoToolbarLocalizedMessages]", providers: [ { provide: ToolbarMessages, useExisting: forwardRef(() => LocalizedToolbarMessagesDirective) } ], usesInheritance: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: LocalizedToolbarMessagesDirective, decorators: [{ type: Directive, args: [{ providers: [ { provide: ToolbarMessages, useExisting: forwardRef(() => LocalizedToolbarMessagesDirective) } ], selector: '[kendoToolbarLocalizedMessages]', standalone: true }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }]; } }); const DEFAULT_SCROLL_BEHAVIOR = 'smooth'; const DEFAULT_SCROLL_SPEED = 100; /** * @hidden */ class ScrollService { ngZone; localization; owner; position = 0; scrollButtonActiveStateChange = new Subject(); get scrollElement() { return this.owner.scrollContainer?.nativeElement; } get scrollContainerOverflowSize() { if (!isDocumentAvailable()) { return 0; } if (!this.scrollElement) { return 0; } const overflowSize = Math.floor(this.scrollElement.scrollWidth - this.scrollElement.getBoundingClientRect().width); return overflowSize < 0 ? 0 : overflowSize; } get toolsOverflow() { return this.scrollContainerOverflowSize > 0; } constructor(ngZone, localization) { this.ngZone = ngZone; this.localization = localization; } toggleScrollButtonsState() { const toolbar = this.owner; if (!toolbar.hasScrollButtons) { return; } const currentPrevButtonActive = !this.isDisabled('prev'); const currentNextButtonActive = !this.isDisabled('next'); const defaultOffset = 1; const rtlDelta = this.localization.rtl ? -1 : 1; const calculatedPrevButtonActive = (this.position * rtlDelta) > 0 && this.scrollContainerOverflowSize > 0; const calculatedNextButtonActive = (this.position * rtlDelta) < this.scrollContainerOverflowSize - defaultOffset && this.scrollContainerOverflowSize > 0; if (calculatedPrevButtonActive !== currentPrevButtonActive) { this.ngZone.run(() => this.toggleButtonActiveState('prev', calculatedPrevButtonActive)); } if (calculatedNextButtonActive !== currentNextButtonActive) { this.ngZone.run(() => this.toggleButtonActiveState('next', calculatedNextButtonActive)); } } onScroll(e) { this.position = e.target.scrollLeft; this.toggleScrollButtonsState(); } scrollTools(direction) { this.calculateListPosition(direction, DEFAULT_SCROLL_SPEED); if (this.scrollElement) { this.scrollElement.scrollTo({ left: this.position, behavior: DEFAULT_SCROLL_BEHAVIOR }); } this.toggleScrollButtonsState(); } updateScrollPosition(element) { this.position = element.scrollLeft; } calculateListPosition(direction, scrollSpeed) { if (direction === 'prev') { if (!this.localization.rtl) { this.position = this.position - scrollSpeed <= 0 ? 0 : this.position - scrollSpeed; } else { this.position = this.position + scrollSpeed >= 0 ? 0 : this.position + scrollSpeed; } } else if (direction === 'next' && this.position < this.scrollContainerOverflowSize) { if (this.position + scrollSpeed > this.scrollContainerOverflowSize) { if (this.localization.rtl) { this.position = -this.scrollContainerOverflowSize; } else { this.position = this.scrollContainerOverflowSize; } return; } if (this.localization.rtl) { this.position -= scrollSpeed; } else { this.position += scrollSpeed; } } } toggleButtonActiveState(buttonType, active) { this.scrollButtonActiveStateChange.next({ buttonType, active }); } isDisabled = (buttonType) => this.owner[`${buttonType}ScrollButton`]?.nativeElement.classList.contains('k-disabled'); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScrollService, deps: [{ token: i0.NgZone }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScrollService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ScrollService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i1.LocalizationService }]; } }); const DIRECTION_CLASSES = { left: 'caret-alt-left', right: 'caret-alt-right' }; /** * @hidden */ class ToolbarScrollableButtonComponent { host; renderer; ngZone; localization; get prevClass() { return this.prev; } get nextClass() { return !this.prev; } role = 'button'; prev = false; overflow; onClick = new EventEmitter(); get iconClass() { return this.scrollButtonIconClass; } get customIconClass() { return this.customScrollButtonIconClass; } get svgIcon() { return this.scrollButtonSVGIcon; } caretAltLeftIcon = caretAltLeftIcon; caretAltRightIcon = caretAltRightIcon; subs = new Subscription(); constructor(host, renderer, ngZone, localization) { this.host = host; this.renderer = renderer; this.ngZone = ngZone; this.localization = localization; } ngAfterViewInit() { this.ngZone.runOutsideAngular(() => { this.subs.add(this.renderer.listen(this.host.nativeElement, 'click', this.clickHandler)); }); } ngOnDestroy() { this.subs.unsubscribe(); } clickHandler = () => { const buttonType = this.prev ? 'prev' : 'next'; this.onClick.emit(buttonType); }; get scrollButtonIconClass() { const defaultPrevIcon = !this.localization.rtl ? DIRECTION_CLASSES.left : DIRECTION_CLASSES.right; const defaultNextIcon = !this.localization.rtl ? DIRECTION_CLASSES.right : DIRECTION_CLASSES.left; if (typeof this.overflow === 'object') { const prevIcon = typeof this.overflow.prevButtonIcon === 'undefined' ? defaultPrevIcon : ''; const nextIcon = typeof this.overflow.nextButtonIcon === 'undefined' ? defaultNextIcon : ''; if (prevIcon && this.prev) { return prevIcon; } else if (nextIcon && !this.prev) { return nextIcon; } } } get customScrollButtonIconClass() { if (typeof this.overflow === 'object') { const prevIcon = this.overflow.prevButtonIcon; const nextIcon = this.overflow.nextButtonIcon; if (prevIcon && this.prev) { return `k-icon ${prevIcon}`; } if (nextIcon && !this.prev) { return `k-icon ${nextIcon}`; } } } get scrollButtonSVGIcon() { const defaultPrevSVGIcon = !this.localization.rtl ? this.caretAltLeftIcon : this.caretAltRightIcon; const defaultNextSVGIcon = !this.localization.rtl ? this.caretAltRightIcon : this.caretAltLeftIcon; if (typeof this.overflow === 'object') { const prevIcon = this.overflow.prevSVGButtonIcon !== undefined ? this.overflow.prevSVGButtonIcon : defaultPrevSVGIcon; const nextIcon = this.overflow.nextSVGButtonIcon !== undefined ? this.overflow.nextSVGButtonIcon : defaultNextSVGIcon; if (prevIcon || nextIcon) { return this.prev ? prevIcon : nextIcon; } } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolbarScrollableButtonComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i1.LocalizationService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ToolbarScrollableButtonComponent, isStandalone: true, selector: "[kendoToolbarScrollableButton]", inputs: { prev: "prev", overflow: "overflow" }, outputs: { onClick: "onClick" }, host: { properties: { "class.k-toolbar-prev": "this.prevClass", "class.k-toolbar-next": "this.nextClass", "attr.role": "this.role" } }, ngImport: i0, template: ` <kendo-icon-wrapper [name]="iconClass" [customFontClass]="customIconClass" [svgIcon]="svgIcon" innerCssClass="k-button-icon" > </kendo-icon-wrapper> `, isInline: true, dependencies: [{ kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ToolbarScrollableButtonComponent, decorators: [{ type: Component, args: [{ template: ` <kendo-icon-wrapper [name]="iconClass" [customFontClass]="customIconClass" [svgIcon]="svgIcon" innerCssClass="k-button-icon" > </kendo-icon-wrapper> `, // eslint-disable-next-line @angular-eslint/component-selector selector: '[kendoToolbarScrollableButton]', standalone: true, imports: [IconWrapperComponent] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i1.LocalizationService }]; }, propDecorators: { prevClass: [{ type: HostBinding, args: ['class.k-toolbar-prev'] }], nextClass: [{ type: HostBinding, args: ['class.k-toolbar-next'] }], role: [{ type: HostBinding, args: ['attr.role'] }], prev: [{ type: Input }], overflow: [{ type: Input }], onClick: [{ type: Output }] } }); /* eslint-disable no-case-declarations */ 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 %}). */ 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) {