@yuuvis/client-framework 3.8.2 → 3.9.0

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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, viewChild, ChangeDetectionStrategy, Component, forwardRef, Injectable, inject, ViewContainerRef, effect, Directive, DestroyRef, ElementRef, ChangeDetectorRef, contentChild, viewChildren, computed, signal, linkedSignal, untracked } from '@angular/core';
2
+ import { input, output, viewChild, ChangeDetectionStrategy, Component, forwardRef, inject, ViewContainerRef, effect, Directive, DestroyRef, ElementRef, ChangeDetectorRef, contentChild, viewChildren, computed, signal, linkedSignal, untracked, Injectable } from '@angular/core';
3
3
  import * as i3 from '@angular/material/icon';
4
4
  import { MatIcon, MatIconModule } from '@angular/material/icon';
5
5
  import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
@@ -20,6 +20,7 @@ import { YuvQueryListModule } from '@yuuvis/client-framework/query-list';
20
20
  import { RendererDirective } from '@yuuvis/client-framework/renderer';
21
21
  import { YmtMatIconRegistryService, YmtIconButtonDirective, YmtButtonDirective } from '@yuuvis/material';
22
22
  import { switchMap, forkJoin, take } from 'rxjs';
23
+ import { ShellService } from '@yuuvis/client-shell-core';
23
24
  import { NgClass } from '@angular/common';
24
25
  import * as i1$1 from '@angular/material/button';
25
26
  import { MatButtonModule } from '@angular/material/button';
@@ -57,61 +58,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
57
58
  args: [{ selector: 'yuv-tile-actions-menu', imports: [MatMenu, MatIcon, MatMenuItem, MatMenuTrigger, MatTooltip, forwardRef(() => TileActionsMenuComponent)], providers: [{ provide: MAT_TOOLTIP_DEFAULT_OPTIONS, useValue: { position: 'right' } }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<mat-menu #menuRef>\n <!-- @if (loading() && !actions().length) {\n <div class=\"loading-indicator\">\n <mat-spinner diameter=\"24\"></mat-spinner>\n </div>\n } -->\n @for (action of actions(); track action.id) {\n @if (action.children?.length) {\n <span [matTooltip]=\"action.description\" [matTooltipDisabled]=\"!action.description\">\n <button\n mat-menu-item\n [disabled]=\"!!action.disabled\"\n [attr.disabled]=\"!!action.disabled\"\n [matMenuTriggerFor]=\"subMenu.matMenu()\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span>{{ action.label }}</span>\n </button>\n <yuv-tile-actions-menu #subMenu [actions]=\"action.children!\" (itemSelect)=\"itemSelect.emit($event)\" />\n </span>\n } @else {\n <span [matTooltip]=\"action.description\" [matTooltipDisabled]=\"!action.description\">\n <button\n mat-menu-item\n [disabled]=\"!!action.disabled\"\n [attr.disabled]=\"!!action.disabled\"\n (click)=\"itemClicked($event, action)\"\n >\n @if (action.icon) {\n <mat-icon>{{ action.icon }}</mat-icon>\n }\n <span>{{ action.label }}</span>\n </button>\n </span>\n }\n }\n</mat-menu>\n", styles: [":host{display:block}.loading-indicator{display:flex;justify-content:center;padding:8px 16px}button[mat-menu-item][disabled]{opacity:var(--ymt-disabled-opacity, .38);cursor:default;pointer-events:none}\n"] }]
58
59
  }], propDecorators: { actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: true }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], itemSelect: [{ type: i0.Output, args: ["itemSelect"] }], matMenu: [{ type: i0.ViewChild, args: ['menuRef', { isSignal: true }] }] } });
59
60
 
60
- class TileExtensionService {
61
- constructor() {
62
- this._extensions = {};
63
- // this.registerTileExtension({
64
- // // typeId: 'appSystemmail:email',
65
- // typeId: 'system:document',
66
- // cmp: EmailTileExtensionComponent
67
- // });
68
- }
69
- registerTileExtension(ext) {
70
- this._extensions[ext.typeId] = ext;
71
- }
72
- hasTileExtension(objectTypeId) {
73
- return !!this._extensions[objectTypeId];
74
- }
75
- /**
76
- * Get tile extensions for a certain type
77
- * @param typeId ID of the type to fetch extesion for (objectTypeID or secondaryObjectTypeID)
78
- * @returns
79
- */
80
- getTileExtension(typeId) {
81
- return this._extensions[typeId];
82
- }
83
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileExtensionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
84
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileExtensionService, providedIn: 'root' }); }
85
- }
86
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileExtensionService, decorators: [{
87
- type: Injectable,
88
- args: [{
89
- providedIn: 'root'
90
- }]
91
- }], ctorParameters: () => [] });
92
-
61
+ /**
62
+ * Structural directive that renders the {@link TileExtension} registered for a
63
+ * tile's object type or applied flavor, if any. Used internally by
64
+ * `yuv-tile-list` via `*yuvTileExtension` on each tile, passing the tile's
65
+ * `objectTypeId`, applied `flavorTypeId` and `instanceData`.
66
+ *
67
+ * On each input change it resolves an extension from {@link ShellService}: the
68
+ * applied flavor's SOT (`flavorTypeId`) takes precedence over the object type
69
+ * (`typeId`). When a match exists it renders the registered component and feeds
70
+ * it the tile's `data`; the component is only recreated when the resolved
71
+ * extension changes. When nothing matches, any previously rendered extension is
72
+ * cleared.
73
+ *
74
+ * Register extensions through {@link ShellService.registerTileExtension}.
75
+ */
93
76
  class TileExtensionDirective {
94
77
  constructor() {
95
- this.#tileExtensioService = inject(TileExtensionService);
78
+ this.#shell = inject(ShellService);
96
79
  this.#containerRef = inject(ViewContainerRef);
80
+ /** The tile's object type id, applied flavor SOT and instance data, see {@link TileExtensionDirectiveInput}. */
97
81
  this.yuvTileExtension = input.required(...(ngDevMode ? [{ debugName: "yuvTileExtension" }] : /* istanbul ignore next */ []));
98
82
  this.#yuvTileExtensionEffect = effect(() => {
99
83
  const tile = this.yuvTileExtension();
100
- if (tile.typeId) {
101
- const ext = this.#tileExtensioService.getTileExtension(tile.typeId);
102
- if (ext)
103
- this._render(ext, tile.data);
104
- }
84
+ const ext = (tile.flavorTypeId ? this.#shell.getTileExtension(tile.flavorTypeId) : undefined) ??
85
+ (tile.typeId ? this.#shell.getTileExtension(tile.typeId) : undefined);
86
+ if (ext)
87
+ this._render(ext, tile.data);
88
+ else
89
+ this._clear();
105
90
  }, ...(ngDevMode ? [{ debugName: "#yuvTileExtensionEffect" }] : /* istanbul ignore next */ []));
106
91
  }
107
- #tileExtensioService;
92
+ #shell;
108
93
  #containerRef;
94
+ #renderedCmp;
109
95
  #yuvTileExtensionEffect;
110
96
  _render(ext, data) {
111
- if (this.component)
97
+ // recreate only when the resolved extension component changes; otherwise just
98
+ // update its data input so toggling flavors doesn't needlessly rebuild it.
99
+ if (this.#renderedCmp !== ext.cmp) {
112
100
  this.#containerRef.clear();
113
- this.component = this.#containerRef.createComponent(ext.cmp);
114
- this.component.setInput('data', data);
101
+ this.component = this.#containerRef.createComponent(ext.cmp);
102
+ this.#renderedCmp = ext.cmp;
103
+ }
104
+ this.component?.setInput('data', data);
105
+ }
106
+ _clear() {
107
+ if (this.#renderedCmp) {
108
+ this.#containerRef.clear();
109
+ this.component = undefined;
110
+ this.#renderedCmp = undefined;
111
+ }
115
112
  }
116
113
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileExtensionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
117
114
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.12", type: TileExtensionDirective, isStandalone: true, selector: "[yuvTileExtension]", inputs: { yuvTileExtension: { classPropertyName: "yuvTileExtension", publicName: "yuvTileExtension", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); }
@@ -937,6 +934,9 @@ class TileListComponent {
937
934
  this.appliedFlavor = this.appliedFlavor?.id === flavor.id ? undefined : flavor;
938
935
  if (this.#rawResultItems)
939
936
  this.items.set(this.#mapToTileData(this.#rawResultItems.map((i) => new DmsObject(i))));
937
+ // Re-run the inner query-list transformer so the *rendered* tiles pick up the
938
+ // flavor-driven config and `flavorTypeId` (the transformer reads `appliedFlavor`).
939
+ this.list().runTransformerAgain();
940
940
  }
941
941
  /**
942
942
  * Applies per-index optimistic overrides to the displayed tile data.
@@ -1246,6 +1246,8 @@ class TileListComponent {
1246
1246
  badges: objectConfig.badges,
1247
1247
  instanceData: dmsObject.data,
1248
1248
  dmsObject,
1249
+ // expose the applied flavor's SOT so a flavor-specific tile extension can be resolved
1250
+ flavorTypeId: this.appliedFlavor && sots.includes(this.appliedFlavor.sot) ? this.appliedFlavor.sot : undefined,
1249
1251
  title: objectConfig.title
1250
1252
  ? this.#getResolvedObjectConfigItem(objectConfig.title.propertyName, dmsObject.data)
1251
1253
  : { propertyName: '', value: '' }
@@ -1300,7 +1302,7 @@ class TileListComponent {
1300
1302
  #closeMenuEffect;
1301
1303
  #outsideClickCloseEffect;
1302
1304
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1303
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: TileListComponent, isStandalone: true, selector: "yuv-tile-list", inputs: { bucket: { classPropertyName: "bucket", publicName: "bucket", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, dense: { classPropertyName: "dense", publicName: "dense", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, flavor: { classPropertyName: "flavor", publicName: "flavor", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, preselect: { classPropertyName: "preselect", publicName: "preselect", isSignal: true, isRequired: false, transformFunction: null }, highlights: { classPropertyName: "highlights", publicName: "highlights", isSignal: true, isRequired: false, transformFunction: null }, preventChangeUntil: { classPropertyName: "preventChangeUntil", publicName: "preventChangeUntil", isSignal: true, isRequired: false, transformFunction: null }, autoSelect: { classPropertyName: "autoSelect", publicName: "autoSelect", isSignal: true, isRequired: false, transformFunction: null }, disableCustomContextMenu: { classPropertyName: "disableCustomContextMenu", publicName: "disableCustomContextMenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelect: "itemSelect", tileCopy: "tileCopy", tileCut: "tileCut", busy: "busy", queryResult: "queryResult", selectionChange: "selectionChange", itemDblClick: "itemDblClick", ctxMenu: "ctxMenu" }, host: { listeners: { "keydown.control.c": "onCopy($event)", "keydown.control.x": "onCut($event)" }, properties: { "class.dense": "dense()" } }, providers: [], queries: [{ propertyName: "menuComponent", first: true, predicate: TileActionsMenuComponent, descendants: true, isSignal: true }, { propertyName: "emptyContent", first: true, predicate: ["empty"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "menuTriggers", predicate: MatMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: "<yuv-query-list\n #list\n [query]=\"query()\"\n [transformer]=\"transformer\"\n idProperty=\"id\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [autoSelect]=\"autoSelect()\"\n [pageSize]=\"pageSize()\"\n [multiselect]=\"multiselect()\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (itemSelect)=\"onListItemsSelect($event)\"\n (queryResult)=\"onQueryResult($event)\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n>\n <ng-template #yuvQueryListItem let-item let-index=\"index\">\n <yuv-list-tile [class.dense]=\"dense()\" [style]=\"highlightStyles()[item.id]\" (contextmenu)=\"contextMenuHandler($event, index)\">\n <ng-template #iconSlot><ng-container *yuvRenderer=\"item.icon\" /></ng-template>\n <ng-template #titleSlot><ng-container *yuvRenderer=\"item.title\" /></ng-template>\n <ng-template #descriptionSlot><ng-container *yuvRenderer=\"item.description\" /></ng-template>\n <ng-template #metaSlot><ng-container *yuvRenderer=\"item.meta\" /></ng-template>\n <ng-template #asideSlot><ng-container *yuvRenderer=\"item.aside\" /></ng-template>\n <ng-template #actionsSlot>\n @for (a of item.actions; track a.id) {\n <button\n ymt-icon-button\n [matTooltip]=\"a.label\"\n icon-button-size=\"small\"\n [disabled]=\"a.disabled\"\n (click)=\"executeAction(item, a, $event)\"\n >\n <mat-icon inert=\"true\">{{ a.icon }}</mat-icon>\n </button>\n }\n\n @if (menu()) {\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n (click)=\"menuItemClicked(item, $event)\"\n [matTooltip]=\"'yuv.tile-list.item.actions-menu.button.tooltip' | translate\"\n [matMenuTriggerFor]=\"menu()\"\n >\n <mat-icon inert=\"true\">more_vert</mat-icon>\n </button>\n }\n <ng-content select=\"yuv-tile-actions-menu, [yuv-tile-actions-menu]\" />\n </ng-template>\n <ng-template #extensionSlot>\n <ng-container *yuvTileExtension=\"{ typeId: item.objectTypeId, data: item.instanceData }\" />\n </ng-template>\n <ng-template #badgesSlot>\n <yuv-tile-badges [dmsObject]=\"item.dmsObject\" [badgeIds]=\"badgeIdsFor(item)\" [bucket]=\"bucket()\" />\n </ng-template>\n </yuv-list-tile>\n </ng-template>\n\n <ng-template #yuvQueryListEmpty>\n <div class=\"empty-list\">\n @let searchExe = searchExecuted();\n @if (searchExe && emptyContent()) {\n <ng-content />\n }\n </div>\n </ng-template>\n <div class=\"offset\" (click)=\"clearSelection()\"></div>\n</yuv-query-list>\n", styles: [":host{--paging-background: var(--ymt-surface);display:flex;flex-direction:column}:host yuv-query-list{flex:1;overflow-y:auto;display:flex;flex-flow:column;height:100%}:host yuv-query-list .offset{flex:1 1 auto}:host .empty-list{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: YuvListModule }, { kind: "component", type: i1.ListTileComponent, selector: "yuv-list-tile" }, { kind: "ngmodule", type: YuvQueryListModule }, { kind: "component", type: i2.QueryListComponent, selector: "yuv-query-list", inputs: ["query", "idProperty", "transformer", "preventChangeUntil", "autoSelect", "pageSize", "enableDragSelect", "multiselect", "selfHandleSelection", "includePermissions"], outputs: ["itemSelect", "dragSelectChange", "itemDoubleClick", "queryResult"] }, { kind: "directive", type: RendererDirective, selector: "[yuvRenderer]", inputs: ["yuvRenderer"] }, { kind: "directive", type: TileExtensionDirective, selector: "[yuvTileExtension]", inputs: ["yuvTileExtension"] }, { kind: "component", type: TileBadgesComponent, selector: "yuv-tile-badges", inputs: ["dmsObject", "badgeIds", "bucket"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1305
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: TileListComponent, isStandalone: true, selector: "yuv-tile-list", inputs: { bucket: { classPropertyName: "bucket", publicName: "bucket", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, dense: { classPropertyName: "dense", publicName: "dense", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, flavor: { classPropertyName: "flavor", publicName: "flavor", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, preselect: { classPropertyName: "preselect", publicName: "preselect", isSignal: true, isRequired: false, transformFunction: null }, highlights: { classPropertyName: "highlights", publicName: "highlights", isSignal: true, isRequired: false, transformFunction: null }, preventChangeUntil: { classPropertyName: "preventChangeUntil", publicName: "preventChangeUntil", isSignal: true, isRequired: false, transformFunction: null }, autoSelect: { classPropertyName: "autoSelect", publicName: "autoSelect", isSignal: true, isRequired: false, transformFunction: null }, disableCustomContextMenu: { classPropertyName: "disableCustomContextMenu", publicName: "disableCustomContextMenu", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemSelect: "itemSelect", tileCopy: "tileCopy", tileCut: "tileCut", busy: "busy", queryResult: "queryResult", selectionChange: "selectionChange", itemDblClick: "itemDblClick", ctxMenu: "ctxMenu" }, host: { listeners: { "keydown.control.c": "onCopy($event)", "keydown.control.x": "onCut($event)" }, properties: { "class.dense": "dense()" } }, providers: [], queries: [{ propertyName: "menuComponent", first: true, predicate: TileActionsMenuComponent, descendants: true, isSignal: true }, { propertyName: "emptyContent", first: true, predicate: ["empty"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "list", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "menuTriggers", predicate: MatMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: "<yuv-query-list\n #list\n [query]=\"query()\"\n [transformer]=\"transformer\"\n idProperty=\"id\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [autoSelect]=\"autoSelect()\"\n [pageSize]=\"pageSize()\"\n [multiselect]=\"multiselect()\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (itemSelect)=\"onListItemsSelect($event)\"\n (queryResult)=\"onQueryResult($event)\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n>\n <ng-template #yuvQueryListItem let-item let-index=\"index\">\n <yuv-list-tile [class.dense]=\"dense()\" [style]=\"highlightStyles()[item.id]\" (contextmenu)=\"contextMenuHandler($event, index)\">\n <ng-template #iconSlot><ng-container *yuvRenderer=\"item.icon\" /></ng-template>\n <ng-template #titleSlot><ng-container *yuvRenderer=\"item.title\" /></ng-template>\n <ng-template #descriptionSlot><ng-container *yuvRenderer=\"item.description\" /></ng-template>\n <ng-template #metaSlot><ng-container *yuvRenderer=\"item.meta\" /></ng-template>\n <ng-template #asideSlot><ng-container *yuvRenderer=\"item.aside\" /></ng-template>\n <ng-template #actionsSlot>\n @for (a of item.actions; track a.id) {\n <button\n ymt-icon-button\n [matTooltip]=\"a.label\"\n icon-button-size=\"small\"\n [disabled]=\"a.disabled\"\n (click)=\"executeAction(item, a, $event)\"\n >\n <mat-icon inert=\"true\">{{ a.icon }}</mat-icon>\n </button>\n }\n\n @if (menu()) {\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n (click)=\"menuItemClicked(item, $event)\"\n [matTooltip]=\"'yuv.tile-list.item.actions-menu.button.tooltip' | translate\"\n [matMenuTriggerFor]=\"menu()\"\n >\n <mat-icon inert=\"true\">more_vert</mat-icon>\n </button>\n }\n <ng-content select=\"yuv-tile-actions-menu, [yuv-tile-actions-menu]\" />\n </ng-template>\n <ng-template #extensionSlot>\n <ng-container *yuvTileExtension=\"{ typeId: item.objectTypeId, flavorTypeId: item.flavorTypeId, data: item.instanceData }\" />\n </ng-template>\n <ng-template #badgesSlot>\n <yuv-tile-badges [dmsObject]=\"item.dmsObject\" [badgeIds]=\"badgeIdsFor(item)\" [bucket]=\"bucket()\" />\n </ng-template>\n </yuv-list-tile>\n </ng-template>\n\n <ng-template #yuvQueryListEmpty>\n <div class=\"empty-list\">\n @let searchExe = searchExecuted();\n @if (searchExe && emptyContent()) {\n <ng-content />\n }\n </div>\n </ng-template>\n <div class=\"offset\" (click)=\"clearSelection()\"></div>\n</yuv-query-list>\n", styles: [":host{--paging-background: var(--ymt-surface);display:flex;flex-direction:column}:host yuv-query-list{flex:1;overflow-y:auto;display:flex;flex-flow:column;height:100%}:host yuv-query-list .offset{flex:1 1 auto}:host .empty-list{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: YuvListModule }, { kind: "component", type: i1.ListTileComponent, selector: "yuv-list-tile" }, { kind: "ngmodule", type: YuvQueryListModule }, { kind: "component", type: i2.QueryListComponent, selector: "yuv-query-list", inputs: ["query", "idProperty", "transformer", "preventChangeUntil", "autoSelect", "pageSize", "enableDragSelect", "multiselect", "selfHandleSelection", "includePermissions"], outputs: ["itemSelect", "dragSelectChange", "itemDoubleClick", "queryResult"] }, { kind: "directive", type: RendererDirective, selector: "[yuvRenderer]", inputs: ["yuvRenderer"] }, { kind: "directive", type: TileExtensionDirective, selector: "[yuvTileExtension]", inputs: ["yuvTileExtension"] }, { kind: "component", type: TileBadgesComponent, selector: "yuv-tile-badges", inputs: ["dmsObject", "badgeIds", "bucket"] }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1304
1306
  }
1305
1307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileListComponent, decorators: [{
1306
1308
  type: Component,
@@ -1319,7 +1321,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1319
1321
  '[class.dense]': 'dense()',
1320
1322
  '(keydown.control.c)': 'onCopy($event)',
1321
1323
  '(keydown.control.x)': 'onCut($event)'
1322
- }, template: "<yuv-query-list\n #list\n [query]=\"query()\"\n [transformer]=\"transformer\"\n idProperty=\"id\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [autoSelect]=\"autoSelect()\"\n [pageSize]=\"pageSize()\"\n [multiselect]=\"multiselect()\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (itemSelect)=\"onListItemsSelect($event)\"\n (queryResult)=\"onQueryResult($event)\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n>\n <ng-template #yuvQueryListItem let-item let-index=\"index\">\n <yuv-list-tile [class.dense]=\"dense()\" [style]=\"highlightStyles()[item.id]\" (contextmenu)=\"contextMenuHandler($event, index)\">\n <ng-template #iconSlot><ng-container *yuvRenderer=\"item.icon\" /></ng-template>\n <ng-template #titleSlot><ng-container *yuvRenderer=\"item.title\" /></ng-template>\n <ng-template #descriptionSlot><ng-container *yuvRenderer=\"item.description\" /></ng-template>\n <ng-template #metaSlot><ng-container *yuvRenderer=\"item.meta\" /></ng-template>\n <ng-template #asideSlot><ng-container *yuvRenderer=\"item.aside\" /></ng-template>\n <ng-template #actionsSlot>\n @for (a of item.actions; track a.id) {\n <button\n ymt-icon-button\n [matTooltip]=\"a.label\"\n icon-button-size=\"small\"\n [disabled]=\"a.disabled\"\n (click)=\"executeAction(item, a, $event)\"\n >\n <mat-icon inert=\"true\">{{ a.icon }}</mat-icon>\n </button>\n }\n\n @if (menu()) {\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n (click)=\"menuItemClicked(item, $event)\"\n [matTooltip]=\"'yuv.tile-list.item.actions-menu.button.tooltip' | translate\"\n [matMenuTriggerFor]=\"menu()\"\n >\n <mat-icon inert=\"true\">more_vert</mat-icon>\n </button>\n }\n <ng-content select=\"yuv-tile-actions-menu, [yuv-tile-actions-menu]\" />\n </ng-template>\n <ng-template #extensionSlot>\n <ng-container *yuvTileExtension=\"{ typeId: item.objectTypeId, data: item.instanceData }\" />\n </ng-template>\n <ng-template #badgesSlot>\n <yuv-tile-badges [dmsObject]=\"item.dmsObject\" [badgeIds]=\"badgeIdsFor(item)\" [bucket]=\"bucket()\" />\n </ng-template>\n </yuv-list-tile>\n </ng-template>\n\n <ng-template #yuvQueryListEmpty>\n <div class=\"empty-list\">\n @let searchExe = searchExecuted();\n @if (searchExe && emptyContent()) {\n <ng-content />\n }\n </div>\n </ng-template>\n <div class=\"offset\" (click)=\"clearSelection()\"></div>\n</yuv-query-list>\n", styles: [":host{--paging-background: var(--ymt-surface);display:flex;flex-direction:column}:host yuv-query-list{flex:1;overflow-y:auto;display:flex;flex-flow:column;height:100%}:host yuv-query-list .offset{flex:1 1 auto}:host .empty-list{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}\n"] }]
1324
+ }, template: "<yuv-query-list\n #list\n [query]=\"query()\"\n [transformer]=\"transformer\"\n idProperty=\"id\"\n [preventChangeUntil]=\"preventChangeUntil()\"\n [autoSelect]=\"autoSelect()\"\n [pageSize]=\"pageSize()\"\n [multiselect]=\"multiselect()\"\n (itemDoubleClick)=\"onItemDoubleClick($event)\"\n (itemSelect)=\"onListItemsSelect($event)\"\n (queryResult)=\"onQueryResult($event)\"\n (dragSelectChange)=\"onDragSelectChange($event)\"\n>\n <ng-template #yuvQueryListItem let-item let-index=\"index\">\n <yuv-list-tile [class.dense]=\"dense()\" [style]=\"highlightStyles()[item.id]\" (contextmenu)=\"contextMenuHandler($event, index)\">\n <ng-template #iconSlot><ng-container *yuvRenderer=\"item.icon\" /></ng-template>\n <ng-template #titleSlot><ng-container *yuvRenderer=\"item.title\" /></ng-template>\n <ng-template #descriptionSlot><ng-container *yuvRenderer=\"item.description\" /></ng-template>\n <ng-template #metaSlot><ng-container *yuvRenderer=\"item.meta\" /></ng-template>\n <ng-template #asideSlot><ng-container *yuvRenderer=\"item.aside\" /></ng-template>\n <ng-template #actionsSlot>\n @for (a of item.actions; track a.id) {\n <button\n ymt-icon-button\n [matTooltip]=\"a.label\"\n icon-button-size=\"small\"\n [disabled]=\"a.disabled\"\n (click)=\"executeAction(item, a, $event)\"\n >\n <mat-icon inert=\"true\">{{ a.icon }}</mat-icon>\n </button>\n }\n\n @if (menu()) {\n <button\n ymt-icon-button\n icon-button-size=\"small\"\n (click)=\"menuItemClicked(item, $event)\"\n [matTooltip]=\"'yuv.tile-list.item.actions-menu.button.tooltip' | translate\"\n [matMenuTriggerFor]=\"menu()\"\n >\n <mat-icon inert=\"true\">more_vert</mat-icon>\n </button>\n }\n <ng-content select=\"yuv-tile-actions-menu, [yuv-tile-actions-menu]\" />\n </ng-template>\n <ng-template #extensionSlot>\n <ng-container *yuvTileExtension=\"{ typeId: item.objectTypeId, flavorTypeId: item.flavorTypeId, data: item.instanceData }\" />\n </ng-template>\n <ng-template #badgesSlot>\n <yuv-tile-badges [dmsObject]=\"item.dmsObject\" [badgeIds]=\"badgeIdsFor(item)\" [bucket]=\"bucket()\" />\n </ng-template>\n </yuv-list-tile>\n </ng-template>\n\n <ng-template #yuvQueryListEmpty>\n <div class=\"empty-list\">\n @let searchExe = searchExecuted();\n @if (searchExe && emptyContent()) {\n <ng-content />\n }\n </div>\n </ng-template>\n <div class=\"offset\" (click)=\"clearSelection()\"></div>\n</yuv-query-list>\n", styles: [":host{--paging-background: var(--ymt-surface);display:flex;flex-direction:column}:host yuv-query-list{flex:1;overflow-y:auto;display:flex;flex-flow:column;height:100%}:host yuv-query-list .offset{flex:1 1 auto}:host .empty-list{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}\n"] }]
1323
1325
  }], ctorParameters: () => [], propDecorators: { menuComponent: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TileActionsMenuComponent), { isSignal: true }] }], emptyContent: [{ type: i0.ContentChild, args: ['empty', { isSignal: true }] }], list: [{ type: i0.ViewChild, args: ['list', { isSignal: true }] }], menuTriggers: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => MatMenuTrigger), { isSignal: true }] }], bucket: [{ type: i0.Input, args: [{ isSignal: true, alias: "bucket", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], multiselect: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiselect", required: false }] }], dense: [{ type: i0.Input, args: [{ isSignal: true, alias: "dense", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], flavor: [{ type: i0.Input, args: [{ isSignal: true, alias: "flavor", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }], preselect: [{ type: i0.Input, args: [{ isSignal: true, alias: "preselect", required: false }] }], highlights: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlights", required: false }] }], preventChangeUntil: [{ type: i0.Input, args: [{ isSignal: true, alias: "preventChangeUntil", required: false }] }], autoSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoSelect", required: false }] }], disableCustomContextMenu: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableCustomContextMenu", required: false }] }], itemSelect: [{ type: i0.Output, args: ["itemSelect"] }], tileCopy: [{ type: i0.Output, args: ["tileCopy"] }], tileCut: [{ type: i0.Output, args: ["tileCut"] }], busy: [{ type: i0.Output, args: ["busy"] }], queryResult: [{ type: i0.Output, args: ["queryResult"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], itemDblClick: [{ type: i0.Output, args: ["itemDblClick"] }], ctxMenu: [{ type: i0.Output, args: ["ctxMenu"] }] } });
1324
1326
 
1325
1327
  class ActionSelectComponent {
@@ -1798,17 +1800,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1798
1800
  args: [{ selector: 'yuv-tile-config-trigger', standalone: true, imports: [TileConfigComponent, TranslatePipe$1, YmtIconButtonDirective, MatButtonModule, MatTooltipModule, MatIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n ymt-icon-button\n [icon-button-size]=\"small() ? 'small' : 'medium'\"\n class=\"settings icon\"\n (click)=\"openTileConfigOverlay()\"\n [matTooltip]=\"'yuv.tile-config.trigger.tooltip' | translate\"\n>\n <mat-icon>settings</mat-icon>\n</button>\n\n<ng-template #tplTileConfig>\n <yuv-tile-config\n (save)=\"onObjectConfigSave()\"\n (canceled)=\"onObjectConfigCancel()\"\n [bucket]=\"bucket() || undefined\"\n [bucketLabel]=\"bucketLabel()\"\n [configTypes]=\"options()?.configTypes\"\n [configFlavors]=\"options()?.configFlavors || []\"\n />\n</ng-template>\n" }]
1799
1801
  }], propDecorators: { tplTileConfig: [{ type: i0.ViewChild, args: ['tplTileConfig', { isSignal: true }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], bucket: [{ type: i0.Input, args: [{ isSignal: true, alias: "bucket", required: false }] }], bucketLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "bucketLabel", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], small: [{ type: i0.Input, args: [{ isSignal: true, alias: "small", required: false }] }] } });
1800
1802
 
1803
+ /**
1804
+ * Reference {@link TileExtensionComponent} implementation: shows an attachment
1805
+ * count and clip icon on email tiles. Register it for the email object type via
1806
+ * `ShellService.registerTileExtension({ typeId: 'appSystemmail:email', cmp: EmailTileExtensionComponent })`.
1807
+ */
1801
1808
  class EmailTileExtensionComponent {
1802
1809
  constructor() {
1803
1810
  this.data = input.required(...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
1804
1811
  this.attachments = computed(() => this.data()['appSystemmail:attachments'] || [], ...(ngDevMode ? [{ debugName: "attachments" }] : /* istanbul ignore next */ []));
1805
1812
  this.hasAttachements = computed(() => !!this.data()['appSystemmail:attachmentCount'], ...(ngDevMode ? [{ debugName: "hasAttachements" }] : /* istanbul ignore next */ []));
1806
1813
  }
1807
- onClick(a, evt) {
1808
- evt.preventDefault();
1809
- evt.stopPropagation();
1810
- alert(`You clicked '${a}' my friend.`);
1811
- }
1812
1814
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: EmailTileExtensionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1813
1815
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: EmailTileExtensionComponent, isStandalone: true, selector: "yuv-email-tile-extension", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.attachments": "hasAttachements()" } }, ngImport: i0, template: `
1814
1816
  @if (hasAttachements()) {
@@ -1837,6 +1839,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1837
1839
  }, styles: [":host{display:flex;justify-content:end;margin-top:.5rem}:host .icon{height:1rem}\n"] }]
1838
1840
  }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }] } });
1839
1841
 
1842
+ /**
1843
+ * @deprecated Use {@link ShellService} instead. Tile extension registration was
1844
+ * consolidated into `ShellService` (see `registerTileExtension`,
1845
+ * `getRegisteredTileExtensions`, `getTileExtension`). This service now only
1846
+ * delegates to `ShellService` so existing callers keep working against a single
1847
+ * registry. It is kept temporarily for backwards compatibility and will be
1848
+ * removed in a future release — migrate consumers to `ShellService`.
1849
+ */
1850
+ class TileExtensionService {
1851
+ constructor() {
1852
+ this.shellService = inject(ShellService);
1853
+ }
1854
+ /**
1855
+ * @deprecated Use {@link ShellService.registerTileExtension} instead.
1856
+ */
1857
+ registerTileExtension(ext) {
1858
+ this.shellService.registerTileExtension(ext);
1859
+ }
1860
+ /**
1861
+ * @deprecated Use {@link ShellService.getTileExtension} instead.
1862
+ */
1863
+ hasTileExtension(objectTypeId) {
1864
+ return !!this.shellService.getTileExtension(objectTypeId);
1865
+ }
1866
+ /**
1867
+ * Get tile extensions for a certain type
1868
+ * @param typeId ID of the type to fetch extesion for (objectTypeID or secondaryObjectTypeID)
1869
+ * @returns
1870
+ * @deprecated Use {@link ShellService.getTileExtension} instead.
1871
+ */
1872
+ getTileExtension(typeId) {
1873
+ return this.shellService.getTileExtension(typeId);
1874
+ }
1875
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileExtensionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1876
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileExtensionService, providedIn: 'root' }); }
1877
+ }
1878
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: TileExtensionService, decorators: [{
1879
+ type: Injectable,
1880
+ args: [{
1881
+ providedIn: 'root'
1882
+ }]
1883
+ }] });
1884
+
1840
1885
  /**
1841
1886
  * Generated bundle index. Do not edit.
1842
1887
  */