@progress/kendo-angular-menu
Version:
Kendo UI Angular Menu component
630 lines (623 loc) • 28.4 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Component, ElementRef, Input, NgZone, Optional, Renderer2, TemplateRef, ViewContainerRef, forwardRef } from '@angular/core';
import { NgFor, NgIf, NgClass, NgStyle, NgTemplateOutlet } from '@angular/common';
import { ChangeDetectorRef, HostBinding, ViewChild } from '@angular/core';
import { isDocumentAvailable } from '@progress/kendo-angular-common';
import { POPUP_CONTAINER, PopupService } from '@progress/kendo-angular-popup';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { ContextMenuService } from '../context-menu/context-menu.service';
import { NODE_INDEX } from '../constants';
import { closest, closestItem, hasClass, inMenu, isFocusable, nodeIndex } from '../dom-queries';
import { ActionsService } from '../services/actions.service';
import { HoverService } from '../services/hover.service';
import { ItemsService } from '../services/items.service';
import { NavigationService } from '../services/navigation.service';
import { bodyFactory, getFontIcon, getSizeClass, getSVGIcon } from '../utils';
import { POPUP_SETTINGS, POPUP_SETTINGS_RTL } from './popup-settings';
import * as i0 from "@angular/core";
import * as i1 from "../services/items.service";
import * as i2 from "../services/hover.service";
import * as i3 from "../services/actions.service";
import * as i4 from "../services/navigation.service";
import * as i5 from "@progress/kendo-angular-popup";
import * as i6 from "../context-menu/context-menu.service";
/* eslint-disable @angular-eslint/component-selector */
/**
* @hidden
*/
export class ListComponent {
itemsService;
hover;
actions;
navigation;
renderer;
ngZone;
element;
appendTo;
items;
level;
index;
animate = true;
size = 'medium';
vertical;
rtl;
openOnClick;
itemTemplate;
itemLinkTemplate;
domSubscriptions;
constructor(itemsService, hover, actions, navigation, renderer, ngZone, element) {
this.itemsService = itemsService;
this.hover = hover;
this.actions = actions;
this.navigation = navigation;
this.renderer = renderer;
this.ngZone = ngZone;
this.element = element;
}
hierarchyIndex(index) {
return this.itemsService.itemIndex(this.index, index);
}
ngOnInit() {
this.itemsService.addList(this);
this.initDomEvents();
}
ngOnDestroy() {
this.itemsService.removeList(this);
if (this.domSubscriptions) {
this.domSubscriptions();
}
}
initDomEvents() {
if (!isDocumentAvailable() || !this.element) {
return;
}
this.ngZone.runOutsideAngular(() => {
const element = this.element.nativeElement;
const container = this.level > 0 ? closest(element, (node) => hasClass(node, 'k-popup')) : element;
const overSubscription = this.renderer.listen(element, 'mouseover', (e) => {
if (e.target === element && this.level === 0) {
this.onLeave();
}
else {
const item = this.nodeItem(e.target) || this.itemsService.get(this.index);
if (item && !(this.openOnClick && this.openOnClick.toggle === 'click' && item.level === 0 && !item.hasContent)) {
this.hover.over(item);
}
}
});
const leaveSubscription = this.renderer.listen(container, 'mouseleave', (e) => {
if (this.leavesMenu(e)) {
this.onLeave();
}
});
const keydownSubscription = this.renderer.listen(element, 'keydown', (e) => {
if (hasClass(e.target, 'k-menu-item')) {
this.navigation.keydown(e);
}
});
const blurSubscription = this.renderer.listen(element, 'focusout', (e) => {
if (this.leavesMenu(e)) {
this.navigation.focusLeave();
}
});
/**
* Handle focus/blur open/close for iOS devices since it behaves inconsistently with the rest
* Refer to: https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
*/
const touchSubscription = this.renderer.listen(document, 'touchstart', (e) => {
if (inMenu(e.target, this.itemsService)) {
const item = this.nodeItem(e.target);
// Needs to be called because the 'click' handler will be called only on secondary tap and the item will remain unfocused
this.navigation.focus(item);
// This is needed since the 'mouseover' event is not always dispatched
if (!item.opened) {
this.hover.over(item);
}
}
else if (this.navigation.focusedIdx) {
// If the touch is outside of the menu and the menu is not currently in focus
const activeItem = this.itemsService.get(this.navigation.activeIndex);
this.onLeave(); // needs to be called explicitly since mouseleave event is not triggered
activeItem.blur(); // needs to be called explicitly otherwise the item remains focused => triggers focusout
}
});
const clickSubscription = this.renderer.listen(element, 'click', this.clickHandler.bind(this));
this.domSubscriptions = () => {
overSubscription();
leaveSubscription();
keydownSubscription();
blurSubscription();
clickSubscription();
touchSubscription();
};
});
}
leavesMenu(e) {
if (!e.relatedTarget) {
return true;
}
return !inMenu(e.relatedTarget, this.itemsService);
}
onLeave() {
const openOnClick = this.openOnClick;
if (!openOnClick || openOnClick.toggle !== 'click') {
this.hover.leave(openOnClick && openOnClick.toggle === 'leave');
}
}
nodeItem(target) {
const node = closestItem(target, this.element.nativeElement);
if (node) {
const index = nodeIndex(node);
return this.itemsService.get(index);
}
}
clickHandler(e) {
if (isFocusable(e.target) && !hasClass(e.target, 'k-menu-item')) {
return;
}
const item = this.nodeItem(e.target);
if (!item || item.isContent || item.navigating) {
return;
}
if (item.disabled) {
e.preventDefault();
return;
}
this.actions.select(item, e, () => {
e.preventDefault();
});
this.navigation.focus(item);
if (item.level > 0 && !item.hasContent) {
this.actions.closeToRoot(item);
}
if (this.openOnClick) {
const hover = this.hover;
if (item.opened) {
if (item.level === 0) {
hover.openOnOver = false;
this.actions.close(item);
}
}
else if (item.hasContent) {
hover.openOnOver = true;
this.actions.closeOthers(item);
this.actions.open(item);
}
else {
hover.openOnOver = false;
if (item.level === 0 && this.openOnClick.toggle === 'click') {
this.hover.closeCurrent();
}
}
}
this.actions.execute();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListComponent, deps: [{ token: i1.ItemsService }, { token: i2.HoverService }, { token: i3.ActionsService }, { token: i4.NavigationService }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ListComponent, isStandalone: true, selector: "[kendoMenuList]", inputs: { appendTo: "appendTo", items: "items", level: "level", index: "index", animate: "animate", size: "size", vertical: "vertical", rtl: "rtl", openOnClick: "openOnClick", itemTemplate: "itemTemplate", itemLinkTemplate: "itemLinkTemplate" }, ngImport: i0, template: "\n <ng-container *ngFor=\"let item of items; let idx = index\">\n <li *ngIf=\"!item.separator\" \n kendoMenuItem\n [appendTo]=\"appendTo\"\n [item]=\"item\"\n [level]=\"level\"\n [size]=\"size\"\n [vertical]=\"vertical\"\n [animate]=\"animate\"\n [rtl]=\"rtl\"\n [itemTemplate]=\"itemTemplate\"\n [itemLinkTemplate]=\"itemLinkTemplate\"\n [openOnClick]=\"openOnClick\"\n [index]=\"hierarchyIndex(idx)\"\n [siblingIndex]=\"idx\"\n [attr.data-kendo-menu-index]=\"hierarchyIndex(idx)\"\n [ngClass]=\"item.cssClass\"\n [ngStyle]=\"item.cssStyle\"\n role=\"menuitem\"\n class=\"k-item k-menu-item\"\n [class.k-first]=\"idx === 0\"\n [class.k-last]=\"idx === items.length - 1\"\n [class.k-disabled]=\"item.disabled\"></li>\n <li \n *ngIf=\"item.separator\"\n class=\"k-separator k-item\"\n [ngClass]=\"item.cssClass\"\n aria-hidden=\"true\"\n [ngStyle]=\"item.cssStyle\">\n \n </li>\n </ng-container>\n ", isInline: true, dependencies: [{ kind: "directive", type: i0.forwardRef(function () { return NgFor; }), selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i0.forwardRef(function () { return NgIf; }), selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i0.forwardRef(function () { return ItemComponent; }), selector: "[kendoMenuItem]", inputs: ["appendTo", "item", "level", "index", "siblingIndex", "animate", "size", "vertical", "rtl", "openOnClick", "itemTemplate", "itemLinkTemplate"] }, { kind: "directive", type: i0.forwardRef(function () { return NgClass; }), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i0.forwardRef(function () { return NgStyle; }), selector: "[ngStyle]", inputs: ["ngStyle"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ListComponent, decorators: [{
type: Component,
args: [{
selector: '[kendoMenuList]',
template: `
<ng-container *ngFor="let item of items; let idx = index">
<li *ngIf="!item.separator"
kendoMenuItem
[appendTo]="appendTo"
[item]="item"
[level]="level"
[size]="size"
[vertical]="vertical"
[animate]="animate"
[rtl]="rtl"
[itemTemplate]="itemTemplate"
[itemLinkTemplate]="itemLinkTemplate"
[openOnClick]="openOnClick"
[index]="hierarchyIndex(idx)"
[siblingIndex]="idx"
[attr.${NODE_INDEX}]="hierarchyIndex(idx)"
[ngClass]="item.cssClass"
[ngStyle]="item.cssStyle"
role="menuitem"
class="k-item k-menu-item"
[class.k-first]="idx === 0"
[class.k-last]="idx === items.length - 1"
[class.k-disabled]="item.disabled"></li>
<li
*ngIf="item.separator"
class="k-separator k-item"
[ngClass]="item.cssClass"
aria-hidden="true"
[ngStyle]="item.cssStyle">
</li>
</ng-container>
`,
standalone: true,
imports: [NgFor, NgIf, forwardRef(() => ItemComponent), NgClass, NgStyle]
}]
}], ctorParameters: function () { return [{ type: i1.ItemsService }, { type: i2.HoverService }, { type: i3.ActionsService }, { type: i4.NavigationService }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: i0.ElementRef }]; }, propDecorators: { appendTo: [{
type: Input
}], items: [{
type: Input
}], level: [{
type: Input
}], index: [{
type: Input
}], animate: [{
type: Input
}], size: [{
type: Input
}], vertical: [{
type: Input
}], rtl: [{
type: Input
}], openOnClick: [{
type: Input
}], itemTemplate: [{
type: Input
}], itemLinkTemplate: [{
type: Input
}] } });
/**
* @hidden
*/
export class ItemComponent {
itemsService;
navigation;
changeDetector;
renderer;
popupService;
element;
contextService;
appendTo;
item;
level;
set index(index) {
if (this._index && this._index !== index) {
this.itemsService.remove(this);
this._index = index;
this.itemsService.add(this);
}
else {
this._index = index;
}
this.childId = this.itemsService.childId(index);
}
get index() {
return this._index;
}
siblingIndex;
animate = true;
size = 'medium';
vertical;
rtl;
openOnClick = false;
itemTemplate;
itemLinkTemplate;
link;
popupTemplate;
get disabled() {
return this.item.disabled;
}
get hasPopup() {
return this.hasContent ? true : null;
}
get label() {
return this.item.text ? this.item.text : null;
}
get popupSettings() {
const settings = this.rtl ? POPUP_SETTINGS_RTL : POPUP_SETTINGS;
return this.horizontal ? settings.horizontal : settings.vertical;
}
get horizontal() {
return this.vertical || this.level > 0;
}
get hasLink() {
return Boolean(this.item.url);
}
get linkTemplate() {
return this.item.linkTemplate || this.itemLinkTemplate;
}
get hasContent() {
const items = this.item.items;
return items && items.length || this.item.contentTemplate;
}
get isContent() {
return Boolean(this.item.content);
}
get iconClass() {
return `k-i-${this.item.icon}`;
}
get isContextMenu() {
return Boolean(this.contextService);
}
get menuListClasses() {
const sizeClass = getSizeClass(this.size);
return this.isContextMenu ? `k-context-menu k-menu-group ${sizeClass}` : `k-menu-group ${sizeClass}`;
}
get children() {
const item = this.item;
if (item.contentTemplate) {
if (!this.contentItems) {
this.contentItems = [{
content: item.contentTemplate,
owner: item,
ownerIndex: this.index
}];
}
return this.contentItems;
}
return item.items;
}
get template() {
return this.item.template || this.itemTemplate;
}
/**
* @hidden
*/
fontIcon = getFontIcon;
/**
* @hidden
*/
SVGIcon = getSVGIcon;
opened = false;
navigating = false;
childId;
contentItems;
popupRef;
_index;
destroyed = false;
constructor(itemsService, navigation, changeDetector, renderer, popupService, element, contextService) {
this.itemsService = itemsService;
this.navigation = navigation;
this.changeDetector = changeDetector;
this.renderer = renderer;
this.popupService = popupService;
this.element = element;
this.contextService = contextService;
}
hasContentTemplates() {
const item = this.item;
return this.itemTemplate || item.contentTemplate || this.itemLinkTemplate ||
(item.items && item.items.find(current => current.template || current.linkTemplate));
}
ngOnInit() {
this.itemsService.add(this);
}
ngAfterViewInit() {
if (this.hasContent) {
this.setAttribute('aria-expanded', this.opened.toString());
}
this.index === this.navigation.activeIndex ? this.setAttribute('tabindex', '0') : this.setAttribute('tabindex', '-1');
}
ngOnDestroy() {
this.itemsService.remove(this);
this.destroyed = true;
if (this.popupRef) {
this.popupRef.close();
this.popupRef = null;
}
}
focus() {
this.element.nativeElement.focus();
}
blur() {
this.element.nativeElement.blur();
}
toggleActive(isActive) {
if (isActive) {
this.setAttribute('tabindex', '0');
}
else {
this.setAttribute('tabindex', '-1');
}
}
open() {
if (!this.destroyed && this.hasContent && !this.opened) {
const popupSettings = this.popupSettings;
const animate = this.animate ? Object.assign({}, this.animate, {
direction: popupSettings.animate
}) : false;
this.opened = true;
this.popupRef = this.popupService.open({
appendTo: this.appendTo,
popupAlign: popupSettings.popup,
anchorAlign: popupSettings.anchor,
collision: popupSettings.collision,
anchor: this.element,
positionMode: 'absolute',
content: this.popupTemplate,
popupClass: {
'k-rtl': this.rtl,
'k-menu-popup': true
},
animate: animate
});
this.setAttribute('aria-expanded', 'true');
this.setAttribute('aria-controls', this.childId);
this.changeDetector.detectChanges();
}
}
close() {
if (!this.destroyed && this.opened) {
this.opened = false;
if (this.popupRef) {
this.popupRef.close();
this.popupRef = null;
}
this.changeDetector.detectChanges();
this.setAttribute('aria-expanded', 'false');
this.renderer.removeAttribute(this.element.nativeElement, 'aria-controls');
}
}
navigate() {
let link;
if (this.linkTemplate) {
link = this.element.nativeElement.querySelector('a.k-menu-link');
}
else if (this.hasLink) {
link = this.link.nativeElement;
}
if (link) {
this.navigating = true;
link.click();
this.navigating = false;
}
}
setAttribute(name, value) {
this.renderer.setAttribute(this.element.nativeElement, name, value);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemComponent, deps: [{ token: i1.ItemsService }, { token: i4.NavigationService }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }, { token: i5.PopupService }, { token: i0.ElementRef }, { token: i6.ContextMenuService, optional: true }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ItemComponent, isStandalone: true, selector: "[kendoMenuItem]", inputs: { appendTo: "appendTo", item: "item", level: "level", index: "index", siblingIndex: "siblingIndex", animate: "animate", size: "size", vertical: "vertical", rtl: "rtl", openOnClick: "openOnClick", itemTemplate: "itemTemplate", itemLinkTemplate: "itemLinkTemplate" }, host: { properties: { "attr.aria-disabled": "this.disabled", "attr.aria-haspopup": "this.hasPopup", "attr.aria-label": "this.label" } }, providers: [PopupService, {
provide: POPUP_CONTAINER,
useFactory: bodyFactory
}], viewQueries: [{ propertyName: "link", first: true, predicate: ["link"], descendants: true }, { propertyName: "popupTemplate", first: true, predicate: ["popupTemplate"], descendants: true, static: true }], ngImport: i0, template: `
<span *ngIf="!hasLink && !item.content && !linkTemplate" class="k-link k-menu-link" #link
[class.k-active]="opened" role="presentation">
<ng-template [ngTemplateOutlet]="itemcontent">
</ng-template>
</span>
<a *ngIf="item.url && !linkTemplate" class="k-link k-menu-link" #link [attr.href]="item.url"
[class.k-active]="opened" tabindex="-1" role="presentation">
<ng-template [ngTemplateOutlet]="itemcontent">
</ng-template>
</a>
<ng-template *ngIf="linkTemplate && !item.content" [ngTemplateOutlet]="linkTemplate"
[ngTemplateOutletContext]="{ item: item, index: index }">
</ng-template>
<div class="k-content" *ngIf="item.content" role="presentation">
<ng-template [ngTemplateOutlet]="item.content" [ngTemplateOutletContext]="{ item: item.owner, index: item.ownerIndex }">
</ng-template>
</div>
<ng-template #popupTemplate>
<ul kendoMenuList
[appendTo]="appendTo"
[attr.id]="childId"
[animate]="animate"
[rtl]="rtl"
[vertical]="vertical"
[size]="size"
[openOnClick]="openOnClick"
[items]="children"
[level]="level + 1"
[index]="index"
[itemTemplate]="itemTemplate"
[itemLinkTemplate]="itemLinkTemplate"
[ngClass]="menuListClasses"
role="menu">
</ul>
</ng-template>
<ng-template #itemcontent>
<kendo-icon-wrapper *ngIf="item.icon || item.svgIcon" [name]="item.icon" [svgIcon]="item.svgIcon" role="presentation"></kendo-icon-wrapper>
<ng-container *ngIf="!template">
<span class="k-menu-link-text">{{ item.text }}</span>
</ng-container>
<ng-template *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ item: item, index: index }">
</ng-template>
<kendo-icon-wrapper *ngIf="hasContent" [name]="fontIcon(horizontal, rtl)" [svgIcon]="SVGIcon(horizontal, rtl)" class="k-menu-expand-arrow" aria-hidden="true"></kendo-icon-wrapper>
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ListComponent, selector: "[kendoMenuList]", inputs: ["appendTo", "items", "level", "index", "animate", "size", "vertical", "rtl", "openOnClick", "itemTemplate", "itemLinkTemplate"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ItemComponent, decorators: [{
type: Component,
args: [{
providers: [PopupService, {
provide: POPUP_CONTAINER,
useFactory: bodyFactory
}],
selector: '[kendoMenuItem]',
template: `
<span *ngIf="!hasLink && !item.content && !linkTemplate" class="k-link k-menu-link" #link
[class.k-active]="opened" role="presentation">
<ng-template [ngTemplateOutlet]="itemcontent">
</ng-template>
</span>
<a *ngIf="item.url && !linkTemplate" class="k-link k-menu-link" #link [attr.href]="item.url"
[class.k-active]="opened" tabindex="-1" role="presentation">
<ng-template [ngTemplateOutlet]="itemcontent">
</ng-template>
</a>
<ng-template *ngIf="linkTemplate && !item.content" [ngTemplateOutlet]="linkTemplate"
[ngTemplateOutletContext]="{ item: item, index: index }">
</ng-template>
<div class="k-content" *ngIf="item.content" role="presentation">
<ng-template [ngTemplateOutlet]="item.content" [ngTemplateOutletContext]="{ item: item.owner, index: item.ownerIndex }">
</ng-template>
</div>
<ng-template #popupTemplate>
<ul kendoMenuList
[appendTo]="appendTo"
[attr.id]="childId"
[animate]="animate"
[rtl]="rtl"
[vertical]="vertical"
[size]="size"
[openOnClick]="openOnClick"
[items]="children"
[level]="level + 1"
[index]="index"
[itemTemplate]="itemTemplate"
[itemLinkTemplate]="itemLinkTemplate"
[ngClass]="menuListClasses"
role="menu">
</ul>
</ng-template>
<ng-template #itemcontent>
<kendo-icon-wrapper *ngIf="item.icon || item.svgIcon" [name]="item.icon" [svgIcon]="item.svgIcon" role="presentation"></kendo-icon-wrapper>
<ng-container *ngIf="!template">
<span class="k-menu-link-text">{{ item.text }}</span>
</ng-container>
<ng-template *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ item: item, index: index }">
</ng-template>
<kendo-icon-wrapper *ngIf="hasContent" [name]="fontIcon(horizontal, rtl)" [svgIcon]="SVGIcon(horizontal, rtl)" class="k-menu-expand-arrow" aria-hidden="true"></kendo-icon-wrapper>
</ng-template>
`,
standalone: true,
imports: [NgIf, NgTemplateOutlet, ListComponent, NgClass, IconWrapperComponent]
}]
}], ctorParameters: function () { return [{ type: i1.ItemsService }, { type: i4.NavigationService }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }, { type: i5.PopupService }, { type: i0.ElementRef }, { type: i6.ContextMenuService, decorators: [{
type: Optional
}] }]; }, propDecorators: { appendTo: [{
type: Input
}], item: [{
type: Input
}], level: [{
type: Input
}], index: [{
type: Input
}], siblingIndex: [{
type: Input
}], animate: [{
type: Input
}], size: [{
type: Input
}], vertical: [{
type: Input
}], rtl: [{
type: Input
}], openOnClick: [{
type: Input
}], itemTemplate: [{
type: Input
}], itemLinkTemplate: [{
type: Input
}], link: [{
type: ViewChild,
args: ['link', { static: false }]
}], popupTemplate: [{
type: ViewChild,
args: ['popupTemplate', { static: true }]
}], disabled: [{
type: HostBinding,
args: ['attr.aria-disabled']
}], hasPopup: [{
type: HostBinding,
args: ['attr.aria-haspopup']
}], label: [{
type: HostBinding,
args: ['attr.aria-label']
}] } });