UNPKG

@progress/kendo-angular-dropdowns

Version:

A wide variety of native Angular dropdown components including AutoComplete, ComboBox, DropDownList, DropDownTree, MultiColumnComboBox, MultiSelect, and MultiSelectTree

2,356 lines 96.8 kB
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, forwardRef, HostBinding, Injector, Input, isDevMode, NgZone, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
import { NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { anyChanged, guid, hasObservers, Keys, KendoInput, isDocumentAvailable, EventsOutsideAngularDirective, ResizeSensorComponent, TemplateContextDirective } from '@progress/kendo-angular-common';
import { AdaptiveService } from '@progress/kendo-angular-utils';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { NavigationService } from '../common/navigation/navigation.service';
import { PopupService } from '@progress/kendo-angular-popup';
import { DataBoundComponent, ExpandableComponent, TreeViewComponent } from '@progress/kendo-angular-treeview';
import { DataService } from '../common/data.service';
import { DisabledItemsService } from '../common/disabled-items/disabled-items.service';
import { SelectionService } from '../common/selection/selection.service';
import { PreventableEvent } from '../common/models/preventable-event';
import { NavigationAction } from '../common/navigation/navigation-action';
import { RemoveTagEvent } from '../common/models/remove-tag-event';
import { MultiSelectTreeMessages } from '../common/constants/error-messages';
import { animationDuration, fetchDescendentNodes, getFillModeClass, getRoundedClass, getSearchableItems, getSizeClass, hasProps, isArray, isLetter, isObject, isObjectArray, isPresent, isTruthy, isUntouched, noop, parseNumber, setActionSheetTitle, updateActionSheetAdaptiveAppearance, valueFrom } from '../common/util';
import { HeaderTemplateDirective } from '../common/templates/header-template.directive';
import { FooterTemplateDirective } from '../common/templates/footer-template.directive';
import { NodeTemplateDirective } from './templates/node-template.directive';
import { NoDataTemplateDirective } from '../common/templates/no-data-template.directive';
import { TagTemplateDirective } from '../common/templates/tag-template.directive';
import { GroupTagTemplateDirective } from '../common/templates/group-tag-template.directive';
import { merge, of, Subject, Subscription } from 'rxjs';
import { debounceTime, filter, tap } from 'rxjs/operators';
import { buildTreeItem, MultiSelectTreeLookupService, nodeIndex } from './lookup/lookup.service';
import { searchIcon, xIcon } from '@progress/kendo-svg-icons';
import { ResponsiveRendererComponent } from '../common/action-sheet.component';
import { CheckDirective } from './checked-state/check.directive';
import { CheckAllDirective } from './checked-state/check-all.directive';
import { FilterInputDirective } from '../common/filter-input.directive';
import { NgIf, NgTemplateOutlet, NgClass } from '@angular/common';
import { TagListComponent } from '../common/taglist.component';
import { LocalizedMessagesDirective } from '../common/localization/localized-messages.directive';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { touchEnabled } from '@progress/kendo-common';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-popup";
import * as i2 from "../common/navigation/navigation.service";
import * as i3 from "@progress/kendo-angular-l10n";
import * as i4 from "./lookup/lookup.service";
import * as i5 from "@progress/kendo-angular-utils";
const DEFAULT_POPUP_SETTINGS = { animate: true };
const DEFAULT_CHECKABLE_SETTINGS = { checkChildren: true, checkOnClick: true };
const hasChildren = () => false;
const fetchChildren = () => of([]);
const itemDisabled = () => false;
const isNodeVisible = () => true;
const DEFAULT_SIZE = 'medium';
const DEFAULT_ROUNDED = 'medium';
const DEFAULT_FILL_MODE = 'solid';
/**
 * Represents the [Kendo UI MultiSelectTree component for Angular]({% slug overview_multiselecttree %}).
 */
export class MultiSelectTreeComponent {
    injector;
    wrapper;
    popupService;
    renderer;
    navigationService;
    _zone;
    localization;
    cdr;
    lookup;
    adaptiveService;
    /**
     * @hidden
     */
    touchEnabled = touchEnabled;
    /**
     * @hidden
     */
    animationDuration = animationDuration;
    /**
     * @hidden
     */
    searchIcon = searchIcon;
    /**
     * @hidden
     */
    xIcon = xIcon;
    hostClasses = true;
    get isDisabled() {
        return this.disabled || null;
    }
    treeViewId = `k-${guid()}`;
    get hostAriaAutocomplete() {
        return this.filterable ? 'list' : null;
    }
    get isLoading() {
        return this.loading;
    }
    get hostAriaInvalid() {
        return this.formControl?.invalid ? true : null;
    }
    get isBusy() {
        return this.loading ? 'true' : null;
    }
    get id() {
        return this.focusableId;
    }
    direction;
    get hostTabIndex() {
        return this.tabindex;
    }
    role = 'combobox';
    ariaHasPopup = 'tree';
    get isReadonly() {
        return this.readonly ? '' : null;
    }
    get ariaDescribedBy() {
        return this.tagListId;
    }
    get ariaActiveDescendant() {
        return this.focusedTagId;
    }
    /**
     * @hidden
     */
    get formControl() {
        const ngControl = this.injector.get(NgControl, null);
        return ngControl?.control || null;
    }
    /**
     * @hidden
     */
    onFilterChange(text) {
        if (this.filterable) {
            this.filterChange.emit(text);
        }
    }
    /**
     * @hidden
     */
    onExpand() {
        this._searchableNodes = getSearchableItems(this.treeViewId, this.actionSheet.element.nativeElement);
        this.isActionSheetExpanded = true;
    }
    /**
     * @hidden
     */
    onCollapse() {
        this.isActionSheetExpanded = false;
    }
    /**
     * Enables or disables the adaptive mode. By default the adaptive rendering is disabled.
     */
    adaptiveMode = 'none';
    /**
     * Sets the title of the ActionSheet that is rendered instead of the Popup when using small screen devices.
     * By default the ActionSheet title uses the text provided for the label of the AutoComplete.
     */
    title = '';
    /**
     * Sets the subtitle of the ActionSheet that is rendered instead of the Popup when using small screen devices.
     * By default the ActionSheet subtitle uses the text provided for the `placeholder` of the AutoComplete.
    */
    set subtitle(_subtitle) {
        this._subtitle = _subtitle;
    }
    get subtitle() {
        return this._subtitle || this.placeholder;
    }
    /**
     * @hidden
     */
    get isAdaptiveModeEnabled() {
        return this.adaptiveMode === 'auto';
    }
    /**
    * @hidden
    */
    windowSize = 'large';
    /**
     * @hidden
     */
    isActionSheetExpanded = false;
    /**
     * @hidden
     */
    handleKeydown(event, input) {
        if (event.target === this.filterInput?.nativeElement &&
            (event.keyCode === Keys.ArrowLeft || event.keyCode === Keys.ArrowRight)) {
            return;
        }
        if (input) {
            event.stopImmediatePropagation();
        }
        const deleteTag = this.isWrapperActive && event.keyCode === Keys.Backspace && this.tags.length > 0;
        if (deleteTag) {
            this.handleBackspace();
            return;
        }
        if (this.disabled || this.readonly) {
            return;
        }
        if (!this.isFilterActive && isLetter(event.key) && !this.actionSheetSearchBar?.onFocus) {
            this.printableCharacters.next(event.key);
        }
        const eventData = event;
        const action = this.navigationService.process({
            originalEvent: eventData,
            openOnSpace: !this.isOpen,
            closeOnSpace: false
        });
        if (action === NavigationAction.Open) {
            eventData.preventDefault();
        }
        if (this.isOpen && action === NavigationAction.Enter) {
            const spaceKeyDownEvent = new KeyboardEvent('keydown', { 'key': ' ', 'code': 'Space', 'keyCode': 32, 'which': 32 });
            this.treeview?.element.nativeElement.dispatchEvent(spaceKeyDownEvent);
        }
    }
    /**
     * @hidden
     */
    responsiveRendererComponent;
    /**
     * @hidden
     */
    get actionSheet() {
        return this.responsiveRendererComponent?.actionSheet;
    }
    /**
     * @hidden
     */
    get actionSheetSearchBar() {
        return this.responsiveRendererComponent?.actionSheetSearchBar;
    }
    /**
     * @hidden
     */
    get isAdaptive() {
        return this.isAdaptiveModeEnabled && this.windowSize !== 'large';
    }
    headerTemplate;
    footerTemplate;
    nodeTemplate;
    noDataTemplate;
    tagTemplate;
    groupTagTemplate;
    popupTemplate;
    container;
    set treeview(treeview) {
        this._treeview = treeview;
        if (treeview) {
            // If filtering is enabled, focus the TreeView on mobile devices instead of the filter input
            if (this.isFocused && !this.filterable && !this.checkAll || this.touchEnabled) {
                treeview.focus();
            }
            /**
             * the treeview animations are initially disabled (we don't want expand animations during popup opening)
             * re-enables the animations for user interaction
             * The Promise is required to properly change the `animate` property when
             * the popup is appended to a container and opened upon initialization.
             * Otherwise, the "Expression has changed..." type error will be thrown.
             */
            Promise.resolve(null).then(() => this.treeview.animate = true);
        }
    }
    get treeview() {
        return this._treeview;
    }
    filterInput;
    checkAllInput;
    /**
     * Specifies the [`tabindex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) of the component.
     */
    set tabindex(value) {
        const providedTabIndex = parseNumber(value);
        const defaultTabIndex = 0;
        this._tabindex = !isNaN(providedTabIndex) ? providedTabIndex : defaultTabIndex;
    }
    get tabindex() {
        return this.disabled ? -1 : this._tabindex;
    }
    /**
     * Sets the size of the component.
     *
     * The possible values are:
     * * `small`
     * * `medium` (default)
     * * `large`
     * * `none`
     *
     */
    set size(size) {
        const newSize = size ? size : DEFAULT_SIZE;
        this.renderer.removeClass(this.wrapper.nativeElement, getSizeClass('input', this.size));
        if (size !== 'none') {
            this.renderer.addClass(this.wrapper.nativeElement, getSizeClass('input', newSize));
        }
        this._size = newSize;
    }
    get size() {
        return this._size;
    }
    /**
     * Sets the border radius of the component.
     *
     * The possible values are:
     * * `small`
     * * `medium` (default)
     * * `large`
     * * `full`
     * * `none`
     *
     */
    set rounded(rounded) {
        const newRounded = rounded ? rounded : DEFAULT_ROUNDED;
        this.renderer.removeClass(this.wrapper.nativeElement, getRoundedClass(this.rounded));
        if (rounded !== 'none') {
            this.renderer.addClass(this.wrapper.nativeElement, getRoundedClass(newRounded));
        }
        this._rounded = newRounded;
    }
    get rounded() {
        return this._rounded;
    }
    /**
     * Sets the fillMode of the component.
     *
     * The possible values are:
     * * `flat`
     * * `solid` (default)
     * * `outline`
     * * `none`
     *
     */
    set fillMode(fillMode) {
        const newFillMode = fillMode ? fillMode : DEFAULT_FILL_MODE;
        this.renderer.removeClass(this.wrapper.nativeElement, getFillModeClass('input', this.fillMode));
        if (fillMode !== 'none') {
            this.renderer.addClass(this.wrapper.nativeElement, getFillModeClass('input', newFillMode));
        }
        this._fillMode = newFillMode;
    }
    get fillMode() {
        return this._fillMode;
    }
    /**
     * Configures the popup of the MultiSelectTree.
     *
     * The available options are:
     * - `animate: Boolean`—Controls the popup animation. By default, the open and close animations are enabled.
     * - `width: Number | String`—Sets the width of the popup container. By default, the width of the host element is used. If set to `auto`, the component automatically adjusts the width of the popup and no item labels are wrapped.
     * - `height: Number`—Sets the height of the popup container.
     * - `popupClass: String`—Specifies a list of CSS classes that are used to style the popup.
     * - `appendTo: "root" | "component" | ViewContainerRef`—Specifies the component to which the popup will be appended.
     */
    set popupSettings(settings) {
        this._popupSettings = Object.assign({}, DEFAULT_POPUP_SETTINGS, settings);
        // `detectChanges` needed, otherwise upon value initialization and `appendTo` property
        // an error is thrown => ExpressionChangedAfterItHasBeenCheckedError
        this.cdr.detectChanges();
    }
    get popupSettings() {
        return this._popupSettings;
    }
    /**
     * Defines the checkable settings of the MultiSelecTree nodes.
     * If no value is provided, the default [`CheckableSettings`]({% slug api_dropdowns_multiselecttreecheckablesettings %}) are applied.
     */
    set checkableSettings(settings) {
        this._checkableSettings = Object.assign({}, DEFAULT_CHECKABLE_SETTINGS, settings);
    }
    get checkableSettings() {
        return this._checkableSettings;
    }
    /**
     * Sets the data of the MultiSelectTree.
     *
     * > The data has to be provided in an array-like list with objects.
     */
    set data(data) {
        this._nodes = data;
        this.setState();
        if (this.isContentInit) {
            // Needed for when the data is loaded later/asynchronously because it would not exist on ngContentInit
            this.registerLookupItems(data);
        }
    }
    get data() {
        return this._nodes;
    }
    /**
     * Sets the value of the MultiSelectTree.
     * It can either be of the primitive (string, numbers) or of the complex (objects) type.
     * To define the type, use the `valuePrimitive` option.
     *
     */
    set value(value) {
        this._value = value ? value : [];
        this.setState();
    }
    get value() {
        return this._value;
    }
    /**
     * Keeps the current `dataItems` object in order to resolve selection.
     * Needs to be provided when when programmatically setting a `value` and `valuePrimitive` is set to `true`.
     */
    set dataItems(items) {
        this._dataItems = (items || []).map((dataItem, index) => {
            if (hasProps(dataItem, ['dataItem', 'index', 'level'])) {
                return dataItem;
            }
            const level = this.valueDepth[index] || 0;
            const key = valueFrom({ dataItem, level }, this.valueField) + '_' + (this.isHeterogeneous ? this.valueDepth[index] : 0);
            return {
                dataItem,
                index: null,
                level,
                key
            };
        });
        this.setState();
    }
    get dataItems() {
        return this._dataItems || this.value.map((value, index) => {
            const level = this.valueDepth[index] || 0;
            const key = valueFrom({ dataItem: value, level }, this.valueField) + '_' + (this.isHeterogeneous ? this.valueDepth[index] : 0);
            return {
                dataItem: value,
                index: null,
                level,
                key
            };
        });
    }
    /**
     * The fields of the data item that provide the text content of the nodes inside the
     * MultiSelectTree ([see example]({% slug databinding_multiselecttree %})). If the `textField`
     * input is set to an array, each hierarchical level uses the field that corresponds
     * to the same index in the array, or the last item in the array.
     *
     * > The `textField` property can be set to point to a nested property value - e.g. `category.name`.
     */
    textField;
    /**
     * The fields of the data item that provide the value of the nodes inside the
     * MultiSelectTree ([see example]({% slug databinding_multiselecttree %})). If the `valueField`
     * input is set to an array, each hierarchical level uses the field that corresponds
     * to the same index in the array, or the last item in the array.
     *
     * > The `valueField` property can be set to point to a nested property value - e.g. `category.id`.
     */
    valueField;
    /**
     * Sets the levels in the data set where the values can be found when `valueField` is an Array.
     * The field serves to correctly allocate a data item used when the MultiSelectTree is initialized with a value.
     */
    valueDepth = [];
    /**
     * Sets and gets the loading state of the MultiSelectTree.
     */
    loading;
    /**
     * The hint which is displayed when the component is empty.
     */
    placeholder = '';
    /**
     * Sets the height of the options list in the popup. By default, `listHeight` is 200px.
     *
     * > The `listHeight` property affects only the list of options and not the whole popup container.
     * > To set the height of the popup container, use `popupSettings.height`.
     *
     * > When using `adaptiveMode` and the screen size is `small` or `medium` the `listHeight` property is set to null.
     */
    set listHeight(_listHeight) {
        this._listHeight = _listHeight;
    }
    get listHeight() {
        if (this.isAdaptive) {
            return;
        }
        return this._listHeight;
    }
    _listHeight = 200;
    /**
     * Sets the disabled state of the component. To learn how to disable the component in reactive forms, refer to the article on [Forms Support](slug:formssupport_multiselecttree#toc-managing-the-multiselecttree-disabled-state-in-reactive-forms).
     */
    disabled = false;
    /**
     * Sets the read-only state of the component.
     *
     * @default false
     */
    readonly = false;
    /**
     * Specifies the type of the selected value
     * ([more information and example]({% slug valuebinding_multiselecttree %}#toc-primitive-values)).
     * If set to `true`, the selected value has to be a primitive one.
     */
    valuePrimitive = false;
    /**
     * Indicates whether the child nodes will be fetched on node expand or will be initially prefetched.
     * @default false
     */
    loadOnDemand = false;
    /**
     * @hidden
     *
     * Used by the kendo-label and kendo-floatinglabel to access and associate the focusable element with the provided label via aria-labelledby.
     */
    focusableId = `k-${guid()}`;
    /**
     * If set to `true`, renders a button on hovering over the component.
     * Clicking this button resets the value of the component to `undefined` and triggers the `change` event.
     * @default true
     */
    clearButton = true;
    /**
     * Renders the built-in input element for filtering the MultiSelectTree.
     * If set to `true`, the component emits the `filterChange` event, which can be used to [filter the MultiSelectTree manually]({% slug filtering_multiselecttree %}#toc-manual-filtering).
     * A built-in filtering implementation is available to use with the [`kendoMultiSelectTreeHierarchyBinding`]({% slug api_dropdowns_multiselecttreehierarchybindingdirective %}) and [`kendoMultiSelectTreeFlatBinding`]({% slug api_dropdowns_multiselecttreeflatbindingdirective %}) directives.
     * @default false
     */
    filterable = false;
    /**
     * If `checkАll` is set to `true` and the checkboxes are enabled, a tri-state checkbox appears above the embedded treeview.
     * Clicking the checkbox checks or unchecks all enabled items of the treeview that are loaded.
     * @default false
     */
    checkAll = false;
    /**
     * A function which determines if a specific node has child nodes.
     */
    hasChildren = hasChildren;
    /**
     * A function which provides the child nodes for a given parent node.
     */
    fetchChildren = fetchChildren;
    /**
     * A function that is executed for each data item and determines if a specific node is expanded.
     */
    isNodeExpanded;
    /**
     * A callback which determines whether a tree node should be rendered as hidden. The utility .k-hidden class is used to hide the nodes.
     * Useful for custom filtering implementations.
     */
    isNodeVisible = isNodeVisible;
    /**
     * A function that is executed for each data item and determines if a specific item is disabled.
     */
    itemDisabled = itemDisabled;
    /**
     * A user-defined callback function which receives an array of selected data items and maps them to an array of tags.
     *
     * @param { Any[] } dataItems - The selected data items from the list.
     * @returns { Any[] } - The tags that will be rendered by the component.
     */
    tagMapper = (tags) => tags || [];
    /**
     * Fires each time the user focuses the MultiSelectTree.
     */
    onFocus = new EventEmitter();
    /**
     * Fires each time the MultiSelectTree gets blurred.
     */
    onBlur = new EventEmitter();
    /**
     * Fires each time the popup is about to open
     * ([see example]({% slug openstate_multiselecttree %})).
     * This event is preventable. If you cancel it, the popup will remain closed.
     */
    open = new EventEmitter();
    /**
     * Fires after the popup has been opened.
     */
    opened = new EventEmitter();
    /**
     * Fires each time the popup is about to close
     * ([see example]({% slug openstate_multiselecttree %})).
     * This event is preventable. If you cancel it, the popup will remain open.
     */
    close = new EventEmitter();
    /**
     * Fires after the popup has been closed.
     */
    closed = new EventEmitter();
    /**
     * Fires when the user expands a node in the popup TreeView.
     */
    nodeExpand = new EventEmitter();
    /**
     * Fires when the user collapses a node in the popup TreeView.
     */
    nodeCollapse = new EventEmitter();
    /**
     * Fires each time the value is changed
     * ([see example](slug:events_multiselecttree)).
     */
    valueChange = new EventEmitter();
    /**
     * Fires each time a tag is about to be removed([see examples]({% slug summarytagmode_multiselect %}#toc-notifying-on-removing-group-tags)).
     * This event is preventable. If you cancel it, the tag will not be removed.
     */
    removeTag = new EventEmitter();
    /**
     * Fires when the value of the built-in filter input element changes.
     */
    filterChange = new EventEmitter();
    /**
     * @hidden
     */
    get focusedTagId() {
        if (!isPresent(this.focusedTagIndex) || this.isOpen) {
            return null;
        }
        return this.tagPrefix + '-' + this.focusedTagIndex;
    }
    set isFocused(isFocused) {
        this.renderer[isFocused ? 'addClass' : 'removeClass'](this.wrapper.nativeElement, 'k-focus');
        this._isFocused = isFocused;
    }
    get isFocused() {
        return this._isFocused;
    }
    /**
     * Returns the current open state. Returns `true` if the popup or actionSheet is open.
     */
    get isOpen() {
        return isTruthy(isPresent(this.popupRef) || this.isActionSheetExpanded);
    }
    get width() {
        const wrapperWidth = this.wrapper.nativeElement.offsetWidth;
        const width = this.popupSettings.width || wrapperWidth;
        const minWidth = isNaN(wrapperWidth) ? wrapperWidth : `${wrapperWidth}px`;
        const maxWidth = isNaN(width) ? width : `${width}px`;
        return { min: minWidth, max: maxWidth };
    }
    get height() {
        const popupHeight = this.popupSettings.height;
        return isPresent(popupHeight) ? `${popupHeight}px` : 'auto';
    }
    get appendTo() {
        const { appendTo } = this.popupSettings;
        if (!appendTo || appendTo === 'root') {
            return undefined;
        }
        return appendTo === 'component' ? this.container : appendTo;
    }
    /**
     * @hidden
     */
    get popupContainerClasses() {
        const containerClasses = ['k-multiselecttree-popup'];
        if (this.popupSettings.popupClass) {
            containerClasses.push(this.popupSettings.popupClass);
        }
        return containerClasses;
    }
    /**
     * @hidden
     *
     * Alias for `data`. Used for compatibility with the `DataBoundComponent` interface.
     * Required for the data-binding directives.
     */
    set nodes(nodes) {
        this.data = nodes;
    }
    get nodes() {
        return this.data;
    }
    /**
     * @hidden
     *
     * Alias for `fetchChildren`. Used for compatibility with the `DataBoundComponent` interface.
     * Required for the data-binding directives
     */
    set children(callback) {
        this.fetchChildren = callback;
    }
    get children() {
        return this.fetchChildren;
    }
    /**
     * @hidden
     *
     * Alias for `nodeExpand`. Used for compatibility with the `ExpandableComponent` interface.
     * Required for the expand-directive.
     */
    get expand() {
        return this.nodeExpand;
    }
    /**
     * @hidden
     *
     * Alias for `nodeCollapse`. Used for compatibility with the `ExpandableComponent` interface.
     * Required for the expand-directive.
     */
    get collapse() {
        return this.nodeCollapse;
    }
    /**
     * @hidden
     *
     * Alias for `isNodeExpanded`. Used for compatibility with the `ExpandableComponent` interface.
     * Required for the expand-directive.
     */
    set isExpanded(callback) {
        this.isNodeExpanded = callback;
    }
    get isExpanded() {
        return this.isNodeExpanded;
    }
    /**
     * @hidden
     *
     * Alias for `isNodeVisible`. Used for compatibility with the `DataBoundComponent` interface.
     * The `DataBoundComponent` interface is used in the data-binding directives.
     */
    set isVisible(callback) {
        this.isNodeVisible = callback;
    }
    get isVisible() {
        return this.isNodeVisible;
    }
    get isTagFocused() {
        return !this.isOpen && this.focusedTagIndex !== undefined;
    }
    get isTreeViewActive() {
        return this.treeview && this.treeview.isActive;
    }
    get isWrapperActive() {
        return document.activeElement === this.wrapper.nativeElement;
    }
    get isFilterActive() {
        return this.filterInput && document.activeElement === this.filterInput.nativeElement;
    }
    get isCheckAllActive() {
        return this.checkAllInput && document.activeElement === this.checkAllInput.nativeElement;
    }
    /**
     * @hidden
     */
    onResize() {
        const currentWindowSize = this.adaptiveService.size;
        if (this.isAdaptiveModeEnabled && this.windowSize !== currentWindowSize) {
            if (this.isOpen) {
                this.togglePopup(false);
            }
            this.windowSize = currentWindowSize;
        }
        if (this.isOpen && !this.isActionSheetExpanded) {
            const popupWrapper = this.popupRef.popupElement;
            const { min, max } = this.width;
            popupWrapper.style.minWidth = min;
            popupWrapper.style.width = max;
        }
    }
    /**
     * @hidden
     */
    filterStateChange = new EventEmitter();
    /**
     * @hidden
     */
    filter = '';
    /**
     * @hidden
     */
    checkedItems = [];
    /**
     * @hidden
     * The flag is needed in order to determine how to construct the items map keys.
     * If `true`, then the key consists of the item's value and level (depth),
     * else the key consists of the item's value and 0 (no leveling required)
     */
    isHeterogeneous;
    /**
     * @hidden
     */
    showAfter = 0;
    /**
     * @hidden
     */
    allNodesHidden = false;
    tagListId = `k-${guid()}`;
    tagPrefix = "tag-" + guid();
    popupRef;
    tags;
    focusedTagIndex = undefined;
    disabledIndices;
    _subtitle;
    _nodes;
    _value = [];
    _tabindex = 0;
    _popupSettings = DEFAULT_POPUP_SETTINGS;
    _checkableSettings = DEFAULT_CHECKABLE_SETTINGS;
    _isFocused = false;
    _treeview;
    _dataItems;
    _tempValue;
    _initiallyCheckedItems = [];
    _size = 'medium';
    _rounded = 'medium';
    _fillMode = 'solid';
    _searchableNodes = [];
    _typedValue = '';
    printableCharacters = new Subject();
    subs = new Subscription();
    // Keep an instance of the last focused node for when the popup close is prevented
    // in order to be able to properly restore the focus
    lastNodeOnFocus;
    // Used as check to avoid unnecessary 'registerLookupItems()' calls upon initialization
    isContentInit;
    constructor(injector, wrapper, popupService, renderer, navigationService, _zone, localization, cdr, lookup, adaptiveService) {
        this.injector = injector;
        this.wrapper = wrapper;
        this.popupService = popupService;
        this.renderer = renderer;
        this.navigationService = navigationService;
        this._zone = _zone;
        this.localization = localization;
        this.cdr = cdr;
        this.lookup = lookup;
        this.adaptiveService = adaptiveService;
        this.direction = localization.rtl ? 'rtl' : 'ltr';
        this.subscribeEvents();
        this.subscribeFocusEvents();
    }
    ngOnInit() {
        this.subs.add(this.printableCharacters.pipe(tap((char) => {
            this._typedValue += char;
            const itemToFocus = this._searchableNodes.find((node) => {
                return node.text.toLowerCase().indexOf(this._typedValue) === 0;
            });
            this.treeview.focus(itemToFocus?.index);
        }), debounceTime(1000)).subscribe(() => {
            this._typedValue = '';
        }));
        this.renderer.removeAttribute(this.wrapper.nativeElement, 'tabindex');
        this.renderer.setAttribute(this.wrapper.nativeElement, 'aria-expanded', String(this.isOpen));
        this.subs.add(this.localization
            .changes.subscribe(({ rtl }) => {
            this.direction = rtl ? 'rtl' : 'ltr';
            this.cdr.markForCheck();
        }));
        this.setComponentClasses();
        this._initiallyCheckedItems = [...this.checkedItems];
    }
    ngAfterViewInit() {
        this.windowSize = this.adaptiveService.size;
        this.subs.add(this.renderer.listen(this.wrapper.nativeElement, 'click', this.handleClick.bind(this)));
        this.subs.add(this.renderer.listen(this.wrapper.nativeElement, 'keydown', this.handleKeydown.bind(this)));
        if (this.actionSheet && isDocumentAvailable()) {
            // The following syntax is used as it does not violate CSP compliance
            this.actionSheet.element.nativeElement.style.setProperty('--kendo-actionsheet-height', '60vh');
            this.actionSheet.element.nativeElement.style.setProperty('--kendo-actionsheet-max-height', 'none');
        }
    }
    /**
     * @hidden
     */
    ngOnDestroy() {
        this.destroyPopup();
        this.unsubscribeEvents();
    }
    /**
     * @hidden
     */
    ngOnChanges(changes) {
        if (anyChanged(['textField', 'valueField', 'valuePrimitive'], changes, false)) {
            this.isHeterogeneous = this.valueField && isArray(this.valueField);
            this.setState();
        }
        if (anyChanged(['valueDepth', 'value', 'dataItems'], changes, false)) {
            if (changes['value'] && !changes['dataItems'] && !this.valuePrimitive) {
                // Update the dataItems if the value is updated programmatically (non-primitive values only)
                // In the primitive case, the client should update the dataItems as well
                this.dataItems = this.value;
            }
            else {
                // Re-map the dataItems because `valueDepth` is not yet available when the check directive parses the items
                this.dataItems = this.dataItems.map((item, index) => ({
                    ...item,
                    key: valueFrom({ dataItem: item.dataItem, index: null, level: this.valueDepth[index] || 0 }, this.valueField) + '_' + (this.isHeterogeneous ? this.valueDepth[index] : 0),
                    level: this.valueDepth[index] || 0
                }));
            }
        }
        if (anyChanged(['data', 'children', 'hasChildren', 'loadOnDemand', 'valueField'], changes, true) && !this.loadOnDemand) {
            this.lookup.reset();
            this.registerLookupItems(this.data);
        }
    }
    /**
     * @hidden
     */
    ngAfterContentChecked() {
        this.verifySettings();
        if (this.data?.length > 0 && this.popupRef) {
            this.allNodesHidden = this.areNodesHidden(this.data);
        }
    }
    /**
     * @hidden
     */
    applyValue() {
        this.value = this._tempValue;
        this._initiallyCheckedItems = [...this.checkedItems];
        this.emitValueChange(this.value);
        this.setTags();
        this.toggle(false);
    }
    /**
     * @hidden
     */
    cancelValue() {
        this.checkedItems = [...this._initiallyCheckedItems];
        this.togglePopup(false);
    }
    ngAfterContentInit() {
        this.isContentInit = true;
        // Still need to keep the call of 'registerLookupItems()' from ngAfterContentInit in the cases when the data is passed initially
        // The call is execute here because we have to make sure it happens after all input properties are loaded (not the case in the data setter initially)
        this.registerLookupItems(this.data);
    }
    /**
     * @hidden
     *
     * Used by the kendo-floatinglabel component to determine if the floating label
     * should be rendered inside the input when the component is not focused.
     */
    isEmpty() {
        return !this.placeholder && (!isPresent(this.value) || this.value.length === 0);
    }
    /**
     * Focuses the MultiSelectTree.
     */
    focus() {
        if (!this.disabled) {
            this.wrapper.nativeElement.focus();
        }
    }
    /**
     * Blurs the MultiSelectTree.
     */
    blur() {
        if (!this.disabled) {
            this.wrapper.nativeElement.blur();
        }
    }
    /**
     * Focuses a specific item of the MultiSelectTree based on a provided index in the format of `1_1`.
     * The targeted item should be expanded in order for it to be focused.
     * If null or invalid index is provided the focus will be set on the first item.
     */
    focusItemAt(index) {
        if (this.treeview) {
            const lookup = this.treeview.itemLookup(index);
            const isItemDisabled = !isPresent(lookup) || this.treeview.isDisabled(lookup.item.dataItem, lookup.item.index);
            if (!isItemDisabled) {
                this.treeview.focus(index);
            }
        }
    }
    /**
     * Resets the value of the MultiSelectTree.
     * If you use the `reset` method to clear the value of the component,
     * the model will not update automatically and the `valueChange` event will not be fired.
     */
    reset() {
        this.value = [];
        this.dataItems = [];
        this.valueDepth = [];
    }
    /**
     * Toggles the visibility of the popup or actionSheet
     * ([see example]({% slug openstate_multiselecttree %})).
     * If you use the `toggle` method to open or close the popup, the `open` and `close` events will not be fired.
     *
     * @param open - The state of the popup.
     */
    toggle(open) {
        // The Promise is required to open the popup on load.
        // Otherwise, the "ViewContainerRef not found..." error will be thrown.
        Promise.resolve(null).then(() => {
            const shouldOpen = isPresent(open) ? open : !isPresent(this.popupRef);
            this.destroyPopup();
            if (shouldOpen) {
                this.createPopup();
            }
        });
    }
    /**
     * @hidden
     */
    handleFocus(event) {
        if (event.target !== this.wrapper.nativeElement) {
            return;
        }
        event.stopImmediatePropagation();
        if (!this.isFocused) {
            this.isFocused = true;
            if (hasObservers(this.onFocus)) {
                this._zone.run(() => {
                    this.onFocus.emit();
                });
            }
            // Re-focus the treeview if `close` is prevented
            if (this.isOpen && this.treeview) {
                if (this.lastNodeOnFocus) {
                    this.lastNodeOnFocus.setAttribute('tabindex', '0');
                }
                this.treeview.focus();
            }
        }
    }
    /**
     * @hidden
     */
    handleBlur(e) {
        if (!this.isActionSheetExpanded) {
            const relatedTarget = e && e.relatedTarget;
            if (this.wrapper.nativeElement.contains(relatedTarget) ||
                (this.isOpen && this.popupRef.popupElement.contains(relatedTarget))) {
                return;
            }
            this.isFocused = false;
            this.togglePopup(false);
            //this is needed for Ang 18 not to throw ng0100 error when closing the popup
            //the component could be refactored using kendoDropDownSharedEvents directive
            //once we are able to debug against Angular 18
            this.cdr.markForCheck();
            if (hasObservers(this.onBlur) ||
                isUntouched(this.wrapper.nativeElement) ||
                this.formControl?.updateOn === 'blur') {
                this._zone.run(() => {
                    this.onBlur.emit();
                    this.onTouchedCallback();
                });
            }
        }
    }
    /**
     * @hidden
     */
    handleNodeClick(node) {
        if (!this.isFocused) {
            // Re-focus the MultiSelectTree when popup close is prevented and a node is clicked
            // On click the focus should be on the clicked element which is why we need to update the lastNodeOnFocus
            const parent = node.originalEvent.target.parentElement.parentElement;
            this.lastNodeOnFocus = parent;
            this.focus();
        }
    }
    /**
     * @hidden
     */
    togglePopup(open) {
        const isDisabled = this.disabled || this.readonly;
        const sameState = this.isOpen === open;
        this._zone.run(() => {
            this.focusedTagIndex = undefined;
        });
        if (isDisabled || sameState) {
            return;
        }
        const togglePrevented = this.triggerPopupEvents(open);
        if (!togglePrevented) {
            if (open) {
                this.createPopup();
            }
            else {
                this.destroyPopup();
            }
        }
        else {
            this.removeTreeViewFromTabOrder();
        }
    }
    /**
     * @hidden
     */
    messageFor(key) {
        return this.localization.get(key);
    }
    lastAction = 'check';
    /**
     * @hidden
     */
    handleCheckedItemsChange(items) {
        this.valueDepth = items.map(item => item.level);
        this.lastAction = items.length > this.dataItems.length ? 'check' : 'uncheck';
        this.dataItems = items.slice();
        this.updateValue(this.dataItems);
    }
    /**
     * @hidden
     */
    handleRemoveTag({ tag, index }) {
        if (this.disabled || this.readonly) {
            return;
        }
        const eventArgs = new RemoveTagEvent(tag);
        this.removeTag.emit(eventArgs);
        if (eventArgs.isDefaultPrevented()) {
            return;
        }
        // Remove tags based on their position index
        if (tag instanceof Array) {
            // Remove group tag
            this.dataItems = this.dataItems.filter((_item, i) => i < this.showAfter || this.disabledIndices.has(i));
            this.valueDepth = this.valueDepth.filter((_item, i) => i < this.showAfter || this.disabledIndices.has(i));
        }
        else if (this.loadOnDemand) {
            // Remove single tag when the child items are fetched on demand
            this.dataItems = this.dataItems.filter((_item, i) => i !== index || this.disabledIndices.has(i));
            this.valueDepth = this.valueDepth.filter((_item, i) => i !== index || this.disabledIndices.has(i));
        }
        else {
            // Remove single tag when the child items are pre-fetched
            const dataItem = this.dataItems.find(item => item.tagPositionIndex === index);
            const itemKey = dataItem.key;
            const lookup = this.lookup.itemLookup(itemKey);
            const pendingCheck = [lookup.item];
            if (this.checkableSettings.checkChildren) {
                fetchDescendentNodes(lookup)
                    .forEach(lookup => pendingCheck.push(lookup.item));
                pendingCheck.push(...this.removeParents(lookup.parent));
            }
            const keysToRemove = pendingCheck.map(item => item.key);
            // Holds the position indexes of the items to be removed
            const valueDepthIndices = [];
            this.dataItems = this.dataItems.filter((_item, i) => {
                const shouldStay = !keysToRemove.includes(_item.key) || this.disabledIndices.has(i);
                if (!shouldStay) {
                    // We need to know the index position of the data item to be able to update the valueDepth array accordignly
                    // as each data item's position is corresponding to the same position in valueDepth
                    valueDepthIndices.push(i);
                }
                return shouldStay;
            });
            this.valueDepth = this.valueDepth.filter((_item, i) => {
                return !valueDepthIndices.includes(i) || this.disabledIndices.has(i);
            });
        }
        this.updateValue(this.dataItems);
        if (!this.isFocused) {
            this.focus();
        }
    }
    /**
     * @hidden
     */
    handleTagMapperChange(showAfter) {
        this.showAfter = parseNumber(showAfter);
        this.setTags();
    }
    /**
     * @hidden
     */
    clearAll(event) {
        event.stopImmediatePropagation();
        event.preventDefault();
        this.focus();
        this.value = this.value.filter((_item, index) => this.disabledIndices.has(index));
        this.dataItems = this.dataItems.filter((_item, index) => this.disabledIndices.has(index));
        this.valueDepth = this.valueDepth.filter((_depth, index) => this.disabledIndices.has(index));
        this.emitValueChange(this.value);
    }
    /**
     * @hidden
     */
    writeValue(value) {
        if (!this.valuePrimitive && isPresent(value)) {
            this.dataItems = value;
        }
        if (!isPresent(value) && isPresent(this.value)) {
            this.dataItems = null;
        }
        this.value = value || [];
    }
    /**
     * @hidden
     */
    registerOnChange(fn) {
        this.onChangeCallback = fn;
    }
    /**
     * @hidden
     */
    registerOnTouched(fn) {
        this.onTouchedCallback = fn;
    }
    /**
     * @hidden
     */
    setDisabledState(isDisabled) {
        this.disabled = isDisabled;
        this.cdr.markForCheck();
    }
    /**
     * @hidden
     */
    handleFilterInputChange(input) {
        this.filter = input.value;
        this.filterChange.next(input.value);
    }
    /**
     * @hidden
     */
    get filterInputClasses() {
        return `${this.size ? getSizeClass('input', this.size) : ''} ${this.fillMode ? 'k-input-' + this.fillMode : ''} ${this.rounded ? getRoundedClass(this.rounded) : ''}`;
    }
    /**
     * @hidden
     */
    get checkAllCheckboxClasses() {
        return `${this.size ? getSizeClass('checkbox', this.size) : ''}`;
    }
    /**
     * @hidden
     */
    toggleCheckAll() {
        this.checkAllInput.nativeElement.focus();
        this.checkAllInput.nativeElement.click();
    }
    onTouchedCallback = noop;
    onChangeCallback = noop;
    verifySettings() {
        if (!isDevMode()) {
            return;
        }
        if (!isPresent(this.valueField) || !isPresent(this.textField)) {
            throw new Error(MultiSelectTreeMessages.textAndValue);
        }
        if (!isArray(this.value)) {
            throw new Error(MultiSelectTreeMessages.array);
        }
        if (this.value.length > 0) {
            if (this.valuePrimitive && this.value.some(item => isObject(item))) {
                throw new Error(MultiSelectTreeMessages.primitive);
            }
            const isEveryDataItemObject = this.dataItems.every(item => isObject(item.dataItem));
            if (this.valuePrimitive && !isArray(this.dataItems)) {
                throw new Error(MultiSelectTreeMessages.dataItems);
            }
            if (this.valuePrimitive && !isEveryDataItemObject) {
                throw new Error(MultiSelectTreeMessages.dataItems);
            }
            if (this.valuePrimitive && this.dataItems.length !== this.value.length) {
                throw new Error(MultiSelectTreeMessages.dataItemsLength);
            }
            if (!this.valuePrimitive && !isObjectArray(this.value)) {
                throw new Error(MultiSelectTreeMessages.object);
            }
            if ((isArray(this.valueField) || isArray(this.textField)) && !isArray(this.valueDepth)) {
                throw new Error(MultiSelectTreeMessages.valueDepth);
            }
            if ((isArray(this.valueField) || isArray(this.textField)) && this.valueDepth.length === 0) {
                throw new Error(MultiSelectTreeMessages.valueDepth);
            }
            if ((isArray(this.valueField) || isArray(this.textField)) && this.valueDepth.length !== this.value.length) {
                throw new Error(MultiSelectTreeMessages.valueDepthLength);
            }
        }
    }
    areNodesHidden(nodes) {
        return nodes.every((node, index) => !this.isVisible(node, String(index)));
    }
    emitValueChange(value) {
        this.onChangeCallback(value);
        this.valueChange.emit(value);
    }
    triggerPopupEvents(open) {
        const eventArgs = new PreventableEvent();
        if (hasObservers(this.open) || hasObservers(this.close)) {
            this._zone.run(() => {
                if (open) {
                    this.open.emit(eventArgs);
                }
                else {
                    this.close.emit(eventArgs);
                }
            });
        }
        return eventArgs.isDefaultPrevented();
    }
    createPopup() {
        this.windowSize = this.adaptiveService.size;
        if (this.isAdaptive) {
            this.openActionSheet();
            this.cdr.detectChanges();
            return;
        }
        const horizontalAlign = this.direction === "rtl" ? "right" : "left";
        const anchorPosition = { horizontal: horizontalAlign, vertical: 'bottom' };
        const popupPosition = { horizontal: horizontalAlign, vertical: 'top' };
        const appendToComponent = typeof this.popupSettings.appendTo === 'string' && this.popupSettings.appendTo === 'component';
        this.popupRef = this.popupService.open({
            anchor: this.wrapper,
            appendTo: this.appendTo,
            anchorAlign: anchorPosition,
            animate: this.popupSettings.animate,
            content: this.popupTemplate,
            popupAlign: popupPosition,
            positionMode: appendToComponent ? 'fixed' : 'absolute',
            popupClass: this.popupContainerClasses
        });
        const popupWrapper = this.popupRef.popupElement;
        const { min, max } = this.width;
        if (!this.appendTo) {
            this.renderer.setAttribute(popupWrapper, 'role', 'region');
            this.renderer.setAttribute(popupWrapper, 'aria-label', this.messageFor('popupLabel'));
        }
        popupWrapper.style.minWidth = min;
        popupWrapper.style.width = max;
        popupWrapper.style.height = this.height;
        this.renderer.setAttribute(this.wrapper.nativeElement, 'aria-controls', this.treeViewId);
        this.renderer.setAttribute(popupWrapper, 'dir', this.direction);
        this.renderer.setAttribute(this.wrapper.nativeElement, 'aria-expanded', 'true');
        this.popupRef.popupOpen.subscribe(() => {
            this.cdr.detectChanges();
            this.opened.emit();
            this._searchableNodes = getSearchableItems(this.treeViewId, this.popupRef.popupElement);
        });
        this.popupRef.popupClose.subscribe(() => {
            if (hasObservers(this.closed)) {
                this._zone.run(() => {
                    this.closed.emit();
                });
            }
        });
    }
    destroyPopup() {
        if (this.isActionSheetExpanded) {
            this.closeActionSheet();
        }
        if (this.popupRef) {
            this.popupRef.close();
            this.popupRef = null;
            this.renderer.setAttribute(this.wrapper.nativeElement, 'aria-expanded', 'false');
            this.renderer.removeAttribute(this.wrapper.nativeElement, 'aria-controls');
            if (this.filter !== "") {
                this.filter = "";
                this.allNodesHidden = false;
                if (hasObservers(this.filterChange)) {
                    this._zone.run(() => {
                        this.filterChange.emit("");
                    });
                }
            }
        }
    }
    handleClick(e) {
        // The check is needed otherwise when appended to the component, the popup reopens on click
        // https://github.com/telerik/kendo-angular/issues/3738
        if ((this.popupRef && this.popupRef.popupElement.contains(e.target)) || this.isActionSheetExpanded) {
            return;
        }
        this.togglePopup(!this.isOpen);
    }
    subscribeEvents() {
        [
            this.navigationService.open.subscribe((event) => {
                event.originalEvent.preventDefault();
                this.togglePopup(true);
            }),
            this.navigationService.enter
                .pipe(tap((event) => event.originalEvent.preventDefault()))
                .subscribe(() => this.togglePopup(true)),
            merge(this.navigationService.close, this.navigationService.esc).subscribe((event) => {
                event.originalEvent.preventDefault();
                this.focus();
                this.togglePopup(false);
            }),
            this.navigationService.tab.subscribe(this.handleTabKey.bind(this)),
            this.navigationService.up.subscribe(this.handleUpKey.bind(this)),
            this.navigationService.down.subscribe(this.handleDownKey.bind(this)),
            this.navigationService.left
                .pipe(filter(() => !this.isTreeViewActive))
                .subscribe(this.direction === 'rtl' ? this.handleRightKey.bind(this) : this.handleLeftKey.bind(this)),
            this.navigationService.right
                .pipe(filter(() => !this.isTreeViewActive))
                .subscribe(this.direction === 'rtl' ? this.handleLeftKey.bind(this) : this.handleRightKey.bind(this)),
            this.navigationService.home.pipe(filter(() => !this.isOpen)).subscribe(this.handleHome.bind(this)),
            this.navigationService.end.pipe(filter(() => !this.isOpen)).subscribe(this.handleEnd.bind(this)),
            this.navigationService.backspace.pipe(filter(() => this.isTagFocused)).subscribe(this.handleBackspace.bind(this)),
            this.navigationService.delete.pipe(filter(() => this.isTagFocused)).subscribe(this.handleDelete.bind(this))
        ].forEach(sub => this.subs.add(sub));
    }
    subscribeFocusEvents() {
        if (isDocumentAvailable()) {
            this.handleFocus = this.handleFocus.bind(this);
            this.handleDocumentBlur = this.handleDocumentBlur.bind(this);
            this._zone.runOutsideAngular(() => {
                const useCapture = true;
                document.addEventListener('focus', this.handleFocus, useCapture);
                document.addEventListener('blur', this.handleDocumentBlur, useCapture);
            });
        }
    }
    unSubscribeFocusEvents() {
        if (isDocumentAvailable()) {
            const useCapture = true;
            document.removeEventListener('focus', this.handleFocus, useCapture);
            document.removeEventListener('blur', this.handleDocumentBlur, useCapture);
        }
    }
    handleDocumentBlur(event) {
        if (event.target !== this.wrapper.nativeElement) {
            return;
        }
        event.stopImmediatePropagation();
        this.handleBlur(event);
    }
    handleTabKey() {
        if (!this.isActionSheetExpanded) {
            this.focus();
        }
        if (this.isOpen) {
            this.treeview.blur();
            this.removeTreeViewFromTabOrder();
        }
    }
    handleUpKey(event) {
        if (!this.treeview) {
            return;
        }
        event.originalEvent.preventDefault();
        // Prevent toggling the focus between the filterInput and the wrapper elements with `up` key
        if (this.isWrapperActive) {
            return;
        }
        const isFirstNodeActive = this.treeview['navigationService']['activeIndex'] === '0';
        // Current focus is on the filter input => should focus the wrapper
        if (this.filterable && this.isFilterActive) {
            this.focus();
            // Current focus is on the treeview first node => should focus the check all checkbox if enabled
        }
        else if (this.checkAll && !this.isCheckAllActive && isFirstNodeActive) {
            this.checkAllInput.nativeElement.focus();
            // Current focus is either on the check all checkbox or the treeview's first node
            // => should focus either the filter input (if enabled) or the wrapper
        }
        else if (this.isCheckAllActive || isFirstNodeActive) {
            if (this.filterable) {
                this.isActionSheetExpanded ? this.actionSheetSearchBar.focus() : this.filterInput.nativeElement.focus();
            }
            else {
                if (!this.isActionSheetExpanded) {
                    this.focus();
                }
            }
        }
    }
    handleDownKey(event) {
        if (!this.treeview) {
            return;
        }
        event.originalEvent.preventDefault();
        // Current focus is on the wrapper => should focus the filter input
        if (this.filterable && this.isWrapperActive) {
            this.filterInput.nativeElement.focus();
            // Current focus is on the wrapper/filter input => should focus check all checkbox if enabled
        }
        else if (this.checkAll && (this.isWrapperActive || this.isFilterActive)) {
            this.checkAllInput.nativeElement.focus();
            // Should focus the treeview if filterable and check all are disabled
        }
        else if (!this.treeview.isActive) {
            this.treeview.focus();
        }
        this.focusedTagIndex = undefined;
    }
    handleRightKey(event) {
        event.originalEvent.preventDefault();
        const last = this.tags.length - 1;
        if (this.focusedTagIndex === last) {
            this.focusedTagIndex = undefined;
        }
        else if (this.focusedTagIndex < last) {
            this.focusedTagIndex++;
        }
        else if (!this.focusedTagIndex) {
            this.focusedTagIndex = 0;
        }
    }
    handleLeftKey(event) {
        event.originalEvent.preventDefault();
        if (this.focusedTagIndex === undefined || this.focusedTagIndex < 0) {
            this.focusedTagIndex = this.tags.length - 1;
        }
        else if (this.focusedTagIndex !== 0) {
            this.focusedTagIndex--;
        }
    }
    handleEnd(event) {
        event.originalEvent.preventDefault();
        this.focusedTagIndex = this.tags.length - 1;
    }
    handleHome(event) {
        event.originalEvent.preventDefault();
        this.focusedTagIndex = 0;
    }
    handleBackspace() {
        if (this.focusedTagIndex !== undefined) {
            this.handleDelete();
        }
        else {
            const tag = this.tags[this.tags.length - 1];
            const index = this.tags.length - 1;
            this.handleRemoveTag({ tag, index });
        }
    }
    handleDelete() {
        const tag = this.tags[this.focusedTagIndex];
        const index = this.focusedTagIndex;
        this.handleRemoveTag({ tag, index });
        if (this.focusedTagIndex === this.tags.length) {
            this.focusedTagIndex = undefined;
        }
    }
    unsubscribeEvents() {
        this.subs.unsubscribe();
        this.unSubscribeFocusEvents();
    }
    /**
     * Remove the `TreeView` from the tab order, otherwise a focus loop between the page elements will occur
     * and the user will not be able to tab to the rest of the browser elements
     */
    removeTreeViewFromTabOrder() {
        const nodes = this.treeview.element.nativeElement.querySelectorAll('li');
        nodes.forEach(item => {
            if (item.getAttribute('tabindex') === '0') {
                this.lastNodeOnFocus = item;
                this.lastNodeOnFocus.setAttribute('tabindex', '-1');
            }
        });
    }
    setState() {
        if (isPresent(this.dataItems) && isPresent(this.valueField)) {
            if (!this.isActionSheetExpanded) {
                this.setTags();
            }
            this.checkedItems = this.dataItems.slice();
        }
        this.cdr.markForCheck();
    }
    setTags() {
        const source = this.dataItems.map(item => item.dataItem);
        this.tags = this.tagMapper(source);
        this.disabledIndices = this.disabledItemsMapper();
        // Create a mapping of tags to position indices
        const tagIndexMap = new Map(this.tags.map((tag, index) => [JSON.stringify(tag), index]));
        // Modify dataItems by adding a property to hold the item's position as displayed in the tags
        this.dataItems.forEach(item => {
            const serializedDataItem = JSON.stringify(item.dataItem);
            item.tagPositionIndex = tagIndexMap.has(serializedDataItem)
                ? tagIndexMap.get(serializedDataItem) // Use the index from tags if it exists
                : null; // Assign a null value if the dataItem is not visible in the tags
        });
    }
    updateValue(value) {
        const newValue = this.valuePrimitive ?
            value.map(item => valueFrom(item, this.valueField)) :
            value.map(item => item.dataItem);
        if (this.isActionSheetExpanded) {
            this._tempValue = newValue.slice();
        }
        else {
            this.value = newValue;
            this.emitValueChange(this.value);
        }
    }
    /**
     * @hidden
     */
    onChildrenLoaded() {
        setTimeout(() => {
            if (this.popupRef) {
                this._searchableNodes = getSearchableItems(this.treeViewId, this.popupRef.popupElement);
            }
            if (this.isActionSheetExpanded) {
                this._searchableNodes = getSearchableItems(this.treeViewId, this.actionSheet.element.nativeElement);
            }
        });
    }
    /**
     * @hidden
     *
     * Determines which of the provided tags should be disabled and stores their position indices
     */
    disabledItemsMapper() {
        return new Set(this.dataItems.reduce((indices, item, index) => {
            if (this.itemDisabled(item.dataItem, item.index)) {
                indices.push(index);
            }
            return indices;
        }, []));
    }
    setComponentClasses() {
        if (this.size !== 'none') {
            this.renderer.addClass(this.wrapper.nativeElement, getSizeClass('input', this.size));
        }
        if (this.rounded !== 'none') {
            this.renderer.addClass(this.wrapper.nativeElement, getRoundedClass(this.rounded));
        }
        if (this.fillMode !== 'none') {
            this.renderer.addClass(this.wrapper.nativeElement, getFillModeClass('input', this.fillMode));
        }
    }
    removeParents(parent) {
        let currentParent = parent;
        const nodes = [];
        while (currentParent) {
            nodes.push(currentParent.item);
            currentParent = currentParent.parent;
        }
        return nodes;
    }
    /**
     * Creates an internal map of the available tree items to be used as a reference
     * to retrieve the item's children/parent and determine the checked sate
     */
    registerLookupItems(data, parentItem = null, levelIndex = 0) {
        if (!isPresent(data) || data.length === 0) {
            return;
        }
        const parentIndex = nodeIndex(parentItem);
        const treeItems = data.map((node) => buildTreeItem(node, this.valueField, levelIndex));
        if (isPresent(parentItem)) {
            this.lookup.registerChildren(parentIndex, treeItems);
        }
        treeItems.forEach(item => {
            this.lookup.registerItem(item, parentItem);
            if (!this.loadOnDemand) {
                this.registerChildLookupItems(item, levelIndex);
            }
        });
    }
    registerChildLookupItems(item, levelIndex) {
        if (this.hasChildren(item.dataItem)) {
            this.children(item.dataItem)
                .subscribe(children => {
                const index = this.isHeterogeneous ? levelIndex + 1 : 0;
                this.registerLookupItems(children, item, index);
            });
        }
    }
    closeActionSheet() {
        this.wrapper.nativeElement.focus();
        this.actionSheet.toggle(false);
        this.renderer.removeAttribute(this.wrapper.nativeElement, 'aria-controls');
        this.renderer.setAttribute(this.wrapper.nativeElement, 'aria-expanded', 'false');
        if (this.filterable) {
            this.actionSheetSearchBar.value = '';
            this.filterChange.emit('');
        }
        this.closed.emit();
    }
    openActionSheet() {
        this.windowSize = this.adaptiveService.size;
        this.actionSheet.toggle(true);
        this.title = setActionSheetTitle(this.wrapper, this.title);
        this.cdr.detectChanges();
        this.renderer.setAttribute(this.wrapper.nativeElement, 'aria-controls', this.treeViewId);
        this.renderer.setAttribute(this.wrapper.nativeElement, 'aria-expanded', 'true');
        updateActionSheetAdaptiveAppearance(this.actionSheet, this.windowSize, this.renderer);
        this.cdr.detectChanges();
        this.opened.emit();
        this.filterable && this.actionSheetSearchBar.focus();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MultiSelectTreeComponent, deps: [{ token: i0.Injector }, { token: i0.ElementRef }, { token: i1.PopupService }, { token: i0.Renderer2 }, { token: i2.NavigationService }, { token: i0.NgZone }, { token: i3.LocalizationService }, { token: i0.ChangeDetectorRef }, { token: i4.MultiSelectTreeLookupService }, { token: i5.AdaptiveService }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: MultiSelectTreeComponent, isStandalone: true, selector: "kendo-multiselecttree", inputs: { adaptiveMode: "adaptiveMode", title: "title", subtitle: "subtitle", tabindex: "tabindex", size: "size", rounded: "rounded", fillMode: "fillMode", popupSettings: "popupSettings", checkableSettings: "checkableSettings", data: "data", value: "value", dataItems: "dataItems", textField: "textField", valueField: "valueField", valueDepth: "valueDepth", loading: "loading", placeholder: "placeholder", listHeight: "listHeight", disabled: "disabled", readonly: "readonly", valuePrimitive: "valuePrimitive", loadOnDemand: "loadOnDemand", focusableId: "focusableId", clearButton: "clearButton", filterable: "filterable", checkAll: "checkAll", hasChildren: "hasChildren", fetchChildren: "fetchChildren", isNodeExpanded: "isNodeExpanded", isNodeVisible: "isNodeVisible", itemDisabled: "itemDisabled", tagMapper: "tagMapper" }, outputs: { onFocus: "focus", onBlur: "blur", open: "open", opened: "opened", close: "close", closed: "closed", nodeExpand: "nodeExpand", nodeCollapse: "nodeCollapse", valueChange: "valueChange", removeTag: "removeTag", filterChange: "filterChange" }, host: { properties: { "class.k-multiselecttree": "this.hostClasses", "class.k-input": "this.hostClasses", "class.k-disabled": "this.isDisabled", "attr.aria-disabled": "this.isDisabled", "attr.aria-autocomplete": "this.hostAriaAutocomplete", "class.k-loading": "this.isLoading", "attr.aria-invalid": "this.hostAriaInvalid", "attr.aria-busy": "this.isBusy", "attr.id": "this.id", "attr.dir": "this.direction", "attr.tabindex": "this.hostTabIndex", "attr.role": "this.role", "attr.aria-haspopup": "this.ariaHasPopup", "attr.readonly": "this.isReadonly", "attr.aria-describedby": "this.ariaDescribedBy", "attr.aria-activedescendant": "this.ariaActiveDescendant", "class.k-readonly": "this.readonly" } }, providers: [
            LocalizationService,
            NavigationService,
            DataService,
            DisabledItemsService,
            SelectionService,
            MultiSelectTreeLookupService,
            {
                provide: L10N_PREFIX,
                useValue: 'kendo.multiselecttree'
            },
            {
                multi: true,
                provide: NG_VALUE_ACCESSOR,
                useExisting: forwardRef(() => MultiSelectTreeComponent)
            },
            {
                provide: DataBoundComponent,
                useExisting: forwardRef(() => MultiSelectTreeComponent)
            },
            {
                provide: ExpandableComponent,
                useExisting: forwardRef(() => MultiSelectTreeComponent)
            },
            {
                provide: KendoInput,
                useExisting: forwardRef(() => MultiSelectTreeComponent)
            }
        ], queries: [{ propertyName: "headerTemplate", first: true, predicate: HeaderTemplateDirective, descendants: true }, { propertyName: "footerTemplate", first: true, predicate: FooterTemplateDirective, descendants: true }, { propertyName: "nodeTemplate", first: true, predicate: NodeTemplateDirective, descendants: true }, { propertyName: "noDataTemplate", first: true, predicate: NoDataTemplateDirective, descendants: true }, { propertyName: "tagTemplate", first: true, predicate: TagTemplateDirective, descendants: true }, { propertyName: "groupTagTemplate", first: true, predicate: GroupTagTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "responsiveRendererComponent", first: true, predicate: ResponsiveRendererComponent, descendants: true }, { propertyName: "popupTemplate", first: true, predicate: ["popupTemplate"], descendants: true, static: true }, { propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "treeview", first: true, predicate: ["treeview"], descendants: true }, { propertyName: "filterInput", first: true, predicate: ["filterInput"], descendants: true }, { propertyName: "checkAllInput", first: true, predicate: ["checkAllInput"], descendants: true }], exportAs: ["kendoMultiSelectTree"], usesOnChanges: true, ngImport: i0, template: `
        <ng-container kendoMultiSelectTreeLocalizedMessages
            i18n-noDataText="kendo.multiselecttree.noDataText|The text displayed in the popup when there are no items"
            noDataText="NO DATA FOUND"

            i18n-clearTitle="kendo.multiselecttree.clearTitle|The title of the clear button"
            clearTitle="clear"

            i18n-checkAllText="kendo.multiselecttree.checkAllText|The text displayed for the check-all checkbox"
            checkAllText="Check all"

            i18n-filterInputLabel="kendo.multiselecttree.filterInputLabel|The text set as aria-label on the filter input"
            filterInputLabel="Filter"

            i18n-popupLabel="kendo.multiselecttree.popupLabel|The label of the popup element that contains the list of options when its role is 'region'"
            popupLabel="Options list"

            i18n-applyButton="kendo.multiselecttree.applyButton|The text of the Apply button in the action sheet"
            applyButton="Apply"

            i18n-cancelButton="kendo.multiselecttree.cancelButton|The text of the Cancel button in the action sheet"
            cancelButton="Cancel"
        >
        </ng-container>
        <kendo-taglist
            class="k-input-values"
            [size]="size"
            [rounded]="rounded"
            [fillMode]="fillMode"
            [id]="tagListId"
            [tags]="tags"
            [focused]="focusedTagIndex"
            [textField]="textField"
            [valueField]="valueField"
            [valueDepth]="valueDepth"
            [disabled]="disabled"
            [tagPrefix]="tagPrefix"
            [template]="tagTemplate"
            [groupTemplate]="groupTagTemplate"
            [disabledIndices]="disabledIndices"
            (removeTag)="handleRemoveTag($event)"
        >
        </kendo-taglist>
        <span class="k-input-inner">
            <span 
                *ngIf="!tags || !tags.length"
                class="k-input-value-text"
            >
                {{ placeholder }}
            </span>
        </span>
        <span
            *ngIf="!disabled && !loading && !readonly && clearButton && tags?.length"
            class="k-clear-value"
            [attr.title]="messageFor('clearTitle')"
            role="button"
            tabindex="-1"
            (click)="clearAll($event)"
        >
            <kendo-icon-wrapper
                class="k-icon"
                name="x"
                [svgIcon]="xIcon"
            >
            </kendo-icon-wrapper>
        </span>
        <span
            *ngIf="loading"
            class="k-icon k-i-loading k-input-loading-icon"
        >
        </span>
        <ng-template #popupTemplate>
            <ng-container *ngTemplateOutlet="sharedPopupActionSheetTemplate"></ng-container>
        </ng-template>
        <ng-container #container></ng-container>
        <!--adaptive rendering-->
        <responsive-renderer
            [sharedPopupActionSheetTemplate]="sharedPopupActionSheetTemplate"
            [isActionSheetExpanded]="isActionSheetExpanded"
            [title]="title"
            [showTextInput]="filterable"
            [showActionButtons]="true"
            [subtitle]="subtitle"
            (closePopup)="togglePopup(false)"
            (onExpand)="onExpand()"
            (onCollapse)="onCollapse()"
            (textInputChange)="onFilterChange($event)"
            (onApply)="applyValue()"
            (onCancel)="cancelValue()"
            >
        </responsive-renderer>
        <ng-template #sharedPopupActionSheetTemplate>
            <div
                *ngIf="filterable && !isActionSheetExpanded"
                class="k-list-filter"
                >
                <span
                    class="k-textbox k-input"
                    [ngClass]="filterInputClasses"
                >
                    <span class="k-input-prefix">
                        <kendo-icon-wrapper
                            class="k-icon"
                            name="search"
                            [svgIcon]="searchIcon"
                        >
                        </kendo-icon-wrapper>
                    </span>
                    <input
                        #filterInput
                        (input)="handleFilterInputChange($event.target)"
                        [filterInput]="filterable && !touchEnabled"
                        [attr.aria-label]="messageFor('filterInputLabel')"
                        (keydown)="handleKeydown($event, filterInput)"
                        [value]="filter"
                        class="k-input-inner"
                        role="searchbox"
                        tabindex="0"
                        aria-disabled="false"
                        aria-readonly="false"
                        [kendoEventsOutsideAngular]="{
                            blur: handleBlur
                        }"
                        [scope]="this"
                    >
                </span>
            </div>
            <!--header template-->
            <ng-template
                *ngIf="headerTemplate"
                [templateContext]="{
                    templateRef: headerTemplate?.templateRef
                }">
            </ng-template>
            <div *ngIf="checkAll" class="k-check-all">
                <span class="k-checkbox-wrap">
                    <input
                        #checkAllInput
                        [checkAll]="!filterable && !touchEnabled"
                        type="checkbox"
                        class="k-checkbox"
                        [ngClass]="checkAllCheckboxClasses"
                        role="checkbox"
                        tabindex="0"
                        aria-disabled="false"
                        aria-readonly="false"
                        [treeview]="treeview"
                        [checkedItems]="checkedItems"
                        [valueField]="valueField"
                        [lastAction]="lastAction"
                        (checkedItemsChange)="handleCheckedItemsChange($event)"
                        (keydown)="handleKeydown($event)"
                        [kendoEventsOutsideAngular]="{
                            blur: handleBlur
                        }"
                        [scope]="this"
                    >
                </span>
                <span
                    class="k-checkbox-label"
                    (click)="toggleCheckAll()"
                    (mousedown)="$event.preventDefault()"
                >
                    {{ messageFor('checkAllText') }}
                </span>
            </div>
            <kendo-treeview
                #treeview
                [size]="size"
                [attr.id]="treeViewId"
                [nodes]="data"
                [style.maxHeight.px]="listHeight"
                [animate]="false"
                kendoMultiSelectTreeCheckable
                [isHeterogeneous]="isHeterogeneous"
                [checkable]="checkableSettings"
                [checkedItems]="checkedItems"
                [valueField]="valueField"
                [textField]="textField"
                [children]="children"
                [hasChildren]="hasChildren"
                [isExpanded]="isNodeExpanded"
                [isDisabled]="itemDisabled"
                [nodeTemplate]="nodeTemplate"
                [loadOnDemand]="loadOnDemand"
                [filter]="filter"
                [isVisible]="isNodeVisible"
                (keydown)="handleKeydown($event)"
                (nodeClick)="handleNodeClick($event)"
                (expand)="nodeExpand.emit($event)"
                (collapse)="nodeCollapse.emit($event)"
                (checkedItemsChange)="handleCheckedItemsChange($event)"
                [kendoEventsOutsideAngular]="{
                    focusout: handleBlur
                }"
                [scope]="this"
                (childrenLoaded)="onChildrenLoaded()"
            >
            </kendo-treeview>
            <!--footer template-->
            <ng-template
                *ngIf="footerTemplate"
                [templateContext]="{
                    templateRef: footerTemplate?.templateRef
                }">
            </ng-template>
            <!--no-data template-->
            <div class="k-no-data" *ngIf="!data || data?.length === 0 || allNodesHidden">
                <ng-template [ngIf]="noDataTemplate"
                    [templateContext]="{
                        templateRef: noDataTemplate?.templateRef
                    }">
                </ng-template>
                <ng-template [ngIf]="!noDataTemplate">
                    <div>{{ messageFor('noDataText') }}</div>
                </ng-template>
            </div>
        </ng-template>

        <kendo-resize-sensor *ngIf="isOpen || isAdaptiveModeEnabled" (resize)="onResize()"></kendo-resize-sensor>
    `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective, selector: "\n    [kendoDropDownListLocalizedMessages],\n    [kendoDropDownTreeLocalizedMessages],\n    [kendoComboBoxLocalizedMessages],\n    [kendoMultiColumnComboBoxLocalizedMessages],\n    [kendoAutoCompleteLocalizedMessages],\n    [kendoMultiSelectLocalizedMessages],\n    [kendoMultiSelectTreeLocalizedMessages]\n  " }, { kind: "component", type: TagListComponent, selector: "kendo-taglist", inputs: ["tags", "textField", "valueField", "valueDepth", "focused", "template", "groupTemplate", "disabled", "tagPrefix", "id", "size", "rounded", "fillMode", "disabledIndices"], outputs: ["removeTag"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ResponsiveRendererComponent, selector: "responsive-renderer", inputs: ["title", "showActionButtons", "subtitle", "size", "showTextInput", "sharedPopupActionSheetTemplate", "isActionSheetExpanded", "text", "placeholder"], outputs: ["closePopup", "textInputChange", "navigate", "onExpand", "onCollapse", "onApply", "onCancel"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: FilterInputDirective, selector: "[filterInput]", inputs: ["filterInput"] }, { kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "directive", type: TemplateContextDirective, selector: "[templateContext]", inputs: ["templateContext"] }, { kind: "directive", type: CheckAllDirective, selector: "[checkAll]", inputs: ["lastAction", "treeview", "checkedItems", "valueField", "checkAll"], outputs: ["checkedItemsChange"] }, { kind: "component", type: TreeViewComponent, selector: "kendo-treeview", inputs: ["filterInputPlaceholder", "expandDisabledNodes", "animate", "nodeTemplate", "loadMoreButtonTemplate", "trackBy", "nodes", "textField", "hasChildren", "isChecked", "isDisabled", "hasCheckbox", "isExpanded", "isSelected", "isVisible", "navigable", "children", "loadOnDemand", "filterable", "filter", "size", "disableParentNodesOnly"], outputs: ["childrenLoaded", "blur", "focus", "expand", "collapse", "nodeDragStart", "nodeDrag", "filterStateChange", "nodeDrop", "nodeDragEnd", "addItem", "removeItem", "checkedChange", "selectionChange", "filterChange", "nodeClick", "nodeDblClick"], exportAs: ["kendoTreeView"] }, { kind: "directive", type: CheckDirective, selector: "[kendoMultiSelectTreeCheckable]", inputs: ["checkable", "valueField", "checkedItems"], outputs: ["checkedItemsChange"] }, { kind: "component", type: ResizeSensorComponent, selector: "kendo-resize-sensor", inputs: ["rateLimit"], outputs: ["resize"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: MultiSelectTreeComponent, decorators: [{
            type: Component,
            args: [{
                    exportAs: 'kendoMultiSelectTree',
                    providers: [
                        LocalizationService,
                        NavigationService,
                        DataService,
                        DisabledItemsService,
                        SelectionService,
                        MultiSelectTreeLookupService,
                        {
                            provide: L10N_PREFIX,
                            useValue: 'kendo.multiselecttree'
                        },
                        {
                            multi: true,
                            provide: NG_VALUE_ACCESSOR,
                            useExisting: forwardRef(() => MultiSelectTreeComponent)
                        },
                        {
                            provide: DataBoundComponent,
                            useExisting: forwardRef(() => MultiSelectTreeComponent)
                        },
                        {
                            provide: ExpandableComponent,
                            useExisting: forwardRef(() => MultiSelectTreeComponent)
                        },
                        {
                            provide: KendoInput,
                            useExisting: forwardRef(() => MultiSelectTreeComponent)
                        }
                    ],
                    selector: 'kendo-multiselecttree',
                    template: `
        <ng-container kendoMultiSelectTreeLocalizedMessages
            i18n-noDataText="kendo.multiselecttree.noDataText|The text displayed in the popup when there are no items"
            noDataText="NO DATA FOUND"

            i18n-clearTitle="kendo.multiselecttree.clearTitle|The title of the clear button"
            clearTitle="clear"

            i18n-checkAllText="kendo.multiselecttree.checkAllText|The text displayed for the check-all checkbox"
            checkAllText="Check all"

            i18n-filterInputLabel="kendo.multiselecttree.filterInputLabel|The text set as aria-label on the filter input"
            filterInputLabel="Filter"

            i18n-popupLabel="kendo.multiselecttree.popupLabel|The label of the popup element that contains the list of options when its role is 'region'"
            popupLabel="Options list"

            i18n-applyButton="kendo.multiselecttree.applyButton|The text of the Apply button in the action sheet"
            applyButton="Apply"

            i18n-cancelButton="kendo.multiselecttree.cancelButton|The text of the Cancel button in the action sheet"
            cancelButton="Cancel"
        >
        </ng-container>
        <kendo-taglist
            class="k-input-values"
            [size]="size"
            [rounded]="rounded"
            [fillMode]="fillMode"
            [id]="tagListId"
            [tags]="tags"
            [focused]="focusedTagIndex"
            [textField]="textField"
            [valueField]="valueField"
            [valueDepth]="valueDepth"
            [disabled]="disabled"
            [tagPrefix]="tagPrefix"
            [template]="tagTemplate"
            [groupTemplate]="groupTagTemplate"
            [disabledIndices]="disabledIndices"
            (removeTag)="handleRemoveTag($event)"
        >
        </kendo-taglist>
        <span class="k-input-inner">
            <span 
                *ngIf="!tags || !tags.length"
                class="k-input-value-text"
            >
                {{ placeholder }}
            </span>
        </span>
        <span
            *ngIf="!disabled && !loading && !readonly && clearButton && tags?.length"
            class="k-clear-value"
            [attr.title]="messageFor('clearTitle')"
            role="button"
            tabindex="-1"
            (click)="clearAll($event)"
        >
            <kendo-icon-wrapper
                class="k-icon"
                name="x"
                [svgIcon]="xIcon"
            >
            </kendo-icon-wrapper>
        </span>
        <span
            *ngIf="loading"
            class="k-icon k-i-loading k-input-loading-icon"
        >
        </span>
        <ng-template #popupTemplate>
            <ng-container *ngTemplateOutlet="sharedPopupActionSheetTemplate"></ng-container>
        </ng-template>
        <ng-container #container></ng-container>
        <!--adaptive rendering-->
        <responsive-renderer
            [sharedPopupActionSheetTemplate]="sharedPopupActionSheetTemplate"
            [isActionSheetExpanded]="isActionSheetExpanded"
            [title]="title"
            [showTextInput]="filterable"
            [showActionButtons]="true"
            [subtitle]="subtitle"
            (closePopup)="togglePopup(false)"
            (onExpand)="onExpand()"
            (onCollapse)="onCollapse()"
            (textInputChange)="onFilterChange($event)"
            (onApply)="applyValue()"
            (onCancel)="cancelValue()"
            >
        </responsive-renderer>
        <ng-template #sharedPopupActionSheetTemplate>
            <div
                *ngIf="filterable && !isActionSheetExpanded"
                class="k-list-filter"
                >
                <span
                    class="k-textbox k-input"
                    [ngClass]="filterInputClasses"
                >
                    <span class="k-input-prefix">
                        <kendo-icon-wrapper
                            class="k-icon"
                            name="search"
                            [svgIcon]="searchIcon"
                        >
                        </kendo-icon-wrapper>
                    </span>
                    <input
                        #filterInput
                        (input)="handleFilterInputChange($event.target)"
                        [filterInput]="filterable && !touchEnabled"
                        [attr.aria-label]="messageFor('filterInputLabel')"
                        (keydown)="handleKeydown($event, filterInput)"
                        [value]="filter"
                        class="k-input-inner"
                        role="searchbox"
                        tabindex="0"
                        aria-disabled="false"
                        aria-readonly="false"
                        [kendoEventsOutsideAngular]="{
                            blur: handleBlur
                        }"
                        [scope]="this"
                    >
                </span>
            </div>
            <!--header template-->
            <ng-template
                *ngIf="headerTemplate"
                [templateContext]="{
                    templateRef: headerTemplate?.templateRef
                }">
            </ng-template>
            <div *ngIf="checkAll" class="k-check-all">
                <span class="k-checkbox-wrap">
                    <input
                        #checkAllInput
                        [checkAll]="!filterable && !touchEnabled"
                        type="checkbox"
                        class="k-checkbox"
                        [ngClass]="checkAllCheckboxClasses"
                        role="checkbox"
                        tabindex="0"
                        aria-disabled="false"
                        aria-readonly="false"
                        [treeview]="treeview"
                        [checkedItems]="checkedItems"
                        [valueField]="valueField"
                        [lastAction]="lastAction"
                        (checkedItemsChange)="handleCheckedItemsChange($event)"
                        (keydown)="handleKeydown($event)"
                        [kendoEventsOutsideAngular]="{
                            blur: handleBlur
                        }"
                        [scope]="this"
                    >
                </span>
                <span
                    class="k-checkbox-label"
                    (click)="toggleCheckAll()"
                    (mousedown)="$event.preventDefault()"
                >
                    {{ messageFor('checkAllText') }}
                </span>
            </div>
            <kendo-treeview
                #treeview
                [size]="size"
                [attr.id]="treeViewId"
                [nodes]="data"
                [style.maxHeight.px]="listHeight"
                [animate]="false"
                kendoMultiSelectTreeCheckable
                [isHeterogeneous]="isHeterogeneous"
                [checkable]="checkableSettings"
                [checkedItems]="checkedItems"
                [valueField]="valueField"
                [textField]="textField"
                [children]="children"
                [hasChildren]="hasChildren"
                [isExpanded]="isNodeExpanded"
                [isDisabled]="itemDisabled"
                [nodeTemplate]="nodeTemplate"
                [loadOnDemand]="loadOnDemand"
                [filter]="filter"
                [isVisible]="isNodeVisible"
                (keydown)="handleKeydown($event)"
                (nodeClick)="handleNodeClick($event)"
                (expand)="nodeExpand.emit($event)"
                (collapse)="nodeCollapse.emit($event)"
                (checkedItemsChange)="handleCheckedItemsChange($event)"
                [kendoEventsOutsideAngular]="{
                    focusout: handleBlur
                }"
                [scope]="this"
                (childrenLoaded)="onChildrenLoaded()"
            >
            </kendo-treeview>
            <!--footer template-->
            <ng-template
                *ngIf="footerTemplate"
                [templateContext]="{
                    templateRef: footerTemplate?.templateRef
                }">
            </ng-template>
            <!--no-data template-->
            <div class="k-no-data" *ngIf="!data || data?.length === 0 || allNodesHidden">
                <ng-template [ngIf]="noDataTemplate"
                    [templateContext]="{
                        templateRef: noDataTemplate?.templateRef
                    }">
                </ng-template>
                <ng-template [ngIf]="!noDataTemplate">
                    <div>{{ messageFor('noDataText') }}</div>
                </ng-template>
            </div>
        </ng-template>

        <kendo-resize-sensor *ngIf="isOpen || isAdaptiveModeEnabled" (resize)="onResize()"></kendo-resize-sensor>
    `,
                    standalone: true,
                    imports: [LocalizedMessagesDirective, TagListComponent, NgIf, IconWrapperComponent, NgTemplateOutlet, ResponsiveRendererComponent, NgClass, FilterInputDirective, EventsOutsideAngularDirective, TemplateContextDirective, CheckAllDirective, TreeViewComponent, CheckDirective, ResizeSensorComponent]
                }]
        }], ctorParameters: function () { return [{ type: i0.Injector }, { type: i0.ElementRef }, { type: i1.PopupService }, { type: i0.Renderer2 }, { type: i2.NavigationService }, { type: i0.NgZone }, { type: i3.LocalizationService }, { type: i0.ChangeDetectorRef }, { type: i4.MultiSelectTreeLookupService }, { type: i5.AdaptiveService }]; }, propDecorators: { hostClasses: [{
                type: HostBinding,
                args: ['class.k-multiselecttree']
            }, {
                type: HostBinding,
                args: ['class.k-input']
            }], isDisabled: [{
                type: HostBinding,
                args: ['class.k-disabled']
            }, {
                type: HostBinding,
                args: ['attr.aria-disabled']
            }], hostAriaAutocomplete: [{
                type: HostBinding,
                args: ['attr.aria-autocomplete']
            }], isLoading: [{
                type: HostBinding,
                args: ['class.k-loading']
            }], hostAriaInvalid: [{
                type: HostBinding,
                args: ['attr.aria-invalid']
            }], isBusy: [{
                type: HostBinding,
                args: ['attr.aria-busy']
            }], id: [{
                type: HostBinding,
                args: ['attr.id']
            }], direction: [{
                type: HostBinding,
                args: ['attr.dir']
            }], hostTabIndex: [{
                type: HostBinding,
                args: ['attr.tabindex']
            }], role: [{
                type: HostBinding,
                args: ['attr.role']
            }], ariaHasPopup: [{
                type: HostBinding,
                args: ['attr.aria-haspopup']
            }], isReadonly: [{
                type: HostBinding,
                args: ['attr.readonly']
            }], ariaDescribedBy: [{
                type: HostBinding,
                args: ['attr.aria-describedby']
            }], ariaActiveDescendant: [{
                type: HostBinding,
                args: ['attr.aria-activedescendant']
            }], adaptiveMode: [{
                type: Input
            }], title: [{
                type: Input
            }], subtitle: [{
                type: Input
            }], responsiveRendererComponent: [{
                type: ViewChild,
                args: [ResponsiveRendererComponent]
            }], headerTemplate: [{
                type: ContentChild,
                args: [HeaderTemplateDirective, { static: false }]
            }], footerTemplate: [{
                type: ContentChild,
                args: [FooterTemplateDirective, { static: false }]
            }], nodeTemplate: [{
                type: ContentChild,
                args: [NodeTemplateDirective, { static: false }]
            }], noDataTemplate: [{
                type: ContentChild,
                args: [NoDataTemplateDirective, { static: false }]
            }], tagTemplate: [{
                type: ContentChild,
                args: [TagTemplateDirective, { static: false }]
            }], groupTagTemplate: [{
                type: ContentChild,
                args: [GroupTagTemplateDirective, { static: false }]
            }], popupTemplate: [{
                type: ViewChild,
                args: ['popupTemplate', { static: true }]
            }], container: [{
                type: ViewChild,
                args: ['container', { read: ViewContainerRef, static: true }]
            }], treeview: [{
                type: ViewChild,
                args: ['treeview', { static: false }]
            }], filterInput: [{
                type: ViewChild,
                args: ['filterInput', { static: false }]
            }], checkAllInput: [{
                type: ViewChild,
                args: ['checkAllInput', { static: false }]
            }], tabindex: [{
                type: Input
            }], size: [{
                type: Input
            }], rounded: [{
                type: Input
            }], fillMode: [{
                type: Input
            }], popupSettings: [{
                type: Input
            }], checkableSettings: [{
                type: Input
            }], data: [{
                type: Input
            }], value: [{
                type: Input
            }], dataItems: [{
                type: Input
            }], textField: [{
                type: Input
            }], valueField: [{
                type: Input
            }], valueDepth: [{
                type: Input
            }], loading: [{
                type: Input
            }], placeholder: [{
                type: Input
            }], listHeight: [{
                type: Input
            }], disabled: [{
                type: Input
            }], readonly: [{
                type: Input
            }, {
                type: HostBinding,
                args: ['class.k-readonly']
            }], valuePrimitive: [{
                type: Input
            }], loadOnDemand: [{
                type: Input
            }], focusableId: [{
                type: Input
            }], clearButton: [{
                type: Input
            }], filterable: [{
                type: Input
            }], checkAll: [{
                type: Input
            }], hasChildren: [{
                type: Input
            }], fetchChildren: [{
                type: Input
            }], isNodeExpanded: [{
                type: Input
            }], isNodeVisible: [{
                type: Input
            }], itemDisabled: [{
                type: Input
            }], tagMapper: [{
                type: Input
            }], onFocus: [{
                type: Output,
                args: ['focus']
            }], onBlur: [{
                type: Output,
                args: ['blur']
            }], open: [{
                type: Output
            }], opened: [{
                type: Output
            }], close: [{
                type: Output
            }], closed: [{
                type: Output
            }], nodeExpand: [{
                type: Output
            }], nodeCollapse: [{
                type: Output
            }], valueChange: [{
                type: Output
            }], removeTag: [{
                type: Output
            }], filterChange: [{
                type: Output
            }] } });