@meshmakers/shared-ui 3.5.10 → 3.5.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,6 +21,7 @@ import { copyIcon, xIcon, calculatorIcon, clockArrowRotateIcon, passwordIcon, ce
21
21
  import { Router, NavigationEnd, RouterLink, ActivatedRoute } from '@angular/router';
22
22
  import { filter, debounceTime, distinctUntilChanged, takeUntil, observeOn, tap, switchMap, catchError } from 'rxjs/operators';
23
23
  import { MessageService, CommandBaseService, CommandSettingsService, provideMmSharedServices } from '@meshmakers/shared-services';
24
+ import { PopupService } from '@progress/kendo-angular-popup';
24
25
  import * as i1$2 from '@progress/kendo-angular-grid';
25
26
  import { DataBindingDirective, GridComponent, ColumnComponent, ToolbarTemplateDirective, GridSpacerComponent, ExcelModule, PDFModule, CommandColumnComponent, CellTemplateDirective, CheckboxColumnComponent, CustomMessagesComponent, FilterCellTemplateDirective, StringFilterCellComponent, NumericFilterCellComponent, BooleanFilterCellComponent, DateFilterCellComponent, NoRecordsTemplateDirective } from '@progress/kendo-angular-grid';
26
27
  import * as i3 from '@progress/kendo-angular-dropdowns';
@@ -1192,7 +1193,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
1192
1193
  }] });
1193
1194
 
1194
1195
  const DEFAULT_NOTIFICATION_DISPLAY_MESSAGES = {
1195
- showDetails: 'Show Details',
1196
+ showDetails: 'Show details',
1196
1197
  };
1197
1198
 
1198
1199
  const NOTIFICATION_DISPLAY_OPTIONS = new InjectionToken('NOTIFICATION_DISPLAY_OPTIONS');
@@ -1484,7 +1485,10 @@ class NotificationDisplayService {
1484
1485
  detailsBtn.className = 'k-button k-button-sm k-button-flat k-button-flat-base notification-details-btn';
1485
1486
  detailsBtn.style.cssText = 'flex-shrink: 0; width: 32px; height: 32px; padding: 8px; display: flex; align-items: center; justify-content: center;';
1486
1487
  detailsBtn.innerHTML = this.createSvgIcon();
1488
+ // Icon-only: tooltip and accessible name (AB#6222, action guideline §2.2).
1489
+ detailsBtn.type = 'button';
1487
1490
  detailsBtn.title = this.messages.showDetails;
1491
+ detailsBtn.setAttribute('aria-label', this.messages.showDetails);
1488
1492
  detailsBtn.onclick = (event) => {
1489
1493
  event.stopPropagation();
1490
1494
  this.messageDetailsDialogService.showDetailsDialog({
@@ -2737,11 +2741,14 @@ function entryMenuText(action) {
2737
2741
  const text = action.menuLabel || action.label;
2738
2742
  return isActionDisabled(action) ? `${text} — ${action.disabledReason.trim()}` : text;
2739
2743
  }
2744
+ /** Per-instance prefix of the toolbar disabled-reason ids. */
2745
+ let nextListViewId = 0;
2740
2746
  function sameEntries(a, b) {
2741
2747
  return a.length === b.length && a.every((entry, i) => entry.commandItem === b[i].commandItem && entry.rowAction === b[i].rowAction && sameAction(entry.action, b[i].action));
2742
2748
  }
2743
2749
  class ListViewComponent extends CommandBaseService {
2744
2750
  cronHumanizer = inject(CronHumanizerService);
2751
+ popupService = inject(PopupService);
2745
2752
  _columns = [];
2746
2753
  _actionCommandItems = [];
2747
2754
  _contextMenuCommandItems = [];
@@ -3301,6 +3308,7 @@ class ListViewComponent extends CommandBaseService {
3301
3308
  this.dataBindingDirective?.applyPageSize(take);
3302
3309
  }
3303
3310
  ngOnDestroy() {
3311
+ this.closeToolbarHint();
3304
3312
  this.resizeObserver?.disconnect();
3305
3313
  this.destroy$.next();
3306
3314
  this.destroy$.complete();
@@ -3406,6 +3414,85 @@ class ListViewComponent extends CommandBaseService {
3406
3414
  getToolbarItemDisabled(commandItem) {
3407
3415
  return CommandBaseService.getIsDisabled(commandItem, this._selectedRows);
3408
3416
  }
3417
+ /**
3418
+ * Toolbar emphasis (AB#6211, action guideline §9): host toolbar actions are flat and neutral;
3419
+ * only an item flagged `primary` renders as the solid primary button. The page's one primary
3420
+ * action belongs in the page header, so the flag is for lists without one.
3421
+ */
3422
+ toolbarItemThemeColor(commandItem) {
3423
+ return commandItem.primary ? 'primary' : 'base';
3424
+ }
3425
+ /** Fill mode of a host toolbar action: the item's own `fillMode`, else solid for `primary`, flat otherwise. */
3426
+ toolbarItemFillMode(commandItem) {
3427
+ return commandItem.fillMode ?? (commandItem.primary ? 'solid' : 'flat');
3428
+ }
3429
+ /**
3430
+ * Why a disabled toolbar action cannot run (AB#6211, guideline §2.5), or `null`. Only for items
3431
+ * with a `disabledReason` (a callback receives the current selection); such a button stays
3432
+ * focusable (`aria-disabled`) and announces the reason. Without a reason the button is plainly
3433
+ * `disabled` as before.
3434
+ */
3435
+ toolbarItemDisabledReason(commandItem) {
3436
+ if (!commandItem.disabledReason || !this.getToolbarItemDisabled(commandItem)) {
3437
+ return null;
3438
+ }
3439
+ const reason = typeof commandItem.disabledReason === 'function'
3440
+ ? commandItem.disabledReason(this._selectedRows)
3441
+ : commandItem.disabledReason;
3442
+ return reason?.trim() ? reason.trim() : null;
3443
+ }
3444
+ toolbarReasonPrefix = `mm-list-view-toolbar-reason-${nextListViewId++}`;
3445
+ /**
3446
+ * Id of the element carrying a toolbar action's disabled reason: unique per list instance,
3447
+ * toolbar band and position (ids of host items may repeat between the bands).
3448
+ */
3449
+ toolbarReasonId(side, index) {
3450
+ return `${this.toolbarReasonPrefix}-${side}-${index}`;
3451
+ }
3452
+ /** Tooltip of a host toolbar action: `tooltip` or `text`, plus " — reason" while disabled with a reason. */
3453
+ toolbarItemTitle(commandItem, reason) {
3454
+ const label = commandItem.tooltip ?? commandItem.text ?? '';
3455
+ return reason ? `${label} — ${reason}` : label;
3456
+ }
3457
+ toolbarHintTemplate;
3458
+ /** Text of the open focus hint (read by the hint template). */
3459
+ toolbarHintText = signal('', /* @ts-ignore */
3460
+ ...(ngDevMode ? [{ debugName: "toolbarHintText" }] : /* istanbul ignore next */ []));
3461
+ toolbarHintRef = null;
3462
+ toolbarPointerFocus = false;
3463
+ /** A pointer press precedes the focus: no hint (keyboard focus only, like :focus-visible). */
3464
+ onToolbarPointerDown() {
3465
+ this.toolbarPointerFocus = true;
3466
+ }
3467
+ /**
3468
+ * Keyboard focus on a toolbar action disabled with a reason (AB#6211, guideline §2.5): shows the
3469
+ * reason as a hint below the control. Rendered through the PopupService under the application
3470
+ * root (not inside the grid, whose toolbar clips overflow); closed on blur and Escape.
3471
+ */
3472
+ onToolbarFocusIn(event, reason) {
3473
+ const pointer = this.toolbarPointerFocus;
3474
+ this.toolbarPointerFocus = false;
3475
+ this.closeToolbarHint();
3476
+ const anchor = event.target;
3477
+ if (!reason || pointer || !anchor || !this.toolbarHintTemplate) {
3478
+ return;
3479
+ }
3480
+ this.toolbarHintText.set(reason);
3481
+ this.toolbarHintRef = this.popupService.open({
3482
+ anchor,
3483
+ content: this.toolbarHintTemplate,
3484
+ anchorAlign: { horizontal: 'left', vertical: 'bottom' },
3485
+ popupAlign: { horizontal: 'left', vertical: 'top' },
3486
+ margin: { horizontal: 0, vertical: 4 },
3487
+ popupClass: 'mm-list-view-toolbar-hint-popup',
3488
+ animate: false,
3489
+ });
3490
+ }
3491
+ /** Closes the focus hint (blur, Escape, destroy). */
3492
+ closeToolbarHint() {
3493
+ this.toolbarHintRef?.close();
3494
+ this.toolbarHintRef = null;
3495
+ }
3409
3496
  /**
3410
3497
  * Accessible name of a host toolbar action. A button with a visible text is named by that
3411
3498
  * text; an icon-only one (no text) gets its tooltip as `aria-label`, because `title` alone is
@@ -3420,12 +3507,22 @@ class ListViewComponent extends CommandBaseService {
3420
3507
  buttonAttributesCache = new Map();
3421
3508
  static NO_BUTTON_ATTRIBUTES = {};
3422
3509
  /**
3423
- * `buttonAttributes` for Kendo split / dropdown buttons: same rule as
3424
- * {@link toolbarItemAriaLabel}, cached per label so change detection sees a stable object.
3510
+ * `buttonAttributes` for Kendo split / dropdown buttons: the accessible name (same rule as
3511
+ * {@link toolbarItemAriaLabel}) and, while disabled with a reason, `aria-disabled` +
3512
+ * `aria-describedby`. Cached per combination so change detection sees a stable object.
3425
3513
  */
3426
- toolbarItemButtonAttributes(commandItem) {
3514
+ toolbarItemButtonAttributes(commandItem, reasonId = null) {
3427
3515
  const label = this.toolbarItemAriaLabel(commandItem);
3428
- return label ? this.ariaLabelAttributes(label) : ListViewComponent.NO_BUTTON_ATTRIBUTES;
3516
+ if (!reasonId) {
3517
+ return label ? this.ariaLabelAttributes(label) : ListViewComponent.NO_BUTTON_ATTRIBUTES;
3518
+ }
3519
+ const key = `${label ?? ''}\u0000${reasonId}`;
3520
+ let attributes = this.buttonAttributesCache.get(key);
3521
+ if (!attributes) {
3522
+ attributes = { ...(label ? { 'aria-label': label } : {}), 'aria-disabled': 'true', 'aria-describedby': reasonId };
3523
+ this.buttonAttributesCache.set(key, attributes);
3524
+ }
3525
+ return attributes;
3429
3526
  }
3430
3527
  /** `buttonAttributes` of the icon-only menu the commands collapse into. */
3431
3528
  get commandsMenuButtonAttributes() {
@@ -4012,7 +4109,7 @@ class ListViewComponent extends CommandBaseService {
4012
4109
  */
4013
4110
  commandsIcon = slidersIcon;
4014
4111
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ListViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4015
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: ListViewComponent, isStandalone: true, selector: "mm-list-view", inputs: { pageSize: "pageSize", skip: "skip", autoPageSize: "autoPageSize", rowIsClickable: "rowIsClickable", showRowCheckBoxes: "showRowCheckBoxes", showRowSelectAllCheckBox: "showRowSelectAllCheckBox", contextMenuType: "contextMenuType", leftToolbarActions: "leftToolbarActions", rightToolbarActions: "rightToolbarActions", actionCommandItems: "actionCommandItems", contextMenuCommandItems: "contextMenuCommandItems", rowActions: "rowActions", maxInlineRowActions: "maxInlineRowActions", excelExportFileName: "excelExportFileName", pdfExportFileName: "pdfExportFileName", pageable: "pageable", sortable: "sortable", rowFilterEnabled: "rowFilterEnabled", searchTextBoxEnabled: "searchTextBoxEnabled", persistListState: "persistListState", listStateKey: "listStateKey", resizable: "resizable", actionsColumnWidth: "actionsColumnWidth", hideCheckboxesBelow: "hideCheckboxesBelow", cardModeBelow: "cardModeBelow", collapseCommandsBelow: "collapseCommandsBelow", hasExternalFilters: "hasExternalFilters", emptyState: "emptyState", emptyTemplate: "emptyTemplate", rowClass: "rowClass", messages: "messages", selectable: "selectable", columns: "columns", rowLabelField: "rowLabelField" }, outputs: { rowClicked: "rowClicked", rowAction: "rowAction", resetFilters: "resetFilters" }, host: { properties: { "class.mm-list-view-narrow": "this.isNarrow", "class.mm-list-view-cards": "this.isCardMode" } }, queries: [{ propertyName: "filtersTemplate", first: true, predicate: ListViewFiltersDirective, descendants: true }], viewQueries: [{ propertyName: "gridComponent", first: true, predicate: GridComponent, descendants: true }, { propertyName: "dataBindingDirective", first: true, predicate: MmListViewDataBindingDirective, descendants: true }, { propertyName: "gridContextMenu", first: true, predicate: ["gridmenu"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<!-- Shared cell renderer (AB#4930): used by the regular grid columns AND the\n card-mode body lines, so both render every dataType identically. -->\n<ng-template #cellContent let-element=\"element\" let-column=\"column\">\n @if (column.formatter) {\n {{ getFormattedValue(element, column) }}\n } @else {\n @switch (column.dataType) {\n @case ('boolean') {\n <!-- Read-only marker: named after its column so a screen reader says what is checked (AB#5621). -->\n <kendo-checkbox type=\"checkbox\" [checkedState]=\"$any(getValue(element, column))\" [disabled]=\"true\"\n [inputAttributes]=\"booleanCellAttributes(column)\" />\n }\n @case ('booleanIcon') {\n <!-- Boolean as icon (AB#5623): named \"<column>: Yes|No\" like the status icons (G13). -->\n @if (booleanIconState(element, column); as state) {\n @if (booleanIcon(column, state); as icon) {\n <span\n class=\"boolean-icon\"\n [class.boolean-icon--true]=\"state === 'true'\"\n [class.boolean-icon--false]=\"state === 'false'\"\n role=\"img\"\n [title]=\"booleanIconLabel(column, state)\"\n [attr.aria-label]=\"booleanIconLabel(column, state)\"\n [style.color]=\"state === 'true' ? column.booleanIcons?.trueColor : column.booleanIcons?.falseColor\">\n <kendo-svg-icon [icon]=\"icon\"></kendo-svg-icon>\n </span>\n }\n }\n }\n @case ('iso8601') {\n {{ $any(getValue(element, column)) | date:column.format }}\n }\n @case ('bytes') {\n {{ $any(getValue(element, column)) | bytesToSize }}\n }\n @case ('statusIcons') {\n <span class=\"status-icons-cell\">\n @for (fieldConfig of getStatusFields(column); track fieldConfig.field) {\n @if (getStatusIconMapping(element, fieldConfig); as mapping) {\n <!-- Icon-only status: the mapping's tooltip is also its accessible name (AB#5621). -->\n <span\n class=\"status-icon\"\n role=\"img\"\n [title]=\"mapping.tooltip\"\n [attr.aria-label]=\"mapping.tooltip || null\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\"></kendo-svg-icon>\n </span>\n }\n }\n </span>\n }\n @case ('cronExpression') {\n <span class=\"cron-expression-cell\">\n <code class=\"cron-expression\">{{ getValue(element, column) }}</code>\n <span class=\"cron-description\">{{ getCronHumanReadable(getValue(element, column)) }}</span>\n </span>\n }\n @case ('numeric') {\n {{ $any(getValue(element, column)) | number:column.format }}\n }\n @case ('progressBar') {\n <span class=\"progress-bar-cell\">\n <span class=\"progress-bar-track\">\n <span class=\"progress-bar-fill\" [style.width.%]=\"getValue(element, column)\"></span>\n </span>\n <span class=\"progress-bar-label\">{{ getValue(element, column) }}%</span>\n </span>\n }\n @case ('badge') {\n @if (getBadgeMapping(element, column); as badge) {\n <span\n class=\"badge-cell\"\n [title]=\"badge.tooltip ?? ''\"\n [style.color]=\"badge.color\"\n [style.background]=\"badge.backgroundColor\"\n [style.borderColor]=\"badge.borderColor ?? badge.backgroundColor\">\n {{ badge.label ?? getValue(element, column) }}\n </span>\n } @else if (!column.badgeHideUnmapped) {\n <span class=\"badge-cell badge-cell-default\">{{ getValue(element, column) }}</span>\n }\n }\n @case ('component') {\n @if (column.cellComponent) {\n <ng-container *ngComponentOutlet=\"column.cellComponent; inputs: getCellInputs(element, column)\" />\n }\n }\n @default {\n {{ getValue(element, column) }}\n }\n }\n }\n</ng-template>\n\n<!-- Row actions (AB#4930, AB#5572): shared by the actions column and the card header. One MmAction\n per action (CommandItems via adapter): neutral icon buttons, danger colour only for destructive\n actions, max `maxInlineRowActions` slots, the rest (and the context menu) behind \"\u2026\". Disabled\n actions stay focusable and announce their reason (mm-action-button). -->\n<ng-template #rowActions let-dataItem=\"dataItem\">\n @let view = rowActionsView(dataItem);\n @if (view.inline.length > 0 || view.hasMenu) {\n <span class=\"mm-list-view-row-actions\" role=\"group\" [attr.aria-label]=\"rowActionsGroupLabel(dataItem)\">\n @for (entry of view.inline; track entry.action.id) {\n <!-- Clicks never reach the grid (no row selection / row navigation from an action). -->\n <mm-action-button [action]=\"entry.action\" [targetLabel]=\"rowLabel(dataItem)\" context=\"row\" display=\"icon\"\n (click)=\"$event.stopPropagation()\"\n (triggered)=\"onInlineRowAction(entry, dataItem)\" />\n }\n @if (view.hasMenu) {\n <button kendoButton themeColor=\"base\" fillMode=\"flat\" size=\"small\" class=\"mm-list-view-row-actions__more\"\n [svgIcon]=\"moreVerticalIcon\" data-action=\"more\"\n [title]=\"rowActionsLabel(dataItem)\" [attr.aria-label]=\"rowActionsLabel(dataItem)\" aria-haspopup=\"menu\"\n [disabled]=\"isLoading()\" (click)=\"$event.stopPropagation(); onContextMenu(dataItem, $event)\"></button>\n }\n </span>\n }\n</ng-template>\n\n<kendo-grid\n mmListViewDataBinding\n [loading]=\"isLoading()\"\n [pageSize]=\"effectivePageSize()\" [selectable]=\"selectable\" (pageChange)=\"onPageChange($event)\"\n [skip]=\"skip\" (selectionChange)=\"onRowSelect($event)\" (cellClick)=\"onCellClick($event)\"\n [pageable]=\"effectivePageable\"\n [sortable]=\"sortable\"\n [filterable]=\"_showRowFilter && !isCardMode\"\n [resizable]=\"resizable\"\n [rowClass]=\"rowClass ?? defaultRowClass\"\n>\n <kendo-grid-messages\n [pagerItemsPerPage]=\"_messages.pagerItemsPerPage\"\n [pagerOf]=\"_messages.pagerOf\"\n [pagerItems]=\"_messages.pagerItems\"\n [pagerPage]=\"_messages.pagerPage\"\n [pagerFirstPage]=\"_messages.pagerFirstPage\"\n [pagerLastPage]=\"_messages.pagerLastPage\"\n [pagerPreviousPage]=\"_messages.pagerPreviousPage\"\n [pagerNextPage]=\"_messages.pagerNextPage\"\n [noRecords]=\"_messages.noRecords\"\n ></kendo-grid-messages>\n <!-- Empty state (AB#3444): one block per list, never per row. -->\n <ng-template kendoGridNoRecordsTemplate>\n @if (emptyTemplate) {\n <ng-container *ngTemplateOutlet=\"emptyTemplate; context: emptyContext\"></ng-container>\n } @else if (emptyState) {\n @if (loadSettled() && !isLoading()) {\n @if (isFiltered) {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"no-results\"\n [heading]=\"emptyState.filteredTitle || _messages.noResultsTitle\"\n [text]=\"emptyState.filteredText || _messages.noResultsText\"></mm-empty-state>\n } @else {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"empty\"\n [heading]=\"emptyState.title\" [text]=\"emptyState.text\"></mm-empty-state>\n }\n }\n } @else {\n {{ _messages.noRecords }}\n }\n </ng-template>\n <ng-template kendoGridToolbarTemplate>\n\n @for (commandItem of leftToolbarActions; track commandItem.id) {\n @if (commandItem) {\n @switch (commandItem.type) {\n @case ('link') {\n @if (commandItem.children && commandItem.children.length > 0 && commandItem.onClick) {\n <kendo-splitbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (buttonClick)=\"onToolbarCommand(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-splitbutton>\n } @else if (commandItem.children && commandItem.children.length > 0) {\n <kendo-dropdownbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-dropdownbutton>\n } @else if (commandItem.svgIcon) {\n <button kendoTooltip kendoButton themeColor=\"primary\" [svgIcon]=\"commandItem.svgIcon\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n } @else {\n <button kendoTooltip kendoButton themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n }\n }\n @case ('separator'){\n <kendo-separator></kendo-separator>\n }\n }\n }\n }\n\n <!-- Host scope/filter controls (view switches and the like). They steer the\n table, so they sit in the table's own band instead of a second strip\n stacked above the grid. -->\n @if (filtersTemplate) {\n <!-- A rule between the host's actions and its filters: both sit on the\n left of the same band, but one acts on records and the other changes\n what the list shows. Only drawn when actions precede them. -->\n @if (leftToolbarActions.length > 0) {\n <kendo-separator class=\"mm-toolbar-filters-separator\"></kendo-separator>\n }\n <div class=\"mm-toolbar-filters\">\n <ng-container [ngTemplateOutlet]=\"filtersTemplate.templateRef\"></ng-container>\n </div>\n }\n\n <kendo-grid-spacer></kendo-grid-spacer>\n @if (searchTextBoxEnabled) {\n <input\n class=\"k-textbox k-input k-input-md k-rounded-md mm-search-input\"\n [placeholder]=\"_messages.searchPlaceholder\"\n [value]=\"searchValue\"\n (input)=\"onFilter($any($event.target).value || null)\"\n />\n }\n\n @for (commandItem of rightToolbarActions; track commandItem.id) {\n @if (commandItem) {\n @switch (commandItem.type) {\n @case ('link') {\n @if (commandItem.children && commandItem.children.length > 0 && commandItem.onClick) {\n <kendo-splitbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (buttonClick)=\"onToolbarCommand(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-splitbutton>\n } @else if (commandItem.children && commandItem.children.length > 0) {\n <kendo-dropdownbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-dropdownbutton>\n } @else if (commandItem.svgIcon) {\n <button kendoTooltip kendoButton themeColor=\"primary\" [svgIcon]=\"commandItem.svgIcon\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n } @else {\n <button kendoTooltip kendoButton themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n }\n }\n @case ('separator'){\n <kendo-separator></kendo-separator>\n }\n }\n }\n }\n\n <!-- Commands: laid out while there is room, collapsed into one menu once\n the list gets narrow. Same behaviour in every host app \u2014 a list this\n wide has space for five buttons, and a list this narrow does not. -->\n @if (commandsCollapsed) {\n <kendo-dropdownbutton class=\"mm-toolbar-commands\" [data]=\"toolbarCommands\" textField=\"text\" [svgIcon]=\"commandsIcon\"\n fillMode=\"flat\" themeColor=\"primary\" [disabled]=\"isLoading()\"\n [title]=\"_messages.commands\"\n [buttonAttributes]=\"commandsMenuButtonAttributes\"\n (itemClick)=\"onCommand($any($event).id)\"></kendo-dropdownbutton>\n } @else {\n @for (command of toolbarCommands; track command.id) {\n @switch (command.id) {\n @case ('excel') {\n <button kendoTooltip kendoGridExcelCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @case ('pdf') {\n <button kendoTooltip kendoGridPDFCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @default {\n <!-- The row filter is a toggle: its on state is shown (k-selected) and announced. -->\n <button kendoTooltip kendoButton themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" (click)=\"onCommand(command.id)\"\n [class.k-selected]=\"command.id === 'rowFilter' && _showRowFilter\"\n [attr.aria-pressed]=\"command.id === 'rowFilter' ? _showRowFilter : null\"\n [attr.data-command]=\"command.id\"\n [disabled]=\"isLoading()\" [title]=\"command.text\" [attr.aria-label]=\"command.text\"></button>\n }\n }\n }\n }\n </ng-template>\n\n @if (showCheckboxColumn()) {\n <kendo-grid-checkbox-column [showSelectAll]=\"showRowSelectAllCheckBox\"\n [width]=\"effectiveCheckboxColumnWidth\"></kendo-grid-checkbox-column>\n }\n\n @if (!isCardMode) {\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\" format=\"{{column.format}}\"\n [width]=\"$any(getEffectiveWidth(column))\"\n [minResizableWidth]=\"column.minWidth ?? 10\"\n [hidden]=\"isColumnHidden(column)\"\n [sortable]=\"column.sortable !== false\"\n [filterable]=\"column.filterable !== false\"\n [class]=\"alignClass(column)\"\n [headerClass]=\"alignClass(column)\"\n [title]=\"getDisplayName(column) | pascalCase\">\n <ng-template kendoGridFilterCellTemplate let-filter let-gridColumn=\"column\">\n @if (hasFilterOptions(column)) {\n <kendo-dropdownlist\n class=\"status-filter-dropdown\"\n [data]=\"column.filterOptions!\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n [value]=\"getSelectedFilterValue(column)\"\n [defaultItem]=\"{ text: '', value: null }\"\n [popupSettings]=\"{ width: 'auto' }\"\n (valueChange)=\"onDropdownFilter($event, column)\"\n >\n <ng-template kendoDropDownListValueTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span class=\"filter-status-item\">\n <span role=\"img\" [attr.aria-label]=\"dataItem.text || mapping.tooltip || null\" [title]=\"dataItem.text || mapping.tooltip\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n </span>\n } @else if (dataItem?.text) {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n <ng-template kendoDropDownListItemTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span style=\"display: inline-flex; align-items: center; gap: 8px;\">\n <span [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n <span>{{ dataItem.text }}</span>\n </span>\n } @else {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n </kendo-dropdownlist>\n } @else if (column.dataType === 'numericRange') {\n <span class=\"numeric-range-filter\">\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'gte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'gte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"From\"\n size=\"small\"\n ></kendo-numerictextbox>\n <span class=\"range-separator\">\u2013</span>\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'lte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'lte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"To\"\n size=\"small\"\n ></kendo-numerictextbox>\n </span>\n } @else {\n @switch (getFilterType(column)) {\n @case ('numeric') {\n <kendo-grid-numeric-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [spinners]=\"!isCompactFilter(column)\" [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-numeric-filter-cell>\n }\n @case ('boolean') {\n <kendo-grid-boolean-filter-cell [column]=\"gridColumn\" [filter]=\"filter\"></kendo-grid-boolean-filter-cell>\n }\n @case ('date') {\n <kendo-grid-date-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-date-filter-cell>\n }\n @default {\n <kendo-grid-string-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"false\"></kendo-grid-string-filter-cell>\n }\n }\n }\n </ng-template>\n <ng-template kendoGridCellTemplate let-dataItem>\n <span\n [class.mm-cell-truncate]=\"column.truncate\"\n [attr.title]=\"column.truncate ? getCellTitle(dataItem, column) : null\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </ng-template>\n </kendo-grid-column>\n }\n\n @if (hasRowActions) {\n <kendo-grid-command-column [title]=\"_messages.actionsColumnTitle\" [width]=\"effectiveActionsColumnWidth\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </ng-template>\n </kendo-grid-command-column>\n }\n } @else {\n <!-- Card mode (AB#4930): one full-width column renders the whole row as a\n stacked card \u2014 title from the first column, labeled lines for the\n rest (empty values skipped), row actions in the card header. -->\n <kendo-grid-column [sortable]=\"false\" [filterable]=\"false\" title=\"\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <div class=\"mm-card\">\n <div class=\"mm-card-header\">\n @if (cardTitleColumn; as titleColumn) {\n <span class=\"mm-card-title\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: titleColumn }\"\n ></ng-container>\n </span>\n }\n <span class=\"mm-card-actions\">\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </span>\n </div>\n <div class=\"mm-card-fields\">\n @for (column of cardBodyColumns; track column.field) {\n @if (hasCardValue(dataItem, column)) {\n <div class=\"mm-card-field\">\n <span class=\"mm-card-label\">{{ getDisplayName(column) | pascalCase }}</span>\n <span class=\"mm-card-value\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n </kendo-grid-column>\n }\n\n <kendo-grid-excel fileName=\"{{excelExportFileName}}\">\n @for (column of columns; track column.field) {\n <kendo-excelexport-column [field]=\"column.field\"\n [title]=\"getDisplayName(column) | pascalCase\">\n </kendo-excelexport-column>\n }\n </kendo-grid-excel>\n\n <kendo-grid-pdf fileName=\"{{pdfExportFileName}}\" paperSize=\"A4\" [repeatHeaders]=\"true\"\n [landscape]=\"true\">\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\"\n [title]=\"getDisplayName(column) | pascalCase\"></kendo-grid-column>\n }\n <kendo-grid-pdf-margin top=\"1.5cm\" left=\"1cm\" right=\"1cm\" bottom=\"1.5cm\"></kendo-grid-pdf-margin>\n <ng-template kendoGridPDFTemplate let-pageNum=\"pageNum\" let-totalPages=\"totalPages\">\n <div class=\"page-template\">\n <div class=\"footer\" style=\"position: absolute; bottom: 0; width: 100%; text-align: center; font-size: 9px; color: #666;\">\n {{ getPdfPageText(pageNum, totalPages) }}\n </div>\n </div>\n </ng-template>\n </kendo-grid-pdf>\n</kendo-grid>\n\n<kendo-contextmenu\n #gridmenu\n [kendoMenuHierarchyBinding]=\"_contextMenuItems\"\n [textField]=\"['text']\"\n childrenField=\"items\"\n svgIconField=\"svgIcon\"\n separatorField=\"separator\"\n disabledField=\"disabled\"\n cssClassField=\"cssClass\"\n (popupClose)=\"onContextMenuClosed($event)\"\n (select)=\"onContextMenuSelect($event)\"\n></kendo-contextmenu>\n", styles: [":host{display:flex;flex-direction:column;min-height:0}:host>kendo-grid{flex:1;min-height:0}.mm-search-input{width:165px}:host ::ng-deep .k-grid th:has(input[type=checkbox]),:host ::ng-deep .k-grid td:has(>kendo-checkbox){text-overflow:clip}:host ::ng-deep .k-command-cell{white-space:nowrap;overflow:hidden;vertical-align:middle}:host ::ng-deep .k-command-cell .k-button{display:inline-flex;vertical-align:middle;margin:0}:host ::ng-deep .k-command-cell .k-button+.k-button{margin-left:4px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar{flex-wrap:wrap;row-gap:6px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-search-input{width:100%;order:10}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar kendo-grid-spacer{display:none}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-toolbar-commands{margin-left:auto}:host ::ng-deep .k-grid td.mm-col-align-right{text-align:right;font-variant-numeric:tabular-nums}:host ::ng-deep .k-grid td.mm-col-align-center{text-align:center}:host ::ng-deep .k-grid th.mm-col-align-right .k-cell-inner>.k-link{justify-content:flex-end}:host ::ng-deep .k-grid th.mm-col-align-center .k-cell-inner>.k-link{justify-content:center}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-wrapper{gap:2px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-datepicker>.k-input-button{display:none}:host ::ng-deep .k-filter-row .mm-filter-compact .k-input-inner{padding-left:4px;padding-right:4px;font-size:12px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator{width:26px;min-width:0}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator .k-input-button{padding:2px}.mm-cell-truncate{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-icons-cell{display:inline-flex;align-items:center;gap:8px}.status-icons-cell .status-icon{display:inline-flex;align-items:center;justify-content:center;cursor:default}.status-icons-cell .status-icon kendo-svg-icon{width:18px;height:18px}:host ::ng-deep .filter-status-item{display:inline-flex;align-items:center;gap:10px}:host ::ng-deep .status-filter-dropdown .k-input-value-text{font-size:0}:host ::ng-deep .status-filter-dropdown .k-input-value-text .filter-status-item,:host ::ng-deep .status-filter-dropdown .k-input-value-text>span{font-size:14px}.numeric-range-filter{display:flex;align-items:center;gap:4px}.numeric-range-filter kendo-numerictextbox{flex:1;min-width:0}.numeric-range-filter .range-separator{flex-shrink:0;opacity:.6}.cron-expression-cell{display:flex;flex-direction:column;gap:2px}.cron-expression-cell .cron-expression{font-family:Roboto Mono,Consolas,monospace;font-size:.85em;background:var(--mm-cron-code-bg, #eeeeee);color:var(--mm-cron-code-text, #333333);padding:2px 6px;border-radius:3px}.cron-expression-cell .cron-description{font-size:.8em;color:var(--mm-cron-text-secondary, #666666)}.badge-cell{display:inline-block;padding:2px 10px;font-size:.75rem;font-weight:600;border-radius:12px;border:1px solid var(--mm-badge-default-border, transparent);background:var(--mm-badge-default-bg, rgba(127, 127, 127, .15));color:var(--mm-badge-default-color, inherit);white-space:nowrap;line-height:1.4}.badge-cell-default{opacity:.7;font-style:italic}.progress-bar-cell{display:inline-flex;align-items:center;gap:8px;width:100%}.progress-bar-cell .progress-bar-track{flex:1;height:8px;background:var(--mm-progress-track-bg, rgba(255, 255, 255, .1));border-radius:4px;overflow:hidden}.progress-bar-cell .progress-bar-fill{display:block;height:100%;background:var(--mm-progress-fill-color, #10b981);border-radius:4px;transition:width .3s ease}.progress-bar-cell .progress-bar-label{min-width:40px;text-align:right;font-size:.85em}:host-context(.k-pdf-export) .k-grid,:host-context(.k-pdf-export) .k-grid-header,:host-context(.k-pdf-export) .k-grid-content,:host-context(.k-pdf-export) .k-grid-header-wrap,:host-context(.k-pdf-export) .k-grid-content-locked,:host-context(.k-pdf-export) .k-grid-header-locked{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border-color:#000!important}:host-context(.k-pdf-export) .k-grid{border:1px solid #000!important;font-family:Arial,sans-serif!important;font-size:10px!important}:host-context(.k-pdf-export) .k-grid-header th,:host-context(.k-pdf-export) .k-header,:host-context(.k-pdf-export) .k-grid th{background:#f0f0f0!important;background-color:#f0f0f0!important;background-image:none!important;color:#000!important;border:1px solid #000!important;font-weight:700!important;padding:6px 8px!important;text-transform:none!important;letter-spacing:normal!important}:host-context(.k-pdf-export) .k-grid td,:host-context(.k-pdf-export) .k-grid-content td,:host-context(.k-pdf-export) .k-master-row td{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border:1px solid #000!important;padding:4px 8px!important}:host-context(.k-pdf-export) .k-grid tr,:host-context(.k-pdf-export) .k-master-row,:host-context(.k-pdf-export) .k-alt{background:#fff!important;background-color:#fff!important;background-image:none!important}:host-context(.k-pdf-export) .k-grid tr:hover,:host-context(.k-pdf-export) .k-grid tr.k-selected,:host-context(.k-pdf-export) .k-grid td:hover{background:#fff!important;background-color:#fff!important}:host-context(.k-pdf-export) .k-grid-toolbar,:host-context(.k-pdf-export) .k-pager,:host-context(.k-pdf-export) .k-grid-pager,:host-context(.k-pdf-export) .k-command-cell,:host-context(.k-pdf-export) .k-checkbox-column,:host-context(.k-pdf-export) kendo-grid-checkbox-column,:host-context(.k-pdf-export) .status-icons-cell,:host-context(.k-pdf-export) .status-icon,:host-context(.k-pdf-export) kendo-svg-icon{display:none!important}:host-context(.k-pdf-export) .badge-cell{background:transparent!important;border:none!important;color:inherit!important;padding:0!important;border-radius:0!important;text-transform:none!important;font-weight:inherit!important;font-size:inherit!important}:host(.mm-list-view-cards) ::ng-deep .k-grid-header{display:none}:host(.mm-list-view-cards) ::ng-deep .k-grid td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td{border:none;background:transparent;padding:4px 2px;white-space:normal}:host(.mm-list-view-cards) ::ng-deep .k-grid td:has(>kendo-checkbox),:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td:has(>kendo-checkbox){padding-left:2px;padding-right:0}:host(.mm-list-view-cards) ::ng-deep .k-grid colgroup>col:first-child:not(:only-child){width:32px!important}:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row:hover,:host(.mm-list-view-cards) ::ng-deep .k-grid tr:hover{background:transparent}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected .mm-card,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected .mm-card{border-color:var(--kendo-color-primary, #ff6358)}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected>td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected>.k-table-td{background:transparent}.mm-card{display:flex;flex-direction:column;gap:6px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:8px;padding:10px 12px}.mm-card-header{display:flex;align-items:center;gap:8px}.mm-card-title{flex:1;min-width:0;font-weight:600;overflow-wrap:anywhere}.mm-card-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.mm-card-fields{display:flex;flex-direction:column;gap:3px}.mm-card-field{display:flex;align-items:baseline;gap:8px;font-size:.9em}.mm-card-label{flex:0 0 38%;max-width:160px;color:var(--kendo-color-subtle, #666666);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mm-card-value{flex:1;min-width:0;overflow-wrap:anywhere}::ng-deep .k-grid-toolbar .mm-toolbar-filters{display:flex;align-items:center;gap:12px;flex-wrap:wrap}::ng-deep .k-grid-toolbar .mm-toolbar-filters-separator{align-self:center;height:22px;margin:0 4px;border-left:1px solid var(--theme-border-subtle, rgba(0, 0, 0, .16));border-top:none;width:0}::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-link,::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-menu-link{color:var(--theme-status-error, #c0385f)}.mm-list-view-row-actions{display:inline-flex;align-items:center;gap:4px;white-space:nowrap;vertical-align:middle}.mm-list-view-row-actions .mm-list-view-row-actions__more:focus-visible{outline:none;box-shadow:var(--theme-focus-ring, 0 0 0 2px #ffffff, 0 0 0 4px #2e8473)}.boolean-icon{display:inline-flex;align-items:center}.boolean-icon.boolean-icon--true{color:var(--mm-list-boolean-true-color, var(--kendo-color-success, #2fb37a))}.boolean-icon.boolean-icon--false{color:var(--mm-list-boolean-false-color, var(--kendo-color-subtle, #8a94a0))}\n"], dependencies: [{ kind: "component", type: GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode", "pinnable", "pinnedTopRows", "pinnedBottomRows", "isRowPinnable"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "rowPinChange", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "csvExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: MmListViewDataBindingDirective, selector: "[mmListViewDataBinding]" }, { kind: "component", type: ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: ToolbarTemplateDirective, selector: "[kendoGridToolbarTemplate]", inputs: ["position"] }, { kind: "component", type: GridSpacerComponent, selector: "kendo-grid-spacer", inputs: ["width"] }, { kind: "ngmodule", type: ExcelModule }, { kind: "component", type: i1$2.ExcelComponent, selector: "kendo-grid-excel", inputs: ["fileName", "filterable", "creator", "date", "forceProxy", "proxyURL", "fetchData", "paddingCellOptions", "headerPaddingCellOptions", "collapsible"], outputs: ["fileCreated"] }, { kind: "component", type: i1$2.ExcelCommandDirective, selector: "[kendoGridExcelCommand]" }, { kind: "component", type: i2$1.ColumnComponent, selector: "kendo-excelexport-column", inputs: ["field", "cellOptions", "groupHeaderCellOptions", "groupFooterCellOptions", "footerCellOptions"] }, { kind: "ngmodule", type: PDFModule }, { kind: "component", type: i1$2.PDFComponent, selector: "kendo-grid-pdf", inputs: ["allPages", "delay"] }, { kind: "component", type: i1$2.PDFMarginComponent, selector: "kendo-grid-pdf-margin" }, { kind: "component", type: i1$2.PDFCommandDirective, selector: "[kendoGridPDFCommand]" }, { kind: "directive", type: i1$2.PDFTemplateDirective, selector: "[kendoGridPDFTemplate]" }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: DropDownButtonComponent, selector: "kendo-dropdownbutton", inputs: ["arrowIcon", "icon", "svgIcon", "iconClass", "imageUrl", "textField", "data", "size", "rounded", "fillMode", "themeColor", "buttonAttributes"], outputs: ["itemClick", "focus", "blur"], exportAs: ["kendoDropDownButton"] }, { kind: "component", type: SplitButtonComponent, selector: "kendo-splitbutton", inputs: ["text", "icon", "svgIcon", "iconClass", "type", "imageUrl", "size", "rounded", "fillMode", "themeColor", "disabled", "popupSettings", "tabIndex", "textField", "data", "arrowButtonClass", "arrowButtonIcon", "arrowButtonSvgIcon", "buttonAttributes"], outputs: ["buttonClick", "itemClick", "focus", "blur", "open", "close"], exportAs: ["kendoSplitButton"] }, { kind: "component", type: CommandColumnComponent, selector: "kendo-grid-command-column" }, { kind: "directive", type: CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "component", type: ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "directive", type: HierarchyBindingDirective, selector: "[kendoMenuHierarchyBinding]", inputs: ["kendoMenuHierarchyBinding", "textField", "urlField", "iconField", "svgIconField", "disabledField", "cssClassField", "cssStyleField", "separatorField", "childrenField"], exportAs: ["kendoMenuHierarchyBinding"] }, { kind: "component", type: CheckboxColumnComponent, selector: "kendo-grid-checkbox-column", inputs: ["showSelectAll", "showDisabledCheckbox"] }, { kind: "component", type: CheckBoxComponent, selector: "kendo-checkbox", inputs: ["checkedState", "rounded"], outputs: ["checkedStateChange"], exportAs: ["kendoCheckBox"] }, { kind: "component", type: SeparatorComponent, selector: "kendo-separator", inputs: ["orientation"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon", "variant"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: CustomMessagesComponent, selector: "kendo-grid-messages" }, { kind: "directive", type: FilterCellTemplateDirective, selector: "[kendoGridFilterCellTemplate]" }, { kind: "component", type: StringFilterCellComponent, selector: "kendo-grid-string-filter-cell", inputs: ["filterDelay", "showOperators", "placeholder"] }, { kind: "component", type: NumericFilterCellComponent, selector: "kendo-grid-numeric-filter-cell", inputs: ["filterDelay", "showOperators", "placeholder"] }, { kind: "component", type: BooleanFilterCellComponent, selector: "kendo-grid-boolean-filter-cell" }, { kind: "component", type: DateFilterCellComponent, selector: "kendo-grid-date-filter-cell", inputs: ["showOperators"] }, { kind: "component", type: DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "directive", type: ValueTemplateDirective, selector: "[kendoDropDownListValueTemplate],[kendoDropDownTreeValueTemplate]" }, { kind: "directive", type: ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ActionButtonComponent, selector: "mm-action-button", inputs: ["action", "targetLabel", "context", "display", "primary"], outputs: ["triggered"] }, { kind: "directive", type: NoRecordsTemplateDirective, selector: "[kendoGridNoRecordsTemplate]" }, { kind: "component", type: EmptyStateComponent, selector: "mm-empty-state", inputs: ["variant", "heading", "text", "size", "showRetry", "retryLabel"], outputs: ["retry"] }, { kind: "pipe", type: PascalCasePipe, name: "pascalCase" }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: BytesToSizePipe, name: "bytesToSize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4112
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: ListViewComponent, isStandalone: true, selector: "mm-list-view", inputs: { pageSize: "pageSize", skip: "skip", autoPageSize: "autoPageSize", rowIsClickable: "rowIsClickable", showRowCheckBoxes: "showRowCheckBoxes", showRowSelectAllCheckBox: "showRowSelectAllCheckBox", contextMenuType: "contextMenuType", leftToolbarActions: "leftToolbarActions", rightToolbarActions: "rightToolbarActions", actionCommandItems: "actionCommandItems", contextMenuCommandItems: "contextMenuCommandItems", rowActions: "rowActions", maxInlineRowActions: "maxInlineRowActions", excelExportFileName: "excelExportFileName", pdfExportFileName: "pdfExportFileName", pageable: "pageable", sortable: "sortable", rowFilterEnabled: "rowFilterEnabled", searchTextBoxEnabled: "searchTextBoxEnabled", persistListState: "persistListState", listStateKey: "listStateKey", resizable: "resizable", actionsColumnWidth: "actionsColumnWidth", hideCheckboxesBelow: "hideCheckboxesBelow", cardModeBelow: "cardModeBelow", collapseCommandsBelow: "collapseCommandsBelow", hasExternalFilters: "hasExternalFilters", emptyState: "emptyState", emptyTemplate: "emptyTemplate", rowClass: "rowClass", messages: "messages", selectable: "selectable", columns: "columns", rowLabelField: "rowLabelField" }, outputs: { rowClicked: "rowClicked", rowAction: "rowAction", resetFilters: "resetFilters" }, host: { properties: { "class.mm-list-view-narrow": "this.isNarrow", "class.mm-list-view-cards": "this.isCardMode" } }, queries: [{ propertyName: "filtersTemplate", first: true, predicate: ListViewFiltersDirective, descendants: true }], viewQueries: [{ propertyName: "gridComponent", first: true, predicate: GridComponent, descendants: true }, { propertyName: "dataBindingDirective", first: true, predicate: MmListViewDataBindingDirective, descendants: true }, { propertyName: "gridContextMenu", first: true, predicate: ["gridmenu"], descendants: true }, { propertyName: "toolbarHintTemplate", first: true, predicate: ["toolbarHint"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<!-- Shared cell renderer (AB#4930): used by the regular grid columns AND the\n card-mode body lines, so both render every dataType identically. -->\n<ng-template #cellContent let-element=\"element\" let-column=\"column\">\n @if (column.formatter) {\n {{ getFormattedValue(element, column) }}\n } @else {\n @switch (column.dataType) {\n @case ('boolean') {\n <!-- Read-only marker: named after its column so a screen reader says what is checked (AB#5621). -->\n <kendo-checkbox type=\"checkbox\" [checkedState]=\"$any(getValue(element, column))\" [disabled]=\"true\"\n [inputAttributes]=\"booleanCellAttributes(column)\" />\n }\n @case ('booleanIcon') {\n <!-- Boolean as icon (AB#5623): named \"<column>: Yes|No\" like the status icons (G13). -->\n @if (booleanIconState(element, column); as state) {\n @if (booleanIcon(column, state); as icon) {\n <span\n class=\"boolean-icon\"\n [class.boolean-icon--true]=\"state === 'true'\"\n [class.boolean-icon--false]=\"state === 'false'\"\n role=\"img\"\n [title]=\"booleanIconLabel(column, state)\"\n [attr.aria-label]=\"booleanIconLabel(column, state)\"\n [style.color]=\"state === 'true' ? column.booleanIcons?.trueColor : column.booleanIcons?.falseColor\">\n <kendo-svg-icon [icon]=\"icon\"></kendo-svg-icon>\n </span>\n }\n }\n }\n @case ('iso8601') {\n {{ $any(getValue(element, column)) | date:column.format }}\n }\n @case ('bytes') {\n {{ $any(getValue(element, column)) | bytesToSize }}\n }\n @case ('statusIcons') {\n <span class=\"status-icons-cell\">\n @for (fieldConfig of getStatusFields(column); track fieldConfig.field) {\n @if (getStatusIconMapping(element, fieldConfig); as mapping) {\n <!-- Icon-only status: the mapping's tooltip is also its accessible name (AB#5621). -->\n <span\n class=\"status-icon\"\n role=\"img\"\n [title]=\"mapping.tooltip\"\n [attr.aria-label]=\"mapping.tooltip || null\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\"></kendo-svg-icon>\n </span>\n }\n }\n </span>\n }\n @case ('cronExpression') {\n <span class=\"cron-expression-cell\">\n <code class=\"cron-expression\">{{ getValue(element, column) }}</code>\n <span class=\"cron-description\">{{ getCronHumanReadable(getValue(element, column)) }}</span>\n </span>\n }\n @case ('numeric') {\n {{ $any(getValue(element, column)) | number:column.format }}\n }\n @case ('progressBar') {\n <span class=\"progress-bar-cell\">\n <span class=\"progress-bar-track\">\n <span class=\"progress-bar-fill\" [style.width.%]=\"getValue(element, column)\"></span>\n </span>\n <span class=\"progress-bar-label\">{{ getValue(element, column) }}%</span>\n </span>\n }\n @case ('badge') {\n @if (getBadgeMapping(element, column); as badge) {\n <span\n class=\"badge-cell\"\n [title]=\"badge.tooltip ?? ''\"\n [style.color]=\"badge.color\"\n [style.background]=\"badge.backgroundColor\"\n [style.borderColor]=\"badge.borderColor ?? badge.backgroundColor\">\n {{ badge.label ?? getValue(element, column) }}\n </span>\n } @else if (!column.badgeHideUnmapped) {\n <span class=\"badge-cell badge-cell-default\">{{ getValue(element, column) }}</span>\n }\n }\n @case ('component') {\n @if (column.cellComponent) {\n <ng-container *ngComponentOutlet=\"column.cellComponent; inputs: getCellInputs(element, column)\" />\n }\n }\n @default {\n {{ getValue(element, column) }}\n }\n }\n }\n</ng-template>\n\n<!-- Row actions (AB#4930, AB#5572): shared by the actions column and the card header. One MmAction\n per action (CommandItems via adapter): neutral icon buttons, danger colour only for destructive\n actions, max `maxInlineRowActions` slots, the rest (and the context menu) behind \"\u2026\". Disabled\n actions stay focusable and announce their reason (mm-action-button). -->\n<ng-template #rowActions let-dataItem=\"dataItem\">\n @let view = rowActionsView(dataItem);\n @if (view.inline.length > 0 || view.hasMenu) {\n <span class=\"mm-list-view-row-actions\" role=\"group\" [attr.aria-label]=\"rowActionsGroupLabel(dataItem)\">\n @for (entry of view.inline; track entry.action.id) {\n <!-- Clicks never reach the grid (no row selection / row navigation from an action). -->\n <mm-action-button [action]=\"entry.action\" [targetLabel]=\"rowLabel(dataItem)\" context=\"row\" display=\"icon\"\n (click)=\"$event.stopPropagation()\"\n (triggered)=\"onInlineRowAction(entry, dataItem)\" />\n }\n @if (view.hasMenu) {\n <button kendoButton themeColor=\"base\" fillMode=\"flat\" size=\"small\" class=\"mm-list-view-row-actions__more\"\n [svgIcon]=\"moreVerticalIcon\" data-action=\"more\"\n [title]=\"rowActionsLabel(dataItem)\" [attr.aria-label]=\"rowActionsLabel(dataItem)\" aria-haspopup=\"menu\"\n [disabled]=\"isLoading()\" (click)=\"$event.stopPropagation(); onContextMenu(dataItem, $event)\"></button>\n }\n </span>\n }\n</ng-template>\n\n<!-- One host toolbar action (left or right band). Flat + neutral unless `primary` (AB#6211). With\n `isDisabled` + `disabledReason` the control stays focusable (aria-disabled), announces the reason\n (aria-describedby) and shows it as a hint bubble on keyboard focus (guideline \u00A72.5); clicks and\n menu opening are swallowed. Applies to buttons, split and dropdown buttons alike. -->\n<ng-template #toolbarItem let-commandItem let-side=\"side\" let-index=\"index\">\n @if (commandItem) {\n @switch (commandItem.type) {\n @case ('link') {\n @let reason = toolbarItemDisabledReason(commandItem);\n @let reasonId = toolbarReasonId(side, index);\n <span class=\"mm-list-view-toolbar-action\" [class.mm-list-view-toolbar-action--disabled]=\"!!reason\"\n (pointerdown)=\"onToolbarPointerDown()\"\n (focusin)=\"onToolbarFocusIn($event, reason)\"\n (focusout)=\"closeToolbarHint()\"\n (keydown.escape)=\"closeToolbarHint()\">\n @if (commandItem.children && commandItem.children.length > 0 && commandItem.onClick) {\n <kendo-splitbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"(getToolbarItemDisabled(commandItem) && !reason) || isLoading()\"\n [textField]=\"'text'\"\n [themeColor]=\"toolbarItemThemeColor(commandItem)\"\n [fillMode]=\"$any(toolbarItemFillMode(commandItem))\"\n [title]=\"toolbarItemTitle(commandItem, reason)\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem, reason ? reasonId : null)\"\n [attr.data-toolbar-action]=\"commandItem.id\"\n (buttonClick)=\"reason ? null : onToolbarCommand(commandItem)\"\n (open)=\"reason ? $event.preventDefault() : null\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-splitbutton>\n } @else if (commandItem.children && commandItem.children.length > 0) {\n <kendo-dropdownbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"(getToolbarItemDisabled(commandItem) && !reason) || isLoading()\"\n [textField]=\"'text'\"\n [themeColor]=\"toolbarItemThemeColor(commandItem)\"\n [fillMode]=\"$any(toolbarItemFillMode(commandItem))\"\n [title]=\"toolbarItemTitle(commandItem, reason)\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem, reason ? reasonId : null)\"\n [attr.data-toolbar-action]=\"commandItem.id\"\n (open)=\"reason ? $event.preventDefault() : null\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-dropdownbutton>\n } @else {\n <button kendoTooltip kendoButton [svgIcon]=\"$any(commandItem.svgIcon)\"\n [themeColor]=\"toolbarItemThemeColor(commandItem)\"\n [fillMode]=\"$any(toolbarItemFillMode(commandItem))\"\n [attr.data-toolbar-action]=\"commandItem.id\"\n [title]=\"toolbarItemTitle(commandItem, reason)\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [attr.aria-disabled]=\"reason ? 'true' : null\"\n [attr.aria-describedby]=\"reason ? reasonId : null\"\n [disabled]=\"(getToolbarItemDisabled(commandItem) && !reason) || isLoading()\"\n (click)=\"reason ? $event.preventDefault() : onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n }\n @if (reason) {\n <span class=\"mm-list-view-toolbar-reason\" [id]=\"reasonId\">{{ reason }}</span>\n }\n </span>\n }\n @case ('separator') {\n <kendo-separator></kendo-separator>\n }\n }\n }\n</ng-template>\n\n<!-- Disabled reason shown on keyboard focus (AB#6211): opened with the Kendo PopupService under the\n application root, so the grid / toolbar overflow cannot clip it. -->\n<ng-template #toolbarHint>\n <div class=\"mm-list-view-toolbar-hint\" data-toolbar-hint>{{ toolbarHintText() }}</div>\n</ng-template>\n\n<kendo-grid\n mmListViewDataBinding\n [loading]=\"isLoading()\"\n [pageSize]=\"effectivePageSize()\" [selectable]=\"selectable\" (pageChange)=\"onPageChange($event)\"\n [skip]=\"skip\" (selectionChange)=\"onRowSelect($event)\" (cellClick)=\"onCellClick($event)\"\n [pageable]=\"effectivePageable\"\n [sortable]=\"sortable\"\n [filterable]=\"_showRowFilter && !isCardMode\"\n [resizable]=\"resizable\"\n [rowClass]=\"rowClass ?? defaultRowClass\"\n>\n <kendo-grid-messages\n [pagerItemsPerPage]=\"_messages.pagerItemsPerPage\"\n [pagerOf]=\"_messages.pagerOf\"\n [pagerItems]=\"_messages.pagerItems\"\n [pagerPage]=\"_messages.pagerPage\"\n [pagerFirstPage]=\"_messages.pagerFirstPage\"\n [pagerLastPage]=\"_messages.pagerLastPage\"\n [pagerPreviousPage]=\"_messages.pagerPreviousPage\"\n [pagerNextPage]=\"_messages.pagerNextPage\"\n [noRecords]=\"_messages.noRecords\"\n ></kendo-grid-messages>\n <!-- Empty state (AB#3444): one block per list, never per row. -->\n <ng-template kendoGridNoRecordsTemplate>\n @if (emptyTemplate) {\n <ng-container *ngTemplateOutlet=\"emptyTemplate; context: emptyContext\"></ng-container>\n } @else if (emptyState) {\n @if (loadSettled() && !isLoading()) {\n @if (isFiltered) {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"no-results\"\n [heading]=\"emptyState.filteredTitle || _messages.noResultsTitle\"\n [text]=\"emptyState.filteredText || _messages.noResultsText\"></mm-empty-state>\n } @else {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"empty\"\n [heading]=\"emptyState.title\" [text]=\"emptyState.text\"></mm-empty-state>\n }\n }\n } @else {\n {{ _messages.noRecords }}\n }\n </ng-template>\n <ng-template kendoGridToolbarTemplate>\n\n @for (commandItem of leftToolbarActions; track commandItem.id; let i = $index) {\n <ng-container [ngTemplateOutlet]=\"toolbarItem\" [ngTemplateOutletContext]=\"{$implicit: commandItem, side: 'left', index: i}\" />\n }\n\n <!-- Host scope/filter controls (view switches and the like). They steer the\n table, so they sit in the table's own band instead of a second strip\n stacked above the grid. -->\n @if (filtersTemplate) {\n <!-- A rule between the host's actions and its filters: both sit on the\n left of the same band, but one acts on records and the other changes\n what the list shows. Only drawn when actions precede them. -->\n @if (leftToolbarActions.length > 0) {\n <kendo-separator class=\"mm-toolbar-filters-separator\"></kendo-separator>\n }\n <div class=\"mm-toolbar-filters\">\n <ng-container [ngTemplateOutlet]=\"filtersTemplate.templateRef\"></ng-container>\n </div>\n }\n\n <kendo-grid-spacer></kendo-grid-spacer>\n @if (searchTextBoxEnabled) {\n <input\n class=\"k-textbox k-input k-input-md k-rounded-md mm-search-input\"\n [placeholder]=\"_messages.searchPlaceholder\"\n [value]=\"searchValue\"\n (input)=\"onFilter($any($event.target).value || null)\"\n />\n }\n\n @for (commandItem of rightToolbarActions; track commandItem.id; let i = $index) {\n <ng-container [ngTemplateOutlet]=\"toolbarItem\" [ngTemplateOutletContext]=\"{$implicit: commandItem, side: 'right', index: i}\" />\n }\n\n <!-- Commands: laid out while there is room, collapsed into one menu once\n the list gets narrow. Same behaviour in every host app \u2014 a list this\n wide has space for five buttons, and a list this narrow does not. -->\n @if (commandsCollapsed) {\n <kendo-dropdownbutton class=\"mm-toolbar-commands\" [data]=\"toolbarCommands\" textField=\"text\" [svgIcon]=\"commandsIcon\"\n fillMode=\"flat\" themeColor=\"primary\" [disabled]=\"isLoading()\"\n [title]=\"_messages.commands\"\n [buttonAttributes]=\"commandsMenuButtonAttributes\"\n (itemClick)=\"onCommand($any($event).id)\"></kendo-dropdownbutton>\n } @else {\n @for (command of toolbarCommands; track command.id) {\n @switch (command.id) {\n @case ('excel') {\n <button kendoTooltip kendoGridExcelCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @case ('pdf') {\n <button kendoTooltip kendoGridPDFCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @default {\n <!-- The row filter is a toggle: its on state is shown (k-selected) and announced. -->\n <button kendoTooltip kendoButton themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" (click)=\"onCommand(command.id)\"\n [class.k-selected]=\"command.id === 'rowFilter' && _showRowFilter\"\n [attr.aria-pressed]=\"command.id === 'rowFilter' ? _showRowFilter : null\"\n [attr.data-command]=\"command.id\"\n [disabled]=\"isLoading()\" [title]=\"command.text\" [attr.aria-label]=\"command.text\"></button>\n }\n }\n }\n }\n </ng-template>\n\n @if (showCheckboxColumn()) {\n <kendo-grid-checkbox-column [showSelectAll]=\"showRowSelectAllCheckBox\"\n [width]=\"effectiveCheckboxColumnWidth\"></kendo-grid-checkbox-column>\n }\n\n @if (!isCardMode) {\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\" format=\"{{column.format}}\"\n [width]=\"$any(getEffectiveWidth(column))\"\n [minResizableWidth]=\"column.minWidth ?? 10\"\n [hidden]=\"isColumnHidden(column)\"\n [sortable]=\"column.sortable !== false\"\n [filterable]=\"column.filterable !== false\"\n [class]=\"alignClass(column)\"\n [headerClass]=\"alignClass(column)\"\n [title]=\"getDisplayName(column) | pascalCase\">\n <ng-template kendoGridFilterCellTemplate let-filter let-gridColumn=\"column\">\n @if (hasFilterOptions(column)) {\n <kendo-dropdownlist\n class=\"status-filter-dropdown\"\n [data]=\"column.filterOptions!\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n [value]=\"getSelectedFilterValue(column)\"\n [defaultItem]=\"{ text: '', value: null }\"\n [popupSettings]=\"{ width: 'auto' }\"\n (valueChange)=\"onDropdownFilter($event, column)\"\n >\n <ng-template kendoDropDownListValueTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span class=\"filter-status-item\">\n <span role=\"img\" [attr.aria-label]=\"dataItem.text || mapping.tooltip || null\" [title]=\"dataItem.text || mapping.tooltip\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n </span>\n } @else if (dataItem?.text) {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n <ng-template kendoDropDownListItemTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span style=\"display: inline-flex; align-items: center; gap: 8px;\">\n <span [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n <span>{{ dataItem.text }}</span>\n </span>\n } @else {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n </kendo-dropdownlist>\n } @else if (column.dataType === 'numericRange') {\n <span class=\"numeric-range-filter\">\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'gte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'gte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"From\"\n size=\"small\"\n ></kendo-numerictextbox>\n <span class=\"range-separator\">\u2013</span>\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'lte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'lte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"To\"\n size=\"small\"\n ></kendo-numerictextbox>\n </span>\n } @else {\n @switch (getFilterType(column)) {\n @case ('numeric') {\n <kendo-grid-numeric-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [spinners]=\"!isCompactFilter(column)\" [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-numeric-filter-cell>\n }\n @case ('boolean') {\n <kendo-grid-boolean-filter-cell [column]=\"gridColumn\" [filter]=\"filter\"></kendo-grid-boolean-filter-cell>\n }\n @case ('date') {\n <kendo-grid-date-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-date-filter-cell>\n }\n @default {\n <kendo-grid-string-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"false\"></kendo-grid-string-filter-cell>\n }\n }\n }\n </ng-template>\n <ng-template kendoGridCellTemplate let-dataItem>\n <span\n [class.mm-cell-truncate]=\"column.truncate\"\n [attr.title]=\"column.truncate ? getCellTitle(dataItem, column) : null\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </ng-template>\n </kendo-grid-column>\n }\n\n @if (hasRowActions) {\n <kendo-grid-command-column [title]=\"_messages.actionsColumnTitle\" [width]=\"effectiveActionsColumnWidth\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </ng-template>\n </kendo-grid-command-column>\n }\n } @else {\n <!-- Card mode (AB#4930): one full-width column renders the whole row as a\n stacked card \u2014 title from the first column, labeled lines for the\n rest (empty values skipped), row actions in the card header. -->\n <kendo-grid-column [sortable]=\"false\" [filterable]=\"false\" title=\"\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <div class=\"mm-card\">\n <div class=\"mm-card-header\">\n @if (cardTitleColumn; as titleColumn) {\n <span class=\"mm-card-title\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: titleColumn }\"\n ></ng-container>\n </span>\n }\n <span class=\"mm-card-actions\">\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </span>\n </div>\n <div class=\"mm-card-fields\">\n @for (column of cardBodyColumns; track column.field) {\n @if (hasCardValue(dataItem, column)) {\n <div class=\"mm-card-field\">\n <span class=\"mm-card-label\">{{ getDisplayName(column) | pascalCase }}</span>\n <span class=\"mm-card-value\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n </kendo-grid-column>\n }\n\n <kendo-grid-excel fileName=\"{{excelExportFileName}}\">\n @for (column of columns; track column.field) {\n <kendo-excelexport-column [field]=\"column.field\"\n [title]=\"getDisplayName(column) | pascalCase\">\n </kendo-excelexport-column>\n }\n </kendo-grid-excel>\n\n <kendo-grid-pdf fileName=\"{{pdfExportFileName}}\" paperSize=\"A4\" [repeatHeaders]=\"true\"\n [landscape]=\"true\">\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\"\n [title]=\"getDisplayName(column) | pascalCase\"></kendo-grid-column>\n }\n <kendo-grid-pdf-margin top=\"1.5cm\" left=\"1cm\" right=\"1cm\" bottom=\"1.5cm\"></kendo-grid-pdf-margin>\n <ng-template kendoGridPDFTemplate let-pageNum=\"pageNum\" let-totalPages=\"totalPages\">\n <div class=\"page-template\">\n <div class=\"footer\" style=\"position: absolute; bottom: 0; width: 100%; text-align: center; font-size: 9px; color: #666;\">\n {{ getPdfPageText(pageNum, totalPages) }}\n </div>\n </div>\n </ng-template>\n </kendo-grid-pdf>\n</kendo-grid>\n\n<kendo-contextmenu\n #gridmenu\n [kendoMenuHierarchyBinding]=\"_contextMenuItems\"\n [textField]=\"['text']\"\n childrenField=\"items\"\n svgIconField=\"svgIcon\"\n separatorField=\"separator\"\n disabledField=\"disabled\"\n cssClassField=\"cssClass\"\n (popupClose)=\"onContextMenuClosed($event)\"\n (select)=\"onContextMenuSelect($event)\"\n></kendo-contextmenu>\n", styles: [":host{display:flex;flex-direction:column;min-height:0}:host>kendo-grid{flex:1;min-height:0}.mm-search-input{width:165px}.mm-list-view-toolbar-action{position:relative;display:inline-flex}:host ::ng-deep .mm-list-view-toolbar-action--disabled .k-button{opacity:.45;cursor:not-allowed}.mm-list-view-toolbar-reason{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mm-list-view-toolbar-hint{max-width:280px;padding:var(--theme-space-1, 4px) var(--theme-space-2, 8px);font-size:.75rem;line-height:1.4;color:var(--theme-text-primary, #0f1c2e);background:var(--theme-bg-overlay, #ffffff);border:1px solid var(--theme-border-default, rgba(7, 23, 43, .12));border-radius:var(--theme-radius-sm, 4px);box-shadow:var(--theme-shadow-popup, 0 8px 24px rgba(7, 23, 43, .14));pointer-events:none}:host ::ng-deep .k-grid th:has(input[type=checkbox]),:host ::ng-deep .k-grid td:has(>kendo-checkbox){text-overflow:clip}:host ::ng-deep .k-command-cell{white-space:nowrap;overflow:hidden;vertical-align:middle}:host ::ng-deep .k-command-cell .k-button{display:inline-flex;vertical-align:middle;margin:0}:host ::ng-deep .k-command-cell .k-button+.k-button{margin-left:4px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar{flex-wrap:wrap;row-gap:6px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-search-input{width:100%;order:10}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar kendo-grid-spacer{display:none}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-toolbar-commands{margin-left:auto}:host ::ng-deep .k-grid td.mm-col-align-right{text-align:right;font-variant-numeric:tabular-nums}:host ::ng-deep .k-grid td.mm-col-align-center{text-align:center}:host ::ng-deep .k-grid th.mm-col-align-right .k-cell-inner>.k-link{justify-content:flex-end}:host ::ng-deep .k-grid th.mm-col-align-center .k-cell-inner>.k-link{justify-content:center}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-wrapper{gap:2px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-datepicker>.k-input-button{display:none}:host ::ng-deep .k-filter-row .mm-filter-compact .k-input-inner{padding-left:4px;padding-right:4px;font-size:12px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator{width:26px;min-width:0}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator .k-input-button{padding:2px}.mm-cell-truncate{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-icons-cell{display:inline-flex;align-items:center;gap:8px}.status-icons-cell .status-icon{display:inline-flex;align-items:center;justify-content:center;cursor:default}.status-icons-cell .status-icon kendo-svg-icon{width:18px;height:18px}:host ::ng-deep .filter-status-item{display:inline-flex;align-items:center;gap:10px}:host ::ng-deep .status-filter-dropdown .k-input-value-text{font-size:0}:host ::ng-deep .status-filter-dropdown .k-input-value-text .filter-status-item,:host ::ng-deep .status-filter-dropdown .k-input-value-text>span{font-size:14px}.numeric-range-filter{display:flex;align-items:center;gap:4px}.numeric-range-filter kendo-numerictextbox{flex:1;min-width:0}.numeric-range-filter .range-separator{flex-shrink:0;opacity:.6}.cron-expression-cell{display:flex;flex-direction:column;gap:2px}.cron-expression-cell .cron-expression{font-family:Roboto Mono,Consolas,monospace;font-size:.85em;background:var(--mm-cron-code-bg, #eeeeee);color:var(--mm-cron-code-text, #333333);padding:2px 6px;border-radius:3px}.cron-expression-cell .cron-description{font-size:.8em;color:var(--mm-cron-text-secondary, #666666)}.badge-cell{display:inline-block;padding:2px 10px;font-size:.75rem;font-weight:600;border-radius:12px;border:1px solid var(--mm-badge-default-border, transparent);background:var(--mm-badge-default-bg, rgba(127, 127, 127, .15));color:var(--mm-badge-default-color, inherit);white-space:nowrap;line-height:1.4}.badge-cell-default{opacity:.7;font-style:italic}.progress-bar-cell{display:inline-flex;align-items:center;gap:8px;width:100%}.progress-bar-cell .progress-bar-track{flex:1;height:8px;background:var(--mm-progress-track-bg, rgba(255, 255, 255, .1));border-radius:4px;overflow:hidden}.progress-bar-cell .progress-bar-fill{display:block;height:100%;background:var(--mm-progress-fill-color, #10b981);border-radius:4px;transition:width .3s ease}.progress-bar-cell .progress-bar-label{min-width:40px;text-align:right;font-size:.85em}:host-context(.k-pdf-export) .k-grid,:host-context(.k-pdf-export) .k-grid-header,:host-context(.k-pdf-export) .k-grid-content,:host-context(.k-pdf-export) .k-grid-header-wrap,:host-context(.k-pdf-export) .k-grid-content-locked,:host-context(.k-pdf-export) .k-grid-header-locked{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border-color:#000!important}:host-context(.k-pdf-export) .k-grid{border:1px solid #000!important;font-family:Arial,sans-serif!important;font-size:10px!important}:host-context(.k-pdf-export) .k-grid-header th,:host-context(.k-pdf-export) .k-header,:host-context(.k-pdf-export) .k-grid th{background:#f0f0f0!important;background-color:#f0f0f0!important;background-image:none!important;color:#000!important;border:1px solid #000!important;font-weight:700!important;padding:6px 8px!important;text-transform:none!important;letter-spacing:normal!important}:host-context(.k-pdf-export) .k-grid td,:host-context(.k-pdf-export) .k-grid-content td,:host-context(.k-pdf-export) .k-master-row td{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border:1px solid #000!important;padding:4px 8px!important}:host-context(.k-pdf-export) .k-grid tr,:host-context(.k-pdf-export) .k-master-row,:host-context(.k-pdf-export) .k-alt{background:#fff!important;background-color:#fff!important;background-image:none!important}:host-context(.k-pdf-export) .k-grid tr:hover,:host-context(.k-pdf-export) .k-grid tr.k-selected,:host-context(.k-pdf-export) .k-grid td:hover{background:#fff!important;background-color:#fff!important}:host-context(.k-pdf-export) .k-grid-toolbar,:host-context(.k-pdf-export) .k-pager,:host-context(.k-pdf-export) .k-grid-pager,:host-context(.k-pdf-export) .k-command-cell,:host-context(.k-pdf-export) .k-checkbox-column,:host-context(.k-pdf-export) kendo-grid-checkbox-column,:host-context(.k-pdf-export) .status-icons-cell,:host-context(.k-pdf-export) .status-icon,:host-context(.k-pdf-export) kendo-svg-icon{display:none!important}:host-context(.k-pdf-export) .badge-cell{background:transparent!important;border:none!important;color:inherit!important;padding:0!important;border-radius:0!important;text-transform:none!important;font-weight:inherit!important;font-size:inherit!important}:host(.mm-list-view-cards) ::ng-deep .k-grid-header{display:none}:host(.mm-list-view-cards) ::ng-deep .k-grid td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td{border:none;background:transparent;padding:4px 2px;white-space:normal}:host(.mm-list-view-cards) ::ng-deep .k-grid td:has(>kendo-checkbox),:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td:has(>kendo-checkbox){padding-left:2px;padding-right:0}:host(.mm-list-view-cards) ::ng-deep .k-grid colgroup>col:first-child:not(:only-child){width:32px!important}:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row:hover,:host(.mm-list-view-cards) ::ng-deep .k-grid tr:hover{background:transparent}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected .mm-card,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected .mm-card{border-color:var(--kendo-color-primary, #ff6358)}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected>td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected>.k-table-td{background:transparent}.mm-card{display:flex;flex-direction:column;gap:6px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:8px;padding:10px 12px}.mm-card-header{display:flex;align-items:center;gap:8px}.mm-card-title{flex:1;min-width:0;font-weight:600;overflow-wrap:anywhere}.mm-card-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.mm-card-fields{display:flex;flex-direction:column;gap:3px}.mm-card-field{display:flex;align-items:baseline;gap:8px;font-size:.9em}.mm-card-label{flex:0 0 38%;max-width:160px;color:var(--kendo-color-subtle, #666666);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mm-card-value{flex:1;min-width:0;overflow-wrap:anywhere}::ng-deep .k-grid-toolbar .mm-toolbar-filters{display:flex;align-items:center;gap:12px;flex-wrap:wrap}::ng-deep .k-grid-toolbar .mm-toolbar-filters-separator{align-self:center;height:22px;margin:0 4px;border-left:1px solid var(--theme-border-subtle, rgba(0, 0, 0, .16));border-top:none;width:0}::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-link,::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-menu-link{color:var(--theme-status-error, #c0385f)}.mm-list-view-row-actions{display:inline-flex;align-items:center;gap:4px;white-space:nowrap;vertical-align:middle}.mm-list-view-row-actions .mm-list-view-row-actions__more:focus-visible{outline:none;box-shadow:var(--theme-focus-ring, 0 0 0 2px #ffffff, 0 0 0 4px #2e8473)}.boolean-icon{display:inline-flex;align-items:center}.boolean-icon.boolean-icon--true{color:var(--mm-list-boolean-true-color, var(--kendo-color-success, #2fb37a))}.boolean-icon.boolean-icon--false{color:var(--mm-list-boolean-false-color, var(--kendo-color-subtle, #8a94a0))}\n"], dependencies: [{ kind: "component", type: GridComponent, selector: "kendo-grid", inputs: ["data", "pageSize", "height", "rowHeight", "adaptiveMode", "detailRowHeight", "skip", "scrollable", "selectable", "sort", "size", "trackBy", "filter", "group", "virtualColumns", "filterable", "sortable", "pageable", "groupable", "gridResizable", "rowReorderable", "navigable", "autoSize", "rowClass", "rowSticky", "rowSelected", "isRowSelectable", "cellSelected", "resizable", "reorderable", "loading", "columnMenu", "hideHeader", "showInactiveTools", "isDetailExpanded", "isGroupExpanded", "dataLayoutMode", "pinnable", "pinnedTopRows", "pinnedBottomRows", "isRowPinnable"], outputs: ["filterChange", "pageChange", "groupChange", "sortChange", "selectionChange", "rowReorder", "rowPinChange", "dataStateChange", "gridStateChange", "groupExpand", "groupCollapse", "detailExpand", "detailCollapse", "edit", "cancel", "save", "remove", "add", "cellClose", "cellClick", "pdfExport", "excelExport", "csvExport", "columnResize", "columnReorder", "columnVisibilityChange", "columnLockedChange", "columnStickyChange", "scrollBottom", "contentScroll"], exportAs: ["kendoGrid"] }, { kind: "directive", type: MmListViewDataBindingDirective, selector: "[mmListViewDataBinding]" }, { kind: "component", type: ColumnComponent, selector: "kendo-grid-column", inputs: ["field", "format", "sortable", "groupable", "editor", "filter", "filterVariant", "filterable", "editable"] }, { kind: "directive", type: ToolbarTemplateDirective, selector: "[kendoGridToolbarTemplate]", inputs: ["position"] }, { kind: "component", type: GridSpacerComponent, selector: "kendo-grid-spacer", inputs: ["width"] }, { kind: "ngmodule", type: ExcelModule }, { kind: "component", type: i1$2.ExcelComponent, selector: "kendo-grid-excel", inputs: ["fileName", "filterable", "creator", "date", "forceProxy", "proxyURL", "fetchData", "paddingCellOptions", "headerPaddingCellOptions", "collapsible"], outputs: ["fileCreated"] }, { kind: "component", type: i1$2.ExcelCommandDirective, selector: "[kendoGridExcelCommand]" }, { kind: "component", type: i2$1.ColumnComponent, selector: "kendo-excelexport-column", inputs: ["field", "cellOptions", "groupHeaderCellOptions", "groupFooterCellOptions", "footerCellOptions"] }, { kind: "ngmodule", type: PDFModule }, { kind: "component", type: i1$2.PDFComponent, selector: "kendo-grid-pdf", inputs: ["allPages", "delay"] }, { kind: "component", type: i1$2.PDFMarginComponent, selector: "kendo-grid-pdf-margin" }, { kind: "component", type: i1$2.PDFCommandDirective, selector: "[kendoGridPDFCommand]" }, { kind: "directive", type: i1$2.PDFTemplateDirective, selector: "[kendoGridPDFTemplate]" }, { kind: "component", type: ButtonComponent, selector: "button[kendoButton]", inputs: ["arrowIcon", "toggleable", "togglable", "selected", "tabIndex", "imageUrl", "iconPosition", "iconClass", "icon", "disabled", "size", "rounded", "fillMode", "themeColor", "svgIcon"], outputs: ["selectedChange", "click"], exportAs: ["kendoButton"] }, { kind: "component", type: DropDownButtonComponent, selector: "kendo-dropdownbutton", inputs: ["arrowIcon", "icon", "svgIcon", "iconClass", "imageUrl", "textField", "data", "size", "rounded", "fillMode", "themeColor", "buttonAttributes"], outputs: ["itemClick", "focus", "blur"], exportAs: ["kendoDropDownButton"] }, { kind: "component", type: SplitButtonComponent, selector: "kendo-splitbutton", inputs: ["text", "icon", "svgIcon", "iconClass", "type", "imageUrl", "size", "rounded", "fillMode", "themeColor", "disabled", "popupSettings", "tabIndex", "textField", "data", "arrowButtonClass", "arrowButtonIcon", "arrowButtonSvgIcon", "buttonAttributes"], outputs: ["buttonClick", "itemClick", "focus", "blur", "open", "close"], exportAs: ["kendoSplitButton"] }, { kind: "component", type: CommandColumnComponent, selector: "kendo-grid-command-column" }, { kind: "directive", type: CellTemplateDirective, selector: "[kendoGridCellTemplate]" }, { kind: "component", type: ContextMenuComponent, selector: "kendo-contextmenu", inputs: ["showOn", "target", "filter", "alignToAnchor", "vertical", "popupAnimate", "popupAlign", "anchorAlign", "collision", "appendTo", "ariaLabel"], outputs: ["popupOpen", "popupClose", "select", "open", "close"], exportAs: ["kendoContextMenu"] }, { kind: "directive", type: HierarchyBindingDirective, selector: "[kendoMenuHierarchyBinding]", inputs: ["kendoMenuHierarchyBinding", "textField", "urlField", "iconField", "svgIconField", "disabledField", "cssClassField", "cssStyleField", "separatorField", "childrenField"], exportAs: ["kendoMenuHierarchyBinding"] }, { kind: "component", type: CheckboxColumnComponent, selector: "kendo-grid-checkbox-column", inputs: ["showSelectAll", "showDisabledCheckbox"] }, { kind: "component", type: CheckBoxComponent, selector: "kendo-checkbox", inputs: ["checkedState", "rounded"], outputs: ["checkedStateChange"], exportAs: ["kendoCheckBox"] }, { kind: "component", type: SeparatorComponent, selector: "kendo-separator", inputs: ["orientation"] }, { kind: "ngmodule", type: SVGIconModule }, { kind: "component", type: i2.SVGIconComponent, selector: "kendo-svg-icon, kendo-svgicon", inputs: ["icon", "variant"], exportAs: ["kendoSVGIcon"] }, { kind: "component", type: CustomMessagesComponent, selector: "kendo-grid-messages" }, { kind: "directive", type: FilterCellTemplateDirective, selector: "[kendoGridFilterCellTemplate]" }, { kind: "component", type: StringFilterCellComponent, selector: "kendo-grid-string-filter-cell", inputs: ["filterDelay", "showOperators", "placeholder"] }, { kind: "component", type: NumericFilterCellComponent, selector: "kendo-grid-numeric-filter-cell", inputs: ["filterDelay", "showOperators", "placeholder"] }, { kind: "component", type: BooleanFilterCellComponent, selector: "kendo-grid-boolean-filter-cell" }, { kind: "component", type: DateFilterCellComponent, selector: "kendo-grid-date-filter-cell", inputs: ["showOperators"] }, { kind: "component", type: DropDownListComponent, selector: "kendo-dropdownlist", inputs: ["customIconClass", "showStickyHeader", "icon", "svgIcon", "loading", "data", "value", "textField", "valueField", "adaptiveMode", "adaptiveTitle", "adaptiveSubtitle", "popupSettings", "listHeight", "defaultItem", "disabled", "itemDisabled", "readonly", "filterable", "virtual", "ignoreCase", "delay", "valuePrimitive", "tabindex", "tabIndex", "size", "rounded", "fillMode", "leftRightArrowsNavigation", "id"], outputs: ["valueChange", "filterChange", "selectionChange", "open", "opened", "close", "closed", "focus", "blur"], exportAs: ["kendoDropDownList"] }, { kind: "directive", type: ValueTemplateDirective, selector: "[kendoDropDownListValueTemplate],[kendoDropDownTreeValueTemplate]" }, { kind: "directive", type: ItemTemplateDirective, selector: "[kendoDropDownListItemTemplate],[kendoComboBoxItemTemplate],[kendoAutoCompleteItemTemplate],[kendoMultiSelectItemTemplate]" }, { kind: "component", type: NumericTextBoxComponent, selector: "kendo-numerictextbox", inputs: ["focusableId", "disabled", "readonly", "title", "autoCorrect", "format", "max", "min", "decimals", "placeholder", "step", "spinners", "rangeValidation", "tabindex", "tabIndex", "changeValueOnScroll", "selectOnFocus", "value", "maxlength", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "focus", "blur", "inputFocus", "inputBlur"], exportAs: ["kendoNumericTextBox"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ActionButtonComponent, selector: "mm-action-button", inputs: ["action", "targetLabel", "context", "display", "primary"], outputs: ["triggered"] }, { kind: "directive", type: NoRecordsTemplateDirective, selector: "[kendoGridNoRecordsTemplate]" }, { kind: "component", type: EmptyStateComponent, selector: "mm-empty-state", inputs: ["variant", "heading", "text", "size", "showRetry", "retryLabel"], outputs: ["retry"] }, { kind: "pipe", type: PascalCasePipe, name: "pascalCase" }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: BytesToSizePipe, name: "bytesToSize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4016
4113
  }
4017
4114
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: ListViewComponent, decorators: [{
4018
4115
  type: Component,
@@ -4054,7 +4151,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
4054
4151
  ActionButtonComponent,
4055
4152
  NoRecordsTemplateDirective,
4056
4153
  EmptyStateComponent
4057
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Shared cell renderer (AB#4930): used by the regular grid columns AND the\n card-mode body lines, so both render every dataType identically. -->\n<ng-template #cellContent let-element=\"element\" let-column=\"column\">\n @if (column.formatter) {\n {{ getFormattedValue(element, column) }}\n } @else {\n @switch (column.dataType) {\n @case ('boolean') {\n <!-- Read-only marker: named after its column so a screen reader says what is checked (AB#5621). -->\n <kendo-checkbox type=\"checkbox\" [checkedState]=\"$any(getValue(element, column))\" [disabled]=\"true\"\n [inputAttributes]=\"booleanCellAttributes(column)\" />\n }\n @case ('booleanIcon') {\n <!-- Boolean as icon (AB#5623): named \"<column>: Yes|No\" like the status icons (G13). -->\n @if (booleanIconState(element, column); as state) {\n @if (booleanIcon(column, state); as icon) {\n <span\n class=\"boolean-icon\"\n [class.boolean-icon--true]=\"state === 'true'\"\n [class.boolean-icon--false]=\"state === 'false'\"\n role=\"img\"\n [title]=\"booleanIconLabel(column, state)\"\n [attr.aria-label]=\"booleanIconLabel(column, state)\"\n [style.color]=\"state === 'true' ? column.booleanIcons?.trueColor : column.booleanIcons?.falseColor\">\n <kendo-svg-icon [icon]=\"icon\"></kendo-svg-icon>\n </span>\n }\n }\n }\n @case ('iso8601') {\n {{ $any(getValue(element, column)) | date:column.format }}\n }\n @case ('bytes') {\n {{ $any(getValue(element, column)) | bytesToSize }}\n }\n @case ('statusIcons') {\n <span class=\"status-icons-cell\">\n @for (fieldConfig of getStatusFields(column); track fieldConfig.field) {\n @if (getStatusIconMapping(element, fieldConfig); as mapping) {\n <!-- Icon-only status: the mapping's tooltip is also its accessible name (AB#5621). -->\n <span\n class=\"status-icon\"\n role=\"img\"\n [title]=\"mapping.tooltip\"\n [attr.aria-label]=\"mapping.tooltip || null\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\"></kendo-svg-icon>\n </span>\n }\n }\n </span>\n }\n @case ('cronExpression') {\n <span class=\"cron-expression-cell\">\n <code class=\"cron-expression\">{{ getValue(element, column) }}</code>\n <span class=\"cron-description\">{{ getCronHumanReadable(getValue(element, column)) }}</span>\n </span>\n }\n @case ('numeric') {\n {{ $any(getValue(element, column)) | number:column.format }}\n }\n @case ('progressBar') {\n <span class=\"progress-bar-cell\">\n <span class=\"progress-bar-track\">\n <span class=\"progress-bar-fill\" [style.width.%]=\"getValue(element, column)\"></span>\n </span>\n <span class=\"progress-bar-label\">{{ getValue(element, column) }}%</span>\n </span>\n }\n @case ('badge') {\n @if (getBadgeMapping(element, column); as badge) {\n <span\n class=\"badge-cell\"\n [title]=\"badge.tooltip ?? ''\"\n [style.color]=\"badge.color\"\n [style.background]=\"badge.backgroundColor\"\n [style.borderColor]=\"badge.borderColor ?? badge.backgroundColor\">\n {{ badge.label ?? getValue(element, column) }}\n </span>\n } @else if (!column.badgeHideUnmapped) {\n <span class=\"badge-cell badge-cell-default\">{{ getValue(element, column) }}</span>\n }\n }\n @case ('component') {\n @if (column.cellComponent) {\n <ng-container *ngComponentOutlet=\"column.cellComponent; inputs: getCellInputs(element, column)\" />\n }\n }\n @default {\n {{ getValue(element, column) }}\n }\n }\n }\n</ng-template>\n\n<!-- Row actions (AB#4930, AB#5572): shared by the actions column and the card header. One MmAction\n per action (CommandItems via adapter): neutral icon buttons, danger colour only for destructive\n actions, max `maxInlineRowActions` slots, the rest (and the context menu) behind \"\u2026\". Disabled\n actions stay focusable and announce their reason (mm-action-button). -->\n<ng-template #rowActions let-dataItem=\"dataItem\">\n @let view = rowActionsView(dataItem);\n @if (view.inline.length > 0 || view.hasMenu) {\n <span class=\"mm-list-view-row-actions\" role=\"group\" [attr.aria-label]=\"rowActionsGroupLabel(dataItem)\">\n @for (entry of view.inline; track entry.action.id) {\n <!-- Clicks never reach the grid (no row selection / row navigation from an action). -->\n <mm-action-button [action]=\"entry.action\" [targetLabel]=\"rowLabel(dataItem)\" context=\"row\" display=\"icon\"\n (click)=\"$event.stopPropagation()\"\n (triggered)=\"onInlineRowAction(entry, dataItem)\" />\n }\n @if (view.hasMenu) {\n <button kendoButton themeColor=\"base\" fillMode=\"flat\" size=\"small\" class=\"mm-list-view-row-actions__more\"\n [svgIcon]=\"moreVerticalIcon\" data-action=\"more\"\n [title]=\"rowActionsLabel(dataItem)\" [attr.aria-label]=\"rowActionsLabel(dataItem)\" aria-haspopup=\"menu\"\n [disabled]=\"isLoading()\" (click)=\"$event.stopPropagation(); onContextMenu(dataItem, $event)\"></button>\n }\n </span>\n }\n</ng-template>\n\n<kendo-grid\n mmListViewDataBinding\n [loading]=\"isLoading()\"\n [pageSize]=\"effectivePageSize()\" [selectable]=\"selectable\" (pageChange)=\"onPageChange($event)\"\n [skip]=\"skip\" (selectionChange)=\"onRowSelect($event)\" (cellClick)=\"onCellClick($event)\"\n [pageable]=\"effectivePageable\"\n [sortable]=\"sortable\"\n [filterable]=\"_showRowFilter && !isCardMode\"\n [resizable]=\"resizable\"\n [rowClass]=\"rowClass ?? defaultRowClass\"\n>\n <kendo-grid-messages\n [pagerItemsPerPage]=\"_messages.pagerItemsPerPage\"\n [pagerOf]=\"_messages.pagerOf\"\n [pagerItems]=\"_messages.pagerItems\"\n [pagerPage]=\"_messages.pagerPage\"\n [pagerFirstPage]=\"_messages.pagerFirstPage\"\n [pagerLastPage]=\"_messages.pagerLastPage\"\n [pagerPreviousPage]=\"_messages.pagerPreviousPage\"\n [pagerNextPage]=\"_messages.pagerNextPage\"\n [noRecords]=\"_messages.noRecords\"\n ></kendo-grid-messages>\n <!-- Empty state (AB#3444): one block per list, never per row. -->\n <ng-template kendoGridNoRecordsTemplate>\n @if (emptyTemplate) {\n <ng-container *ngTemplateOutlet=\"emptyTemplate; context: emptyContext\"></ng-container>\n } @else if (emptyState) {\n @if (loadSettled() && !isLoading()) {\n @if (isFiltered) {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"no-results\"\n [heading]=\"emptyState.filteredTitle || _messages.noResultsTitle\"\n [text]=\"emptyState.filteredText || _messages.noResultsText\"></mm-empty-state>\n } @else {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"empty\"\n [heading]=\"emptyState.title\" [text]=\"emptyState.text\"></mm-empty-state>\n }\n }\n } @else {\n {{ _messages.noRecords }}\n }\n </ng-template>\n <ng-template kendoGridToolbarTemplate>\n\n @for (commandItem of leftToolbarActions; track commandItem.id) {\n @if (commandItem) {\n @switch (commandItem.type) {\n @case ('link') {\n @if (commandItem.children && commandItem.children.length > 0 && commandItem.onClick) {\n <kendo-splitbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (buttonClick)=\"onToolbarCommand(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-splitbutton>\n } @else if (commandItem.children && commandItem.children.length > 0) {\n <kendo-dropdownbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-dropdownbutton>\n } @else if (commandItem.svgIcon) {\n <button kendoTooltip kendoButton themeColor=\"primary\" [svgIcon]=\"commandItem.svgIcon\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n } @else {\n <button kendoTooltip kendoButton themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n }\n }\n @case ('separator'){\n <kendo-separator></kendo-separator>\n }\n }\n }\n }\n\n <!-- Host scope/filter controls (view switches and the like). They steer the\n table, so they sit in the table's own band instead of a second strip\n stacked above the grid. -->\n @if (filtersTemplate) {\n <!-- A rule between the host's actions and its filters: both sit on the\n left of the same band, but one acts on records and the other changes\n what the list shows. Only drawn when actions precede them. -->\n @if (leftToolbarActions.length > 0) {\n <kendo-separator class=\"mm-toolbar-filters-separator\"></kendo-separator>\n }\n <div class=\"mm-toolbar-filters\">\n <ng-container [ngTemplateOutlet]=\"filtersTemplate.templateRef\"></ng-container>\n </div>\n }\n\n <kendo-grid-spacer></kendo-grid-spacer>\n @if (searchTextBoxEnabled) {\n <input\n class=\"k-textbox k-input k-input-md k-rounded-md mm-search-input\"\n [placeholder]=\"_messages.searchPlaceholder\"\n [value]=\"searchValue\"\n (input)=\"onFilter($any($event.target).value || null)\"\n />\n }\n\n @for (commandItem of rightToolbarActions; track commandItem.id) {\n @if (commandItem) {\n @switch (commandItem.type) {\n @case ('link') {\n @if (commandItem.children && commandItem.children.length > 0 && commandItem.onClick) {\n <kendo-splitbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (buttonClick)=\"onToolbarCommand(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-splitbutton>\n } @else if (commandItem.children && commandItem.children.length > 0) {\n <kendo-dropdownbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n [textField]=\"'text'\"\n themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem)\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-dropdownbutton>\n } @else if (commandItem.svgIcon) {\n <button kendoTooltip kendoButton themeColor=\"primary\" [svgIcon]=\"commandItem.svgIcon\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n } @else {\n <button kendoTooltip kendoButton themeColor=\"primary\"\n [fillMode]=\"$any(commandItem.fillMode ?? 'solid')\"\n [title]=\"commandItem.tooltip ?? commandItem.text\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [disabled]=\"getToolbarItemDisabled(commandItem) || isLoading()\"\n (click)=\"onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n }\n }\n @case ('separator'){\n <kendo-separator></kendo-separator>\n }\n }\n }\n }\n\n <!-- Commands: laid out while there is room, collapsed into one menu once\n the list gets narrow. Same behaviour in every host app \u2014 a list this\n wide has space for five buttons, and a list this narrow does not. -->\n @if (commandsCollapsed) {\n <kendo-dropdownbutton class=\"mm-toolbar-commands\" [data]=\"toolbarCommands\" textField=\"text\" [svgIcon]=\"commandsIcon\"\n fillMode=\"flat\" themeColor=\"primary\" [disabled]=\"isLoading()\"\n [title]=\"_messages.commands\"\n [buttonAttributes]=\"commandsMenuButtonAttributes\"\n (itemClick)=\"onCommand($any($event).id)\"></kendo-dropdownbutton>\n } @else {\n @for (command of toolbarCommands; track command.id) {\n @switch (command.id) {\n @case ('excel') {\n <button kendoTooltip kendoGridExcelCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @case ('pdf') {\n <button kendoTooltip kendoGridPDFCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @default {\n <!-- The row filter is a toggle: its on state is shown (k-selected) and announced. -->\n <button kendoTooltip kendoButton themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" (click)=\"onCommand(command.id)\"\n [class.k-selected]=\"command.id === 'rowFilter' && _showRowFilter\"\n [attr.aria-pressed]=\"command.id === 'rowFilter' ? _showRowFilter : null\"\n [attr.data-command]=\"command.id\"\n [disabled]=\"isLoading()\" [title]=\"command.text\" [attr.aria-label]=\"command.text\"></button>\n }\n }\n }\n }\n </ng-template>\n\n @if (showCheckboxColumn()) {\n <kendo-grid-checkbox-column [showSelectAll]=\"showRowSelectAllCheckBox\"\n [width]=\"effectiveCheckboxColumnWidth\"></kendo-grid-checkbox-column>\n }\n\n @if (!isCardMode) {\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\" format=\"{{column.format}}\"\n [width]=\"$any(getEffectiveWidth(column))\"\n [minResizableWidth]=\"column.minWidth ?? 10\"\n [hidden]=\"isColumnHidden(column)\"\n [sortable]=\"column.sortable !== false\"\n [filterable]=\"column.filterable !== false\"\n [class]=\"alignClass(column)\"\n [headerClass]=\"alignClass(column)\"\n [title]=\"getDisplayName(column) | pascalCase\">\n <ng-template kendoGridFilterCellTemplate let-filter let-gridColumn=\"column\">\n @if (hasFilterOptions(column)) {\n <kendo-dropdownlist\n class=\"status-filter-dropdown\"\n [data]=\"column.filterOptions!\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n [value]=\"getSelectedFilterValue(column)\"\n [defaultItem]=\"{ text: '', value: null }\"\n [popupSettings]=\"{ width: 'auto' }\"\n (valueChange)=\"onDropdownFilter($event, column)\"\n >\n <ng-template kendoDropDownListValueTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span class=\"filter-status-item\">\n <span role=\"img\" [attr.aria-label]=\"dataItem.text || mapping.tooltip || null\" [title]=\"dataItem.text || mapping.tooltip\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n </span>\n } @else if (dataItem?.text) {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n <ng-template kendoDropDownListItemTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span style=\"display: inline-flex; align-items: center; gap: 8px;\">\n <span [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n <span>{{ dataItem.text }}</span>\n </span>\n } @else {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n </kendo-dropdownlist>\n } @else if (column.dataType === 'numericRange') {\n <span class=\"numeric-range-filter\">\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'gte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'gte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"From\"\n size=\"small\"\n ></kendo-numerictextbox>\n <span class=\"range-separator\">\u2013</span>\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'lte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'lte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"To\"\n size=\"small\"\n ></kendo-numerictextbox>\n </span>\n } @else {\n @switch (getFilterType(column)) {\n @case ('numeric') {\n <kendo-grid-numeric-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [spinners]=\"!isCompactFilter(column)\" [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-numeric-filter-cell>\n }\n @case ('boolean') {\n <kendo-grid-boolean-filter-cell [column]=\"gridColumn\" [filter]=\"filter\"></kendo-grid-boolean-filter-cell>\n }\n @case ('date') {\n <kendo-grid-date-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-date-filter-cell>\n }\n @default {\n <kendo-grid-string-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"false\"></kendo-grid-string-filter-cell>\n }\n }\n }\n </ng-template>\n <ng-template kendoGridCellTemplate let-dataItem>\n <span\n [class.mm-cell-truncate]=\"column.truncate\"\n [attr.title]=\"column.truncate ? getCellTitle(dataItem, column) : null\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </ng-template>\n </kendo-grid-column>\n }\n\n @if (hasRowActions) {\n <kendo-grid-command-column [title]=\"_messages.actionsColumnTitle\" [width]=\"effectiveActionsColumnWidth\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </ng-template>\n </kendo-grid-command-column>\n }\n } @else {\n <!-- Card mode (AB#4930): one full-width column renders the whole row as a\n stacked card \u2014 title from the first column, labeled lines for the\n rest (empty values skipped), row actions in the card header. -->\n <kendo-grid-column [sortable]=\"false\" [filterable]=\"false\" title=\"\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <div class=\"mm-card\">\n <div class=\"mm-card-header\">\n @if (cardTitleColumn; as titleColumn) {\n <span class=\"mm-card-title\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: titleColumn }\"\n ></ng-container>\n </span>\n }\n <span class=\"mm-card-actions\">\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </span>\n </div>\n <div class=\"mm-card-fields\">\n @for (column of cardBodyColumns; track column.field) {\n @if (hasCardValue(dataItem, column)) {\n <div class=\"mm-card-field\">\n <span class=\"mm-card-label\">{{ getDisplayName(column) | pascalCase }}</span>\n <span class=\"mm-card-value\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n </kendo-grid-column>\n }\n\n <kendo-grid-excel fileName=\"{{excelExportFileName}}\">\n @for (column of columns; track column.field) {\n <kendo-excelexport-column [field]=\"column.field\"\n [title]=\"getDisplayName(column) | pascalCase\">\n </kendo-excelexport-column>\n }\n </kendo-grid-excel>\n\n <kendo-grid-pdf fileName=\"{{pdfExportFileName}}\" paperSize=\"A4\" [repeatHeaders]=\"true\"\n [landscape]=\"true\">\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\"\n [title]=\"getDisplayName(column) | pascalCase\"></kendo-grid-column>\n }\n <kendo-grid-pdf-margin top=\"1.5cm\" left=\"1cm\" right=\"1cm\" bottom=\"1.5cm\"></kendo-grid-pdf-margin>\n <ng-template kendoGridPDFTemplate let-pageNum=\"pageNum\" let-totalPages=\"totalPages\">\n <div class=\"page-template\">\n <div class=\"footer\" style=\"position: absolute; bottom: 0; width: 100%; text-align: center; font-size: 9px; color: #666;\">\n {{ getPdfPageText(pageNum, totalPages) }}\n </div>\n </div>\n </ng-template>\n </kendo-grid-pdf>\n</kendo-grid>\n\n<kendo-contextmenu\n #gridmenu\n [kendoMenuHierarchyBinding]=\"_contextMenuItems\"\n [textField]=\"['text']\"\n childrenField=\"items\"\n svgIconField=\"svgIcon\"\n separatorField=\"separator\"\n disabledField=\"disabled\"\n cssClassField=\"cssClass\"\n (popupClose)=\"onContextMenuClosed($event)\"\n (select)=\"onContextMenuSelect($event)\"\n></kendo-contextmenu>\n", styles: [":host{display:flex;flex-direction:column;min-height:0}:host>kendo-grid{flex:1;min-height:0}.mm-search-input{width:165px}:host ::ng-deep .k-grid th:has(input[type=checkbox]),:host ::ng-deep .k-grid td:has(>kendo-checkbox){text-overflow:clip}:host ::ng-deep .k-command-cell{white-space:nowrap;overflow:hidden;vertical-align:middle}:host ::ng-deep .k-command-cell .k-button{display:inline-flex;vertical-align:middle;margin:0}:host ::ng-deep .k-command-cell .k-button+.k-button{margin-left:4px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar{flex-wrap:wrap;row-gap:6px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-search-input{width:100%;order:10}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar kendo-grid-spacer{display:none}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-toolbar-commands{margin-left:auto}:host ::ng-deep .k-grid td.mm-col-align-right{text-align:right;font-variant-numeric:tabular-nums}:host ::ng-deep .k-grid td.mm-col-align-center{text-align:center}:host ::ng-deep .k-grid th.mm-col-align-right .k-cell-inner>.k-link{justify-content:flex-end}:host ::ng-deep .k-grid th.mm-col-align-center .k-cell-inner>.k-link{justify-content:center}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-wrapper{gap:2px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-datepicker>.k-input-button{display:none}:host ::ng-deep .k-filter-row .mm-filter-compact .k-input-inner{padding-left:4px;padding-right:4px;font-size:12px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator{width:26px;min-width:0}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator .k-input-button{padding:2px}.mm-cell-truncate{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-icons-cell{display:inline-flex;align-items:center;gap:8px}.status-icons-cell .status-icon{display:inline-flex;align-items:center;justify-content:center;cursor:default}.status-icons-cell .status-icon kendo-svg-icon{width:18px;height:18px}:host ::ng-deep .filter-status-item{display:inline-flex;align-items:center;gap:10px}:host ::ng-deep .status-filter-dropdown .k-input-value-text{font-size:0}:host ::ng-deep .status-filter-dropdown .k-input-value-text .filter-status-item,:host ::ng-deep .status-filter-dropdown .k-input-value-text>span{font-size:14px}.numeric-range-filter{display:flex;align-items:center;gap:4px}.numeric-range-filter kendo-numerictextbox{flex:1;min-width:0}.numeric-range-filter .range-separator{flex-shrink:0;opacity:.6}.cron-expression-cell{display:flex;flex-direction:column;gap:2px}.cron-expression-cell .cron-expression{font-family:Roboto Mono,Consolas,monospace;font-size:.85em;background:var(--mm-cron-code-bg, #eeeeee);color:var(--mm-cron-code-text, #333333);padding:2px 6px;border-radius:3px}.cron-expression-cell .cron-description{font-size:.8em;color:var(--mm-cron-text-secondary, #666666)}.badge-cell{display:inline-block;padding:2px 10px;font-size:.75rem;font-weight:600;border-radius:12px;border:1px solid var(--mm-badge-default-border, transparent);background:var(--mm-badge-default-bg, rgba(127, 127, 127, .15));color:var(--mm-badge-default-color, inherit);white-space:nowrap;line-height:1.4}.badge-cell-default{opacity:.7;font-style:italic}.progress-bar-cell{display:inline-flex;align-items:center;gap:8px;width:100%}.progress-bar-cell .progress-bar-track{flex:1;height:8px;background:var(--mm-progress-track-bg, rgba(255, 255, 255, .1));border-radius:4px;overflow:hidden}.progress-bar-cell .progress-bar-fill{display:block;height:100%;background:var(--mm-progress-fill-color, #10b981);border-radius:4px;transition:width .3s ease}.progress-bar-cell .progress-bar-label{min-width:40px;text-align:right;font-size:.85em}:host-context(.k-pdf-export) .k-grid,:host-context(.k-pdf-export) .k-grid-header,:host-context(.k-pdf-export) .k-grid-content,:host-context(.k-pdf-export) .k-grid-header-wrap,:host-context(.k-pdf-export) .k-grid-content-locked,:host-context(.k-pdf-export) .k-grid-header-locked{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border-color:#000!important}:host-context(.k-pdf-export) .k-grid{border:1px solid #000!important;font-family:Arial,sans-serif!important;font-size:10px!important}:host-context(.k-pdf-export) .k-grid-header th,:host-context(.k-pdf-export) .k-header,:host-context(.k-pdf-export) .k-grid th{background:#f0f0f0!important;background-color:#f0f0f0!important;background-image:none!important;color:#000!important;border:1px solid #000!important;font-weight:700!important;padding:6px 8px!important;text-transform:none!important;letter-spacing:normal!important}:host-context(.k-pdf-export) .k-grid td,:host-context(.k-pdf-export) .k-grid-content td,:host-context(.k-pdf-export) .k-master-row td{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border:1px solid #000!important;padding:4px 8px!important}:host-context(.k-pdf-export) .k-grid tr,:host-context(.k-pdf-export) .k-master-row,:host-context(.k-pdf-export) .k-alt{background:#fff!important;background-color:#fff!important;background-image:none!important}:host-context(.k-pdf-export) .k-grid tr:hover,:host-context(.k-pdf-export) .k-grid tr.k-selected,:host-context(.k-pdf-export) .k-grid td:hover{background:#fff!important;background-color:#fff!important}:host-context(.k-pdf-export) .k-grid-toolbar,:host-context(.k-pdf-export) .k-pager,:host-context(.k-pdf-export) .k-grid-pager,:host-context(.k-pdf-export) .k-command-cell,:host-context(.k-pdf-export) .k-checkbox-column,:host-context(.k-pdf-export) kendo-grid-checkbox-column,:host-context(.k-pdf-export) .status-icons-cell,:host-context(.k-pdf-export) .status-icon,:host-context(.k-pdf-export) kendo-svg-icon{display:none!important}:host-context(.k-pdf-export) .badge-cell{background:transparent!important;border:none!important;color:inherit!important;padding:0!important;border-radius:0!important;text-transform:none!important;font-weight:inherit!important;font-size:inherit!important}:host(.mm-list-view-cards) ::ng-deep .k-grid-header{display:none}:host(.mm-list-view-cards) ::ng-deep .k-grid td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td{border:none;background:transparent;padding:4px 2px;white-space:normal}:host(.mm-list-view-cards) ::ng-deep .k-grid td:has(>kendo-checkbox),:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td:has(>kendo-checkbox){padding-left:2px;padding-right:0}:host(.mm-list-view-cards) ::ng-deep .k-grid colgroup>col:first-child:not(:only-child){width:32px!important}:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row:hover,:host(.mm-list-view-cards) ::ng-deep .k-grid tr:hover{background:transparent}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected .mm-card,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected .mm-card{border-color:var(--kendo-color-primary, #ff6358)}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected>td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected>.k-table-td{background:transparent}.mm-card{display:flex;flex-direction:column;gap:6px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:8px;padding:10px 12px}.mm-card-header{display:flex;align-items:center;gap:8px}.mm-card-title{flex:1;min-width:0;font-weight:600;overflow-wrap:anywhere}.mm-card-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.mm-card-fields{display:flex;flex-direction:column;gap:3px}.mm-card-field{display:flex;align-items:baseline;gap:8px;font-size:.9em}.mm-card-label{flex:0 0 38%;max-width:160px;color:var(--kendo-color-subtle, #666666);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mm-card-value{flex:1;min-width:0;overflow-wrap:anywhere}::ng-deep .k-grid-toolbar .mm-toolbar-filters{display:flex;align-items:center;gap:12px;flex-wrap:wrap}::ng-deep .k-grid-toolbar .mm-toolbar-filters-separator{align-self:center;height:22px;margin:0 4px;border-left:1px solid var(--theme-border-subtle, rgba(0, 0, 0, .16));border-top:none;width:0}::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-link,::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-menu-link{color:var(--theme-status-error, #c0385f)}.mm-list-view-row-actions{display:inline-flex;align-items:center;gap:4px;white-space:nowrap;vertical-align:middle}.mm-list-view-row-actions .mm-list-view-row-actions__more:focus-visible{outline:none;box-shadow:var(--theme-focus-ring, 0 0 0 2px #ffffff, 0 0 0 4px #2e8473)}.boolean-icon{display:inline-flex;align-items:center}.boolean-icon.boolean-icon--true{color:var(--mm-list-boolean-true-color, var(--kendo-color-success, #2fb37a))}.boolean-icon.boolean-icon--false{color:var(--mm-list-boolean-false-color, var(--kendo-color-subtle, #8a94a0))}\n"] }]
4154
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Shared cell renderer (AB#4930): used by the regular grid columns AND the\n card-mode body lines, so both render every dataType identically. -->\n<ng-template #cellContent let-element=\"element\" let-column=\"column\">\n @if (column.formatter) {\n {{ getFormattedValue(element, column) }}\n } @else {\n @switch (column.dataType) {\n @case ('boolean') {\n <!-- Read-only marker: named after its column so a screen reader says what is checked (AB#5621). -->\n <kendo-checkbox type=\"checkbox\" [checkedState]=\"$any(getValue(element, column))\" [disabled]=\"true\"\n [inputAttributes]=\"booleanCellAttributes(column)\" />\n }\n @case ('booleanIcon') {\n <!-- Boolean as icon (AB#5623): named \"<column>: Yes|No\" like the status icons (G13). -->\n @if (booleanIconState(element, column); as state) {\n @if (booleanIcon(column, state); as icon) {\n <span\n class=\"boolean-icon\"\n [class.boolean-icon--true]=\"state === 'true'\"\n [class.boolean-icon--false]=\"state === 'false'\"\n role=\"img\"\n [title]=\"booleanIconLabel(column, state)\"\n [attr.aria-label]=\"booleanIconLabel(column, state)\"\n [style.color]=\"state === 'true' ? column.booleanIcons?.trueColor : column.booleanIcons?.falseColor\">\n <kendo-svg-icon [icon]=\"icon\"></kendo-svg-icon>\n </span>\n }\n }\n }\n @case ('iso8601') {\n {{ $any(getValue(element, column)) | date:column.format }}\n }\n @case ('bytes') {\n {{ $any(getValue(element, column)) | bytesToSize }}\n }\n @case ('statusIcons') {\n <span class=\"status-icons-cell\">\n @for (fieldConfig of getStatusFields(column); track fieldConfig.field) {\n @if (getStatusIconMapping(element, fieldConfig); as mapping) {\n <!-- Icon-only status: the mapping's tooltip is also its accessible name (AB#5621). -->\n <span\n class=\"status-icon\"\n role=\"img\"\n [title]=\"mapping.tooltip\"\n [attr.aria-label]=\"mapping.tooltip || null\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\"></kendo-svg-icon>\n </span>\n }\n }\n </span>\n }\n @case ('cronExpression') {\n <span class=\"cron-expression-cell\">\n <code class=\"cron-expression\">{{ getValue(element, column) }}</code>\n <span class=\"cron-description\">{{ getCronHumanReadable(getValue(element, column)) }}</span>\n </span>\n }\n @case ('numeric') {\n {{ $any(getValue(element, column)) | number:column.format }}\n }\n @case ('progressBar') {\n <span class=\"progress-bar-cell\">\n <span class=\"progress-bar-track\">\n <span class=\"progress-bar-fill\" [style.width.%]=\"getValue(element, column)\"></span>\n </span>\n <span class=\"progress-bar-label\">{{ getValue(element, column) }}%</span>\n </span>\n }\n @case ('badge') {\n @if (getBadgeMapping(element, column); as badge) {\n <span\n class=\"badge-cell\"\n [title]=\"badge.tooltip ?? ''\"\n [style.color]=\"badge.color\"\n [style.background]=\"badge.backgroundColor\"\n [style.borderColor]=\"badge.borderColor ?? badge.backgroundColor\">\n {{ badge.label ?? getValue(element, column) }}\n </span>\n } @else if (!column.badgeHideUnmapped) {\n <span class=\"badge-cell badge-cell-default\">{{ getValue(element, column) }}</span>\n }\n }\n @case ('component') {\n @if (column.cellComponent) {\n <ng-container *ngComponentOutlet=\"column.cellComponent; inputs: getCellInputs(element, column)\" />\n }\n }\n @default {\n {{ getValue(element, column) }}\n }\n }\n }\n</ng-template>\n\n<!-- Row actions (AB#4930, AB#5572): shared by the actions column and the card header. One MmAction\n per action (CommandItems via adapter): neutral icon buttons, danger colour only for destructive\n actions, max `maxInlineRowActions` slots, the rest (and the context menu) behind \"\u2026\". Disabled\n actions stay focusable and announce their reason (mm-action-button). -->\n<ng-template #rowActions let-dataItem=\"dataItem\">\n @let view = rowActionsView(dataItem);\n @if (view.inline.length > 0 || view.hasMenu) {\n <span class=\"mm-list-view-row-actions\" role=\"group\" [attr.aria-label]=\"rowActionsGroupLabel(dataItem)\">\n @for (entry of view.inline; track entry.action.id) {\n <!-- Clicks never reach the grid (no row selection / row navigation from an action). -->\n <mm-action-button [action]=\"entry.action\" [targetLabel]=\"rowLabel(dataItem)\" context=\"row\" display=\"icon\"\n (click)=\"$event.stopPropagation()\"\n (triggered)=\"onInlineRowAction(entry, dataItem)\" />\n }\n @if (view.hasMenu) {\n <button kendoButton themeColor=\"base\" fillMode=\"flat\" size=\"small\" class=\"mm-list-view-row-actions__more\"\n [svgIcon]=\"moreVerticalIcon\" data-action=\"more\"\n [title]=\"rowActionsLabel(dataItem)\" [attr.aria-label]=\"rowActionsLabel(dataItem)\" aria-haspopup=\"menu\"\n [disabled]=\"isLoading()\" (click)=\"$event.stopPropagation(); onContextMenu(dataItem, $event)\"></button>\n }\n </span>\n }\n</ng-template>\n\n<!-- One host toolbar action (left or right band). Flat + neutral unless `primary` (AB#6211). With\n `isDisabled` + `disabledReason` the control stays focusable (aria-disabled), announces the reason\n (aria-describedby) and shows it as a hint bubble on keyboard focus (guideline \u00A72.5); clicks and\n menu opening are swallowed. Applies to buttons, split and dropdown buttons alike. -->\n<ng-template #toolbarItem let-commandItem let-side=\"side\" let-index=\"index\">\n @if (commandItem) {\n @switch (commandItem.type) {\n @case ('link') {\n @let reason = toolbarItemDisabledReason(commandItem);\n @let reasonId = toolbarReasonId(side, index);\n <span class=\"mm-list-view-toolbar-action\" [class.mm-list-view-toolbar-action--disabled]=\"!!reason\"\n (pointerdown)=\"onToolbarPointerDown()\"\n (focusin)=\"onToolbarFocusIn($event, reason)\"\n (focusout)=\"closeToolbarHint()\"\n (keydown.escape)=\"closeToolbarHint()\">\n @if (commandItem.children && commandItem.children.length > 0 && commandItem.onClick) {\n <kendo-splitbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"(getToolbarItemDisabled(commandItem) && !reason) || isLoading()\"\n [textField]=\"'text'\"\n [themeColor]=\"toolbarItemThemeColor(commandItem)\"\n [fillMode]=\"$any(toolbarItemFillMode(commandItem))\"\n [title]=\"toolbarItemTitle(commandItem, reason)\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem, reason ? reasonId : null)\"\n [attr.data-toolbar-action]=\"commandItem.id\"\n (buttonClick)=\"reason ? null : onToolbarCommand(commandItem)\"\n (open)=\"reason ? $event.preventDefault() : null\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-splitbutton>\n } @else if (commandItem.children && commandItem.children.length > 0) {\n <kendo-dropdownbutton\n [data]=\"commandItem.children\"\n [svgIcon]=\"$any(commandItem.svgIcon)\"\n [disabled]=\"(getToolbarItemDisabled(commandItem) && !reason) || isLoading()\"\n [textField]=\"'text'\"\n [themeColor]=\"toolbarItemThemeColor(commandItem)\"\n [fillMode]=\"$any(toolbarItemFillMode(commandItem))\"\n [title]=\"toolbarItemTitle(commandItem, reason)\"\n [buttonAttributes]=\"toolbarItemButtonAttributes(commandItem, reason ? reasonId : null)\"\n [attr.data-toolbar-action]=\"commandItem.id\"\n (open)=\"reason ? $event.preventDefault() : null\"\n (itemClick)=\"onToolbarDropdownItemClick($event)\">\n {{ commandItem.text }}\n </kendo-dropdownbutton>\n } @else {\n <button kendoTooltip kendoButton [svgIcon]=\"$any(commandItem.svgIcon)\"\n [themeColor]=\"toolbarItemThemeColor(commandItem)\"\n [fillMode]=\"$any(toolbarItemFillMode(commandItem))\"\n [attr.data-toolbar-action]=\"commandItem.id\"\n [title]=\"toolbarItemTitle(commandItem, reason)\"\n [attr.aria-label]=\"toolbarItemAriaLabel(commandItem)\"\n [attr.aria-disabled]=\"reason ? 'true' : null\"\n [attr.aria-describedby]=\"reason ? reasonId : null\"\n [disabled]=\"(getToolbarItemDisabled(commandItem) && !reason) || isLoading()\"\n (click)=\"reason ? $event.preventDefault() : onToolbarCommand(commandItem)\">{{ commandItem.text }}\n </button>\n }\n @if (reason) {\n <span class=\"mm-list-view-toolbar-reason\" [id]=\"reasonId\">{{ reason }}</span>\n }\n </span>\n }\n @case ('separator') {\n <kendo-separator></kendo-separator>\n }\n }\n }\n</ng-template>\n\n<!-- Disabled reason shown on keyboard focus (AB#6211): opened with the Kendo PopupService under the\n application root, so the grid / toolbar overflow cannot clip it. -->\n<ng-template #toolbarHint>\n <div class=\"mm-list-view-toolbar-hint\" data-toolbar-hint>{{ toolbarHintText() }}</div>\n</ng-template>\n\n<kendo-grid\n mmListViewDataBinding\n [loading]=\"isLoading()\"\n [pageSize]=\"effectivePageSize()\" [selectable]=\"selectable\" (pageChange)=\"onPageChange($event)\"\n [skip]=\"skip\" (selectionChange)=\"onRowSelect($event)\" (cellClick)=\"onCellClick($event)\"\n [pageable]=\"effectivePageable\"\n [sortable]=\"sortable\"\n [filterable]=\"_showRowFilter && !isCardMode\"\n [resizable]=\"resizable\"\n [rowClass]=\"rowClass ?? defaultRowClass\"\n>\n <kendo-grid-messages\n [pagerItemsPerPage]=\"_messages.pagerItemsPerPage\"\n [pagerOf]=\"_messages.pagerOf\"\n [pagerItems]=\"_messages.pagerItems\"\n [pagerPage]=\"_messages.pagerPage\"\n [pagerFirstPage]=\"_messages.pagerFirstPage\"\n [pagerLastPage]=\"_messages.pagerLastPage\"\n [pagerPreviousPage]=\"_messages.pagerPreviousPage\"\n [pagerNextPage]=\"_messages.pagerNextPage\"\n [noRecords]=\"_messages.noRecords\"\n ></kendo-grid-messages>\n <!-- Empty state (AB#3444): one block per list, never per row. -->\n <ng-template kendoGridNoRecordsTemplate>\n @if (emptyTemplate) {\n <ng-container *ngTemplateOutlet=\"emptyTemplate; context: emptyContext\"></ng-container>\n } @else if (emptyState) {\n @if (loadSettled() && !isLoading()) {\n @if (isFiltered) {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"no-results\"\n [heading]=\"emptyState.filteredTitle || _messages.noResultsTitle\"\n [text]=\"emptyState.filteredText || _messages.noResultsText\"></mm-empty-state>\n } @else {\n <mm-empty-state class=\"mm-list-view-empty\" variant=\"empty\"\n [heading]=\"emptyState.title\" [text]=\"emptyState.text\"></mm-empty-state>\n }\n }\n } @else {\n {{ _messages.noRecords }}\n }\n </ng-template>\n <ng-template kendoGridToolbarTemplate>\n\n @for (commandItem of leftToolbarActions; track commandItem.id; let i = $index) {\n <ng-container [ngTemplateOutlet]=\"toolbarItem\" [ngTemplateOutletContext]=\"{$implicit: commandItem, side: 'left', index: i}\" />\n }\n\n <!-- Host scope/filter controls (view switches and the like). They steer the\n table, so they sit in the table's own band instead of a second strip\n stacked above the grid. -->\n @if (filtersTemplate) {\n <!-- A rule between the host's actions and its filters: both sit on the\n left of the same band, but one acts on records and the other changes\n what the list shows. Only drawn when actions precede them. -->\n @if (leftToolbarActions.length > 0) {\n <kendo-separator class=\"mm-toolbar-filters-separator\"></kendo-separator>\n }\n <div class=\"mm-toolbar-filters\">\n <ng-container [ngTemplateOutlet]=\"filtersTemplate.templateRef\"></ng-container>\n </div>\n }\n\n <kendo-grid-spacer></kendo-grid-spacer>\n @if (searchTextBoxEnabled) {\n <input\n class=\"k-textbox k-input k-input-md k-rounded-md mm-search-input\"\n [placeholder]=\"_messages.searchPlaceholder\"\n [value]=\"searchValue\"\n (input)=\"onFilter($any($event.target).value || null)\"\n />\n }\n\n @for (commandItem of rightToolbarActions; track commandItem.id; let i = $index) {\n <ng-container [ngTemplateOutlet]=\"toolbarItem\" [ngTemplateOutletContext]=\"{$implicit: commandItem, side: 'right', index: i}\" />\n }\n\n <!-- Commands: laid out while there is room, collapsed into one menu once\n the list gets narrow. Same behaviour in every host app \u2014 a list this\n wide has space for five buttons, and a list this narrow does not. -->\n @if (commandsCollapsed) {\n <kendo-dropdownbutton class=\"mm-toolbar-commands\" [data]=\"toolbarCommands\" textField=\"text\" [svgIcon]=\"commandsIcon\"\n fillMode=\"flat\" themeColor=\"primary\" [disabled]=\"isLoading()\"\n [title]=\"_messages.commands\"\n [buttonAttributes]=\"commandsMenuButtonAttributes\"\n (itemClick)=\"onCommand($any($event).id)\"></kendo-dropdownbutton>\n } @else {\n @for (command of toolbarCommands; track command.id) {\n @switch (command.id) {\n @case ('excel') {\n <button kendoTooltip kendoGridExcelCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @case ('pdf') {\n <button kendoTooltip kendoGridPDFCommand themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" [disabled]=\"isLoading()\" [title]=\"command.text\"\n [attr.aria-label]=\"command.text\" [attr.data-command]=\"command.id\"></button>\n }\n @default {\n <!-- The row filter is a toggle: its on state is shown (k-selected) and announced. -->\n <button kendoTooltip kendoButton themeColor=\"primary\" fillMode=\"flat\"\n [svgIcon]=\"command.svgIcon\" (click)=\"onCommand(command.id)\"\n [class.k-selected]=\"command.id === 'rowFilter' && _showRowFilter\"\n [attr.aria-pressed]=\"command.id === 'rowFilter' ? _showRowFilter : null\"\n [attr.data-command]=\"command.id\"\n [disabled]=\"isLoading()\" [title]=\"command.text\" [attr.aria-label]=\"command.text\"></button>\n }\n }\n }\n }\n </ng-template>\n\n @if (showCheckboxColumn()) {\n <kendo-grid-checkbox-column [showSelectAll]=\"showRowSelectAllCheckBox\"\n [width]=\"effectiveCheckboxColumnWidth\"></kendo-grid-checkbox-column>\n }\n\n @if (!isCardMode) {\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\" format=\"{{column.format}}\"\n [width]=\"$any(getEffectiveWidth(column))\"\n [minResizableWidth]=\"column.minWidth ?? 10\"\n [hidden]=\"isColumnHidden(column)\"\n [sortable]=\"column.sortable !== false\"\n [filterable]=\"column.filterable !== false\"\n [class]=\"alignClass(column)\"\n [headerClass]=\"alignClass(column)\"\n [title]=\"getDisplayName(column) | pascalCase\">\n <ng-template kendoGridFilterCellTemplate let-filter let-gridColumn=\"column\">\n @if (hasFilterOptions(column)) {\n <kendo-dropdownlist\n class=\"status-filter-dropdown\"\n [data]=\"column.filterOptions!\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n [value]=\"getSelectedFilterValue(column)\"\n [defaultItem]=\"{ text: '', value: null }\"\n [popupSettings]=\"{ width: 'auto' }\"\n (valueChange)=\"onDropdownFilter($event, column)\"\n >\n <ng-template kendoDropDownListValueTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span class=\"filter-status-item\">\n <span role=\"img\" [attr.aria-label]=\"dataItem.text || mapping.tooltip || null\" [title]=\"dataItem.text || mapping.tooltip\"\n [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n </span>\n } @else if (dataItem?.text) {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n <ng-template kendoDropDownListItemTemplate let-dataItem>\n @if (dataItem?.value && column.statusMapping?.[dataItem.value]; as mapping) {\n <span style=\"display: inline-flex; align-items: center; gap: 8px;\">\n <span [style.color]=\"mapping.color\">\n <kendo-svg-icon [icon]=\"mapping.icon\" [size]=\"'small'\"></kendo-svg-icon>\n </span>\n <span>{{ dataItem.text }}</span>\n </span>\n } @else {\n <span>{{ dataItem.text }}</span>\n }\n </ng-template>\n </kendo-dropdownlist>\n } @else if (column.dataType === 'numericRange') {\n <span class=\"numeric-range-filter\">\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'gte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'gte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"From\"\n size=\"small\"\n ></kendo-numerictextbox>\n <span class=\"range-separator\">\u2013</span>\n <kendo-numerictextbox\n [value]=\"$any(getRangeFilterValue(column, 'lte'))\"\n (valueChange)=\"onRangeFilterChange($event, column, 'lte')\"\n [spinners]=\"false\"\n [decimals]=\"2\"\n placeholder=\"To\"\n size=\"small\"\n ></kendo-numerictextbox>\n </span>\n } @else {\n @switch (getFilterType(column)) {\n @case ('numeric') {\n <kendo-grid-numeric-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [spinners]=\"!isCompactFilter(column)\" [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-numeric-filter-cell>\n }\n @case ('boolean') {\n <kendo-grid-boolean-filter-cell [column]=\"gridColumn\" [filter]=\"filter\"></kendo-grid-boolean-filter-cell>\n }\n @case ('date') {\n <kendo-grid-date-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"true\" [operator]=\"column.filterOperator || 'eq'\"\n [class.mm-filter-compact]=\"isCompactFilter(column)\"></kendo-grid-date-filter-cell>\n }\n @default {\n <kendo-grid-string-filter-cell [column]=\"gridColumn\" [filter]=\"filter\" [showOperators]=\"false\"></kendo-grid-string-filter-cell>\n }\n }\n }\n </ng-template>\n <ng-template kendoGridCellTemplate let-dataItem>\n <span\n [class.mm-cell-truncate]=\"column.truncate\"\n [attr.title]=\"column.truncate ? getCellTitle(dataItem, column) : null\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </ng-template>\n </kendo-grid-column>\n }\n\n @if (hasRowActions) {\n <kendo-grid-command-column [title]=\"_messages.actionsColumnTitle\" [width]=\"effectiveActionsColumnWidth\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </ng-template>\n </kendo-grid-command-column>\n }\n } @else {\n <!-- Card mode (AB#4930): one full-width column renders the whole row as a\n stacked card \u2014 title from the first column, labeled lines for the\n rest (empty values skipped), row actions in the card header. -->\n <kendo-grid-column [sortable]=\"false\" [filterable]=\"false\" title=\"\">\n <ng-template kendoGridCellTemplate let-dataItem>\n <div class=\"mm-card\">\n <div class=\"mm-card-header\">\n @if (cardTitleColumn; as titleColumn) {\n <span class=\"mm-card-title\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: titleColumn }\"\n ></ng-container>\n </span>\n }\n <span class=\"mm-card-actions\">\n <ng-container\n [ngTemplateOutlet]=\"rowActions\"\n [ngTemplateOutletContext]=\"{ dataItem: dataItem }\"\n ></ng-container>\n </span>\n </div>\n <div class=\"mm-card-fields\">\n @for (column of cardBodyColumns; track column.field) {\n @if (hasCardValue(dataItem, column)) {\n <div class=\"mm-card-field\">\n <span class=\"mm-card-label\">{{ getDisplayName(column) | pascalCase }}</span>\n <span class=\"mm-card-value\">\n <ng-container\n [ngTemplateOutlet]=\"cellContent\"\n [ngTemplateOutletContext]=\"{ element: dataItem, column: column }\"\n ></ng-container>\n </span>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n </kendo-grid-column>\n }\n\n <kendo-grid-excel fileName=\"{{excelExportFileName}}\">\n @for (column of columns; track column.field) {\n <kendo-excelexport-column [field]=\"column.field\"\n [title]=\"getDisplayName(column) | pascalCase\">\n </kendo-excelexport-column>\n }\n </kendo-grid-excel>\n\n <kendo-grid-pdf fileName=\"{{pdfExportFileName}}\" paperSize=\"A4\" [repeatHeaders]=\"true\"\n [landscape]=\"true\">\n @for (column of columns; track column.field) {\n <kendo-grid-column [field]=\"column.field\"\n [filter]=\"getFilterType(column)\"\n [title]=\"getDisplayName(column) | pascalCase\"></kendo-grid-column>\n }\n <kendo-grid-pdf-margin top=\"1.5cm\" left=\"1cm\" right=\"1cm\" bottom=\"1.5cm\"></kendo-grid-pdf-margin>\n <ng-template kendoGridPDFTemplate let-pageNum=\"pageNum\" let-totalPages=\"totalPages\">\n <div class=\"page-template\">\n <div class=\"footer\" style=\"position: absolute; bottom: 0; width: 100%; text-align: center; font-size: 9px; color: #666;\">\n {{ getPdfPageText(pageNum, totalPages) }}\n </div>\n </div>\n </ng-template>\n </kendo-grid-pdf>\n</kendo-grid>\n\n<kendo-contextmenu\n #gridmenu\n [kendoMenuHierarchyBinding]=\"_contextMenuItems\"\n [textField]=\"['text']\"\n childrenField=\"items\"\n svgIconField=\"svgIcon\"\n separatorField=\"separator\"\n disabledField=\"disabled\"\n cssClassField=\"cssClass\"\n (popupClose)=\"onContextMenuClosed($event)\"\n (select)=\"onContextMenuSelect($event)\"\n></kendo-contextmenu>\n", styles: [":host{display:flex;flex-direction:column;min-height:0}:host>kendo-grid{flex:1;min-height:0}.mm-search-input{width:165px}.mm-list-view-toolbar-action{position:relative;display:inline-flex}:host ::ng-deep .mm-list-view-toolbar-action--disabled .k-button{opacity:.45;cursor:not-allowed}.mm-list-view-toolbar-reason{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mm-list-view-toolbar-hint{max-width:280px;padding:var(--theme-space-1, 4px) var(--theme-space-2, 8px);font-size:.75rem;line-height:1.4;color:var(--theme-text-primary, #0f1c2e);background:var(--theme-bg-overlay, #ffffff);border:1px solid var(--theme-border-default, rgba(7, 23, 43, .12));border-radius:var(--theme-radius-sm, 4px);box-shadow:var(--theme-shadow-popup, 0 8px 24px rgba(7, 23, 43, .14));pointer-events:none}:host ::ng-deep .k-grid th:has(input[type=checkbox]),:host ::ng-deep .k-grid td:has(>kendo-checkbox){text-overflow:clip}:host ::ng-deep .k-command-cell{white-space:nowrap;overflow:hidden;vertical-align:middle}:host ::ng-deep .k-command-cell .k-button{display:inline-flex;vertical-align:middle;margin:0}:host ::ng-deep .k-command-cell .k-button+.k-button{margin-left:4px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar{flex-wrap:wrap;row-gap:6px}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-search-input{width:100%;order:10}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar kendo-grid-spacer{display:none}:host(.mm-list-view-narrow) ::ng-deep .k-grid-toolbar .mm-toolbar-commands{margin-left:auto}:host ::ng-deep .k-grid td.mm-col-align-right{text-align:right;font-variant-numeric:tabular-nums}:host ::ng-deep .k-grid td.mm-col-align-center{text-align:center}:host ::ng-deep .k-grid th.mm-col-align-right .k-cell-inner>.k-link{justify-content:flex-end}:host ::ng-deep .k-grid th.mm-col-align-center .k-cell-inner>.k-link{justify-content:center}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-wrapper{gap:2px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-datepicker>.k-input-button{display:none}:host ::ng-deep .k-filter-row .mm-filter-compact .k-input-inner{padding-left:4px;padding-right:4px;font-size:12px}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator{width:26px;min-width:0}:host ::ng-deep .k-filter-row .mm-filter-compact .k-filtercell-operator .k-dropdown-operator .k-input-button{padding:2px}.mm-cell-truncate{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.status-icons-cell{display:inline-flex;align-items:center;gap:8px}.status-icons-cell .status-icon{display:inline-flex;align-items:center;justify-content:center;cursor:default}.status-icons-cell .status-icon kendo-svg-icon{width:18px;height:18px}:host ::ng-deep .filter-status-item{display:inline-flex;align-items:center;gap:10px}:host ::ng-deep .status-filter-dropdown .k-input-value-text{font-size:0}:host ::ng-deep .status-filter-dropdown .k-input-value-text .filter-status-item,:host ::ng-deep .status-filter-dropdown .k-input-value-text>span{font-size:14px}.numeric-range-filter{display:flex;align-items:center;gap:4px}.numeric-range-filter kendo-numerictextbox{flex:1;min-width:0}.numeric-range-filter .range-separator{flex-shrink:0;opacity:.6}.cron-expression-cell{display:flex;flex-direction:column;gap:2px}.cron-expression-cell .cron-expression{font-family:Roboto Mono,Consolas,monospace;font-size:.85em;background:var(--mm-cron-code-bg, #eeeeee);color:var(--mm-cron-code-text, #333333);padding:2px 6px;border-radius:3px}.cron-expression-cell .cron-description{font-size:.8em;color:var(--mm-cron-text-secondary, #666666)}.badge-cell{display:inline-block;padding:2px 10px;font-size:.75rem;font-weight:600;border-radius:12px;border:1px solid var(--mm-badge-default-border, transparent);background:var(--mm-badge-default-bg, rgba(127, 127, 127, .15));color:var(--mm-badge-default-color, inherit);white-space:nowrap;line-height:1.4}.badge-cell-default{opacity:.7;font-style:italic}.progress-bar-cell{display:inline-flex;align-items:center;gap:8px;width:100%}.progress-bar-cell .progress-bar-track{flex:1;height:8px;background:var(--mm-progress-track-bg, rgba(255, 255, 255, .1));border-radius:4px;overflow:hidden}.progress-bar-cell .progress-bar-fill{display:block;height:100%;background:var(--mm-progress-fill-color, #10b981);border-radius:4px;transition:width .3s ease}.progress-bar-cell .progress-bar-label{min-width:40px;text-align:right;font-size:.85em}:host-context(.k-pdf-export) .k-grid,:host-context(.k-pdf-export) .k-grid-header,:host-context(.k-pdf-export) .k-grid-content,:host-context(.k-pdf-export) .k-grid-header-wrap,:host-context(.k-pdf-export) .k-grid-content-locked,:host-context(.k-pdf-export) .k-grid-header-locked{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border-color:#000!important}:host-context(.k-pdf-export) .k-grid{border:1px solid #000!important;font-family:Arial,sans-serif!important;font-size:10px!important}:host-context(.k-pdf-export) .k-grid-header th,:host-context(.k-pdf-export) .k-header,:host-context(.k-pdf-export) .k-grid th{background:#f0f0f0!important;background-color:#f0f0f0!important;background-image:none!important;color:#000!important;border:1px solid #000!important;font-weight:700!important;padding:6px 8px!important;text-transform:none!important;letter-spacing:normal!important}:host-context(.k-pdf-export) .k-grid td,:host-context(.k-pdf-export) .k-grid-content td,:host-context(.k-pdf-export) .k-master-row td{background:#fff!important;background-color:#fff!important;background-image:none!important;color:#000!important;border:1px solid #000!important;padding:4px 8px!important}:host-context(.k-pdf-export) .k-grid tr,:host-context(.k-pdf-export) .k-master-row,:host-context(.k-pdf-export) .k-alt{background:#fff!important;background-color:#fff!important;background-image:none!important}:host-context(.k-pdf-export) .k-grid tr:hover,:host-context(.k-pdf-export) .k-grid tr.k-selected,:host-context(.k-pdf-export) .k-grid td:hover{background:#fff!important;background-color:#fff!important}:host-context(.k-pdf-export) .k-grid-toolbar,:host-context(.k-pdf-export) .k-pager,:host-context(.k-pdf-export) .k-grid-pager,:host-context(.k-pdf-export) .k-command-cell,:host-context(.k-pdf-export) .k-checkbox-column,:host-context(.k-pdf-export) kendo-grid-checkbox-column,:host-context(.k-pdf-export) .status-icons-cell,:host-context(.k-pdf-export) .status-icon,:host-context(.k-pdf-export) kendo-svg-icon{display:none!important}:host-context(.k-pdf-export) .badge-cell{background:transparent!important;border:none!important;color:inherit!important;padding:0!important;border-radius:0!important;text-transform:none!important;font-weight:inherit!important;font-size:inherit!important}:host(.mm-list-view-cards) ::ng-deep .k-grid-header{display:none}:host(.mm-list-view-cards) ::ng-deep .k-grid td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td{border:none;background:transparent;padding:4px 2px;white-space:normal}:host(.mm-list-view-cards) ::ng-deep .k-grid td:has(>kendo-checkbox),:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-td:has(>kendo-checkbox){padding-left:2px;padding-right:0}:host(.mm-list-view-cards) ::ng-deep .k-grid colgroup>col:first-child:not(:only-child){width:32px!important}:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row:hover,:host(.mm-list-view-cards) ::ng-deep .k-grid tr:hover{background:transparent}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected .mm-card,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected .mm-card{border-color:var(--kendo-color-primary, #ff6358)}:host(.mm-list-view-cards) ::ng-deep .k-grid tr.k-selected>td,:host(.mm-list-view-cards) ::ng-deep .k-grid .k-table-row.k-selected>.k-table-td{background:transparent}.mm-card{display:flex;flex-direction:column;gap:6px;border:1px solid var(--kendo-color-border, #dee2e6);border-radius:8px;padding:10px 12px}.mm-card-header{display:flex;align-items:center;gap:8px}.mm-card-title{flex:1;min-width:0;font-weight:600;overflow-wrap:anywhere}.mm-card-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.mm-card-fields{display:flex;flex-direction:column;gap:3px}.mm-card-field{display:flex;align-items:baseline;gap:8px;font-size:.9em}.mm-card-label{flex:0 0 38%;max-width:160px;color:var(--kendo-color-subtle, #666666);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mm-card-value{flex:1;min-width:0;overflow-wrap:anywhere}::ng-deep .k-grid-toolbar .mm-toolbar-filters{display:flex;align-items:center;gap:12px;flex-wrap:wrap}::ng-deep .k-grid-toolbar .mm-toolbar-filters-separator{align-self:center;height:22px;margin:0 4px;border-left:1px solid var(--theme-border-subtle, rgba(0, 0, 0, .16));border-top:none;width:0}::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-link,::ng-deep .k-menu-group .mm-list-view-menu-item--danger>.k-menu-link{color:var(--theme-status-error, #c0385f)}.mm-list-view-row-actions{display:inline-flex;align-items:center;gap:4px;white-space:nowrap;vertical-align:middle}.mm-list-view-row-actions .mm-list-view-row-actions__more:focus-visible{outline:none;box-shadow:var(--theme-focus-ring, 0 0 0 2px #ffffff, 0 0 0 4px #2e8473)}.boolean-icon{display:inline-flex;align-items:center}.boolean-icon.boolean-icon--true{color:var(--mm-list-boolean-true-color, var(--kendo-color-success, #2fb37a))}.boolean-icon.boolean-icon--false{color:var(--mm-list-boolean-false-color, var(--kendo-color-subtle, #8a94a0))}\n"] }]
4058
4155
  }], ctorParameters: () => [], propDecorators: { filtersTemplate: [{
4059
4156
  type: ContentChild,
4060
4157
  args: [ListViewFiltersDirective]
@@ -4143,6 +4240,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
4143
4240
  type: Input
4144
4241
  }], columns: [{
4145
4242
  type: Input
4243
+ }], toolbarHintTemplate: [{
4244
+ type: ViewChild,
4245
+ args: ['toolbarHint', { static: true }]
4146
4246
  }], resetFilters: [{
4147
4247
  type: Output
4148
4248
  }], rowLabelField: [{
@@ -7893,6 +7993,216 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
7893
7993
  `, styles: [".mm-row-actions{display:inline-flex;align-items:center;gap:var(--theme-space-1, 4px)}.mm-row-actions__item{display:inline-flex;align-items:flex-start;gap:var(--theme-space-2, 8px)}.mm-row-actions__item-text{display:inline-flex;flex-direction:column}.mm-row-actions__item-reason{font-size:.85em;color:var(--theme-text-muted, #75829a);white-space:normal;max-width:280px}.mm-row-actions__item--danger{color:var(--theme-status-error, #c0385f)}.mm-row-actions__more .k-button:focus-visible{outline:none;box-shadow:var(--theme-focus-ring, 0 0 0 2px #ffffff, 0 0 0 4px #2e8473)}\n"] }]
7894
7994
  }], propDecorators: { actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: true }] }], rowLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowLabel", required: true }] }], maxInline: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxInline", required: false }] }], messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: false }] }], triggered: [{ type: i0.Output, args: ["triggered"] }] } });
7895
7995
 
7996
+ /** Host lookup for library texts. Optional — without it the English defaults are used. */
7997
+ const MM_TRANSLATE = new InjectionToken('MM_TRANSLATE');
7998
+ /** The active UI language. Optional — without it the language is `'en'`. */
7999
+ const MM_LANGUAGE = new InjectionToken('MM_LANGUAGE');
8000
+ /** English default tables of the libraries (multi provider, see {@link provideMmTranslateDefaults}). */
8001
+ const MM_TRANSLATE_DEFAULTS = new InjectionToken('MM_TRANSLATE_DEFAULTS');
8002
+ const ENGLISH = signal('en').asReadonly();
8003
+ /**
8004
+ * Registers a library's English defaults. Use it in the library's `provideX()` (environment
8005
+ * providers) or in a component's `providers` (element injector).
8006
+ */
8007
+ function provideMmTranslateDefaults(table) {
8008
+ return { provide: MM_TRANSLATE_DEFAULTS, useValue: table, multi: true };
8009
+ }
8010
+ /** The value at `key` in a table: the flat entry first, then the dotted path into nested objects. */
8011
+ function lookupMmTable(table, key) {
8012
+ if (!table) {
8013
+ return undefined;
8014
+ }
8015
+ const flat = table[key];
8016
+ if (typeof flat === 'string') {
8017
+ return flat;
8018
+ }
8019
+ let node = table;
8020
+ for (const part of key.split('.')) {
8021
+ if (!node || typeof node === 'string') {
8022
+ return undefined;
8023
+ }
8024
+ node = node[part];
8025
+ }
8026
+ return typeof node === 'string' ? node : undefined;
8027
+ }
8028
+ /** Replaces `{{ name }}` placeholders (ngx-translate syntax) with `params`; unknown names stay. */
8029
+ function interpolateMm(text, params) {
8030
+ if (!params) {
8031
+ return text;
8032
+ }
8033
+ return text.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (match, name) => {
8034
+ const value = params[name];
8035
+ return value === undefined || value === null ? match : String(value);
8036
+ });
8037
+ }
8038
+ /**
8039
+ * Resolution order of a library text:
8040
+ * 1. the host ({@link MM_TRANSLATE}), unless it returns `undefined`, an empty string or the key;
8041
+ * 2. the library defaults (`defaults` argument, then every {@link MM_TRANSLATE_DEFAULTS} table);
8042
+ * 3. the explicit `fallback`;
8043
+ * 4. the key itself (a visible gap, never an exception).
8044
+ * A host lookup that throws counts as "no translation" (fail-safe, concept §6).
8045
+ */
8046
+ function resolveMmTranslation(host, defaults, key, params, fallback) {
8047
+ if (host) {
8048
+ try {
8049
+ const value = host(key, params ?? undefined);
8050
+ if (value && value !== key) {
8051
+ return value;
8052
+ }
8053
+ }
8054
+ catch {
8055
+ // Fail-safe: a broken host lookup must not break the library UI.
8056
+ }
8057
+ }
8058
+ for (const table of defaults) {
8059
+ const value = lookupMmTable(table, key);
8060
+ if (value !== undefined) {
8061
+ return interpolateMm(value, params);
8062
+ }
8063
+ }
8064
+ return fallback !== undefined ? interpolateMm(fallback, params) : key;
8065
+ }
8066
+ /** The active language signal: {@link MM_LANGUAGE}, or a constant `'en'`. Injection context only. */
8067
+ function injectMmLanguage() {
8068
+ return inject(MM_LANGUAGE, { optional: true }) ?? ENGLISH;
8069
+ }
8070
+ /**
8071
+ * A translate function for TS code (injection context only). It reads {@link MM_LANGUAGE}, so a
8072
+ * `computed(() => t('MM.X.y'))` re-runs on a language switch.
8073
+ *
8074
+ * @param defaults the library's English defaults (its bundled `en.json`), checked before the
8075
+ * {@link MM_TRANSLATE_DEFAULTS} tables.
8076
+ */
8077
+ function injectMmT(defaults) {
8078
+ const host = inject(MM_TRANSLATE, { optional: true });
8079
+ const language = injectMmLanguage();
8080
+ const tables = [...(defaults ? [defaults] : []), ...(inject(MM_TRANSLATE_DEFAULTS, { optional: true }) ?? [])];
8081
+ return (key, params, fallback) => {
8082
+ language();
8083
+ return resolveMmTranslation(host, tables, key, params, fallback);
8084
+ };
8085
+ }
8086
+
8087
+ /**
8088
+ * `{{ 'MM.SHARED_UI.LIST_VIEW.refresh' | mmT }}` — a library text in the host's language
8089
+ * (see `mm-translate.ts`). Optional arguments: interpolation params and an English fallback:
8090
+ * `{{ 'MM.X.count' | mmT: { count: n } : '{{ count }} items' }}`.
8091
+ *
8092
+ * Impure on purpose, but cached: the pipe reads the {@link MM_LANGUAGE} signal, so the template
8093
+ * (also an OnPush one) re-renders on a language switch, and it recomputes only when the language,
8094
+ * key, params or fallback change. Without host providers it returns the English default.
8095
+ */
8096
+ class MmTranslatePipe {
8097
+ host = inject(MM_TRANSLATE, { optional: true });
8098
+ defaults = inject(MM_TRANSLATE_DEFAULTS, { optional: true }) ?? [];
8099
+ language = injectMmLanguage();
8100
+ lastLanguage = null;
8101
+ lastKey = null;
8102
+ lastParams = undefined;
8103
+ lastParamsJson = '';
8104
+ lastFallback = undefined;
8105
+ lastValue = '';
8106
+ transform(key, params, fallback) {
8107
+ if (!key) {
8108
+ return '';
8109
+ }
8110
+ const language = this.language();
8111
+ const paramsJson = params === this.lastParams ? this.lastParamsJson : safeJson(params);
8112
+ if (language === this.lastLanguage && key === this.lastKey && paramsJson === this.lastParamsJson
8113
+ && fallback === this.lastFallback) {
8114
+ this.lastParams = params;
8115
+ return this.lastValue;
8116
+ }
8117
+ this.lastLanguage = language;
8118
+ this.lastKey = key;
8119
+ this.lastParams = params;
8120
+ this.lastParamsJson = paramsJson;
8121
+ this.lastFallback = fallback;
8122
+ this.lastValue = resolveMmTranslation(this.host, this.defaults, key, params, fallback);
8123
+ return this.lastValue;
8124
+ }
8125
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: MmTranslatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
8126
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.5", ngImport: i0, type: MmTranslatePipe, isStandalone: true, name: "mmT", pure: false });
8127
+ }
8128
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: MmTranslatePipe, decorators: [{
8129
+ type: Pipe,
8130
+ args: [{ name: 'mmT', pure: false }]
8131
+ }] });
8132
+ let unserializable = 0;
8133
+ /** Params as a comparable string; params that cannot be serialised never hit the cache. */
8134
+ function safeJson(value) {
8135
+ if (value === null || value === undefined) {
8136
+ return '';
8137
+ }
8138
+ try {
8139
+ return JSON.stringify(value);
8140
+ }
8141
+ catch {
8142
+ return `\u0000${++unserializable}`;
8143
+ }
8144
+ }
8145
+
8146
+ /**
8147
+ * Bridges between a host's translation store and the libraries' `*_MESSAGES` objects
8148
+ * (`SHELL_MESSAGES`, `COMMAND_PALETTE_MESSAGES`, `ASSISTANT_MESSAGES`, `MM_ROW_ACTIONS_MESSAGES`,
8149
+ * `ListViewMessages`, …). Moved here from the meshmakers-app (`shell-messages.i18n.ts`) so every
8150
+ * app wires the tokens the same way (Studio i18n concept §6, AB#6163).
8151
+ *
8152
+ * A key map assigns a translation key to each message member:
8153
+ * ```ts
8154
+ * const KEYS = { search: 'SHELL.MESSAGES.SEARCH', signOut: 'SHELL.MESSAGES.SIGN_OUT' } as const
8155
+ * satisfies Partial<Record<keyof ShellMessages, string>>;
8156
+ * { provide: SHELL_MESSAGES, useFactory: () => {
8157
+ * const translate = inject(TranslateService);
8158
+ * return liveMessages(KEYS, (k) => translate.instant(k) as string);
8159
+ * } }
8160
+ * ```
8161
+ * A member whose lookup returns nothing, an empty string or the key itself (ngx-translate's answer
8162
+ * for a missing key) stays `undefined`, so the library's English default applies instead of a raw
8163
+ * key.
8164
+ */
8165
+ function translated(t, key) {
8166
+ let value;
8167
+ try {
8168
+ value = t(key);
8169
+ }
8170
+ catch {
8171
+ return undefined;
8172
+ }
8173
+ return value && value !== key ? value : undefined;
8174
+ }
8175
+ /**
8176
+ * A snapshot of the messages in the active language. Re-run it on every language change and feed
8177
+ * the result to a component's `messages` input or a signal-backed messages factory.
8178
+ */
8179
+ function messagesFromTranslate(keys, t) {
8180
+ const messages = {};
8181
+ for (const [member, key] of Object.entries(keys)) {
8182
+ const value = translated(t, key);
8183
+ if (value !== undefined) {
8184
+ messages[member] = value;
8185
+ }
8186
+ }
8187
+ return messages;
8188
+ }
8189
+ /**
8190
+ * A messages object whose members are getters over the CURRENT translation — for app-wide tokens
8191
+ * that are injected once: a service that reads a member while deriving a text picks up a language
8192
+ * switch on its next read, without re-providing the token. Members without a translation are
8193
+ * `undefined` (the library keeps its English default).
8194
+ */
8195
+ function liveMessages(keys, t) {
8196
+ const messages = {};
8197
+ for (const [member, key] of Object.entries(keys)) {
8198
+ Object.defineProperty(messages, member, {
8199
+ enumerable: true,
8200
+ get: () => translated(t, key),
8201
+ });
8202
+ }
8203
+ return messages;
8204
+ }
8205
+
7896
8206
  /*
7897
8207
  * Public API Surface of shared-ui
7898
8208
  */
@@ -7916,5 +8226,5 @@ function provideMmSharedUi() {
7916
8226
  * Generated bundle index. Do not edit.
7917
8227
  */
7918
8228
 
7919
- export { ActionButtonComponent, BaseFormComponent, BaseTreeDetailComponent, ButtonTypes, BytesToSizePipe, CRON_PRESETS, ConfirmationService, ConfirmationWindowComponent, ConfirmationWindowResult, CopyableTextComponent, CronBuilderComponent, CronHumanizerService, CronParserService, DANGER_CONFIRM_ENVIRONMENT, DANGER_CONFIRM_FALLBACK_WORD, DANGER_CONFIRM_SAFE_FOCUS, DEFAULT_CONFIRMATION_WINDOW_MESSAGES, DEFAULT_CRON_BUILDER_CONFIG, DEFAULT_DANGER_CONFIRMATION_MESSAGES, DEFAULT_EMPTY_STATE_RETRY_LABEL, DEFAULT_ENTITY_SELECT_DIALOG_MESSAGES, DEFAULT_ENTITY_SELECT_INPUT_MESSAGES, DEFAULT_INPUT_DIALOG_MESSAGES, DEFAULT_LIST_VIEW_MESSAGES, DEFAULT_MESSAGE_DETAILS_DIALOG_MESSAGES, DEFAULT_NOTIFICATION_DISPLAY_MESSAGES, DEFAULT_PROGRESS_WINDOW_MESSAGES, DEFAULT_RESPONSIVE_COLSPAN, DEFAULT_ROW_ACTIONS_MESSAGES, DEFAULT_SAVE_AS_DIALOG_MESSAGES, DEFAULT_TIME_RANGE_LABELS, DangerConfirmationResult, DangerConfirmationWindowComponent, DataSourceBase, DataSourceTyped, DialogType, EmptyStateComponent, EntitySelectDialogComponent, EntitySelectDialogService, EntitySelectInputComponent, FetchResultBase, FetchResultTyped, FileUploadResult, FileUploadService, FormTitleExtraDirective, HAS_UNSAVED_CHANGES, HOUR_INTERVALS, HierarchyDataSource, HierarchyDataSourceBase, ImportStrategyDialogComponent, ImportStrategyDialogResult, ImportStrategyDialogService, ImportStrategyDto, InputDialogComponent, InputService, ListStateService, ListViewComponent, ListViewFiltersDirective, MINUTE_INTERVALS, MM_ACTION_ICONS, MM_ROW_ACTIONS_MAX_INLINE, MM_ROW_ACTIONS_MESSAGES, MessageDetailsDialogComponent, MessageDetailsDialogService, MessageListenerService, MmListViewDataBindingDirective, NOTIFICATION_DISPLAY_OPTIONS, NotificationDisplayService, OCTOBOT_ASSET_BASE_URL, OCTOBOT_FORCE_STILL, OCTOBOT_LOOP_MS, OCTOBOT_SIZE_PX, OctoBotComponent, PascalCasePipe, ProgressValue, ProgressWindowComponent, ProgressWindowService, RELATIVE_WEEKS, RowActionsComponent, SECOND_INTERVALS, SaveAsDialogComponent, SaveAsDialogService, TimeRangePickerComponent, TimeRangeUtils, TreeComponent, UnsavedChangesDirective, UnsavedChangesGuard, UploadFileDialogComponent, WEEKDAYS, WEEKDAY_ABBREVIATIONS, WindowStateService, actionAccessibleName, actionTooltip, applyDangerConfirmEnvironment, dangerConfirmNameMatches, dangerConfirmTypingToken, generateDayOfMonthOptions, generateHourOptions, generateMinuteOptions, isActionDisabled, isActionVisible, provideMmSharedUi, resolveListRowAction, resolveListRowLabel, resolveListViewMessages, resolveRowActionsMessages, sameAction, splitRowActions };
8229
+ export { ActionButtonComponent, BaseFormComponent, BaseTreeDetailComponent, ButtonTypes, BytesToSizePipe, CRON_PRESETS, ConfirmationService, ConfirmationWindowComponent, ConfirmationWindowResult, CopyableTextComponent, CronBuilderComponent, CronHumanizerService, CronParserService, DANGER_CONFIRM_ENVIRONMENT, DANGER_CONFIRM_FALLBACK_WORD, DANGER_CONFIRM_SAFE_FOCUS, DEFAULT_CONFIRMATION_WINDOW_MESSAGES, DEFAULT_CRON_BUILDER_CONFIG, DEFAULT_DANGER_CONFIRMATION_MESSAGES, DEFAULT_EMPTY_STATE_RETRY_LABEL, DEFAULT_ENTITY_SELECT_DIALOG_MESSAGES, DEFAULT_ENTITY_SELECT_INPUT_MESSAGES, DEFAULT_INPUT_DIALOG_MESSAGES, DEFAULT_LIST_VIEW_MESSAGES, DEFAULT_MESSAGE_DETAILS_DIALOG_MESSAGES, DEFAULT_NOTIFICATION_DISPLAY_MESSAGES, DEFAULT_PROGRESS_WINDOW_MESSAGES, DEFAULT_RESPONSIVE_COLSPAN, DEFAULT_ROW_ACTIONS_MESSAGES, DEFAULT_SAVE_AS_DIALOG_MESSAGES, DEFAULT_TIME_RANGE_LABELS, DangerConfirmationResult, DangerConfirmationWindowComponent, DataSourceBase, DataSourceTyped, DialogType, EmptyStateComponent, EntitySelectDialogComponent, EntitySelectDialogService, EntitySelectInputComponent, FetchResultBase, FetchResultTyped, FileUploadResult, FileUploadService, FormTitleExtraDirective, HAS_UNSAVED_CHANGES, HOUR_INTERVALS, HierarchyDataSource, HierarchyDataSourceBase, ImportStrategyDialogComponent, ImportStrategyDialogResult, ImportStrategyDialogService, ImportStrategyDto, InputDialogComponent, InputService, ListStateService, ListViewComponent, ListViewFiltersDirective, MINUTE_INTERVALS, MM_ACTION_ICONS, MM_LANGUAGE, MM_ROW_ACTIONS_MAX_INLINE, MM_ROW_ACTIONS_MESSAGES, MM_TRANSLATE, MM_TRANSLATE_DEFAULTS, MessageDetailsDialogComponent, MessageDetailsDialogService, MessageListenerService, MmListViewDataBindingDirective, MmTranslatePipe, NOTIFICATION_DISPLAY_OPTIONS, NotificationDisplayService, OCTOBOT_ASSET_BASE_URL, OCTOBOT_FORCE_STILL, OCTOBOT_LOOP_MS, OCTOBOT_SIZE_PX, OctoBotComponent, PascalCasePipe, ProgressValue, ProgressWindowComponent, ProgressWindowService, RELATIVE_WEEKS, RowActionsComponent, SECOND_INTERVALS, SaveAsDialogComponent, SaveAsDialogService, TimeRangePickerComponent, TimeRangeUtils, TreeComponent, UnsavedChangesDirective, UnsavedChangesGuard, UploadFileDialogComponent, WEEKDAYS, WEEKDAY_ABBREVIATIONS, WindowStateService, actionAccessibleName, actionTooltip, applyDangerConfirmEnvironment, dangerConfirmNameMatches, dangerConfirmTypingToken, generateDayOfMonthOptions, generateHourOptions, generateMinuteOptions, injectMmLanguage, injectMmT, interpolateMm, isActionDisabled, isActionVisible, liveMessages, lookupMmTable, messagesFromTranslate, provideMmSharedUi, provideMmTranslateDefaults, resolveListRowAction, resolveListRowLabel, resolveListViewMessages, resolveMmTranslation, resolveRowActionsMessages, sameAction, splitRowActions };
7920
8230
  //# sourceMappingURL=meshmakers-shared-ui.mjs.map