@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
JavaScript
/**-----------------------------------------------------------------------------------------
* 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`—Controls the popup animation. By default, the open and close animations are enabled.
* - `popupClass: String`—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) {