@ifsworld/granite-components 16.0.0 → 16.1.0-beta.2

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 { ChangeDetectionStrategy, Component, Input, ContentChildren, HostBinding, NgModule, InjectionToken, Attribute, Inject, Optional, EventEmitter, QueryList, TemplateRef, Output, ViewChild, Directive, Self, HostListener, ContentChild, Injectable, inject, ElementRef, ViewContainerRef, Pipe } from '@angular/core';
2
+ import { ChangeDetectionStrategy, Component, Input, ContentChildren, HostBinding, NgModule, InjectionToken, Attribute, Inject, Optional, HostListener, Directive, EventEmitter, QueryList, TemplateRef, ContentChild, Output, ViewChild, Self, Injectable, inject, ElementRef, ViewContainerRef, Pipe } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
4
  import { CommonModule, DOCUMENT } from '@angular/common';
5
5
  import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
@@ -689,20 +689,20 @@ function throwGraniteMenuMissingError() {
689
689
  }
690
690
  /**
691
691
  * Throws an exception for the case when menu's x-position value isn't valid.
692
- * In other words, it doesn't match 'before' or 'after'.
692
+ * In other words, it doesn't match 'before', 'after', or 'center'.
693
693
  * @docs-private
694
694
  */
695
695
  function throwGraniteMenuInvalidPositionX() {
696
- throw Error(`xPosition value must be either 'before' or after'.
696
+ throw Error(`xPosition value must be either 'before', 'after', or 'center'.
697
697
  Example: <granite-menu xPosition="before" #menu="graniteMenu"></granite-menu>`);
698
698
  }
699
699
  /**
700
700
  * Throws an exception for the case when menu's y-position value isn't valid.
701
- * In other words, it doesn't match 'above' or 'below'.
701
+ * In other words, it doesn't match 'above', 'below', or 'center'.
702
702
  * @docs-private
703
703
  */
704
704
  function throwGraniteMenuInvalidPositionY() {
705
- throw Error(`yPosition value must be either 'above' or below'.
705
+ throw Error(`yPosition value must be either 'above', 'below', or 'center'.
706
706
  Example: <granite-menu yPosition="above" #menu="graniteMenu"></granite-menu>`);
707
707
  }
708
708
 
@@ -828,6 +828,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
828
828
  type: Input
829
829
  }] } });
830
830
 
831
+ /**
832
+ * Directive to mark an element as a custom template area within a menu.
833
+ * This allows inserting custom content (dropdowns, inputs, etc.) anywhere
834
+ * within the menu, maintaining the natural DOM order.
835
+ */
836
+ class GraniteMenuCustomTemplateDirective {
837
+ constructor(_elementRef) {
838
+ this._elementRef = _elementRef;
839
+ }
840
+ _handleEvent(event) {
841
+ // Stop propagation as a safeguard - menu-base also checks for this
842
+ event.stopPropagation();
843
+ }
844
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: GraniteMenuCustomTemplateDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
845
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.4", type: GraniteMenuCustomTemplateDirective, isStandalone: false, selector: "[graniteMenuCustomTemplate]", host: { listeners: { "click": "_handleEvent($event)", "change": "_handleEvent($event)", "mousedown": "_handleEvent($event)", "input": "_handleEvent($event)" }, properties: { "class.granite-menu-custom-template": "true" } }, exportAs: ["graniteMenuCustomTemplate"], ngImport: i0 }); }
846
+ }
847
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: GraniteMenuCustomTemplateDirective, decorators: [{
848
+ type: Directive,
849
+ args: [{
850
+ selector: '[graniteMenuCustomTemplate]',
851
+ standalone: false,
852
+ exportAs: 'graniteMenuCustomTemplate',
853
+ host: {
854
+ '[class.granite-menu-custom-template]': 'true',
855
+ },
856
+ }]
857
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { _handleEvent: [{
858
+ type: HostListener,
859
+ args: ['click', ['$event']]
860
+ }, {
861
+ type: HostListener,
862
+ args: ['change', ['$event']]
863
+ }, {
864
+ type: HostListener,
865
+ args: ['mousedown', ['$event']]
866
+ }, {
867
+ type: HostListener,
868
+ args: ['input', ['$event']]
869
+ }] } });
870
+
831
871
  /** Counter for panel ID generation */
832
872
  let menuPanelUid = 0;
833
873
  /** Menu panel animation default transform values */
@@ -845,7 +885,7 @@ class _MenuBaseComponent {
845
885
  return this._xPosition;
846
886
  }
847
887
  set xPosition(value) {
848
- if (value !== 'before' && value !== 'after') {
888
+ if (value !== 'before' && value !== 'after' && value !== 'center') {
849
889
  throwGraniteMenuInvalidPositionX();
850
890
  }
851
891
  this._xPosition = value;
@@ -855,7 +895,7 @@ class _MenuBaseComponent {
855
895
  return this._yPosition;
856
896
  }
857
897
  set yPosition(value) {
858
- if (value !== 'above' && value !== 'below') {
898
+ if (value !== 'above' && value !== 'below' && value !== 'center') {
859
899
  throwGraniteMenuInvalidPositionY();
860
900
  }
861
901
  this._yPosition = value;
@@ -869,6 +909,13 @@ class _MenuBaseComponent {
869
909
  }
870
910
  constructor(_changeDetectorRef) {
871
911
  this._changeDetectorRef = _changeDetectorRef;
912
+ /**
913
+ * Custom position configuration for the overlay.
914
+ * When provided, this overrides the default positioning logic based on
915
+ * xPosition and yPosition. Uses Angular CDK ConnectedPosition format.
916
+ * If empty or not provided, falls back to default positioning behavior.
917
+ */
918
+ this.cdkConnectedOverlayPosition = [];
872
919
  this.openOnHover = true;
873
920
  this.scrollStrategy = 'reposition';
874
921
  /**
@@ -885,6 +932,8 @@ class _MenuBaseComponent {
885
932
  * Event emitted when the menu is opened
886
933
  */
887
934
  this.opened = new EventEmitter();
935
+ // Added to prevent the main menu from closing if a menu is being used in the custom template (Needs review)
936
+ this.preventParentClose = false;
888
937
  /**
889
938
  * Used for locating the panel in tests and setting the aria-control attribute
890
939
  * for the menu trigger.
@@ -1098,7 +1147,14 @@ class _MenuBaseComponent {
1098
1147
  * Handle click on the menu by emitting on the `closed` emitter
1099
1148
  * with a `click` reason
1100
1149
  */
1101
- _handleClick() {
1150
+ _handleClick(event) {
1151
+ // Don't close if click originated from custom template area
1152
+ if (event && event.target) {
1153
+ const target = event.target;
1154
+ if (target.closest('.granite-menu-custom-template')) {
1155
+ return;
1156
+ }
1157
+ }
1102
1158
  this.closed.emit('click');
1103
1159
  }
1104
1160
  /**
@@ -1147,12 +1203,17 @@ class _MenuBaseComponent {
1147
1203
  this._allItems.changes
1148
1204
  .pipe(startWith(this._allItems))
1149
1205
  .subscribe((items) => {
1150
- this._directDescendantItems.reset(items.filter((item) => item._parentMenu === this));
1206
+ this._directDescendantItems.reset(items.filter((item) => {
1207
+ const hostElement = item._getHostElement();
1208
+ // Exclude custom template areas from menu items
1209
+ return (item._parentMenu === this &&
1210
+ !hostElement.closest('.granite-menu-custom-template'));
1211
+ }));
1151
1212
  this._directDescendantItems.notifyOnChanges();
1152
1213
  });
1153
1214
  }
1154
1215
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: _MenuBaseComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive }); }
1155
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.4", type: _MenuBaseComponent, isStandalone: true, inputs: { xPosition: "xPosition", yPosition: "yPosition", title: "title", closeLabel: "closeLabel", openOnHover: "openOnHover", scrollStrategy: "scrollStrategy", styles: "styles", touchStyles: "touchStyles" }, outputs: { closed: "closed", opened: "opened" }, queries: [{ propertyName: "_allItems", predicate: GraniteMenuItemComponent, descendants: true }], viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true }], ngImport: i0 }); }
1216
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.4", type: _MenuBaseComponent, isStandalone: true, inputs: { xPosition: "xPosition", yPosition: "yPosition", cdkConnectedOverlayPosition: "cdkConnectedOverlayPosition", title: "title", closeLabel: "closeLabel", openOnHover: "openOnHover", scrollStrategy: "scrollStrategy", styles: "styles", touchStyles: "touchStyles", preventParentClose: "preventParentClose" }, outputs: { closed: "closed", opened: "opened" }, queries: [{ propertyName: "customTemplate", first: true, predicate: GraniteMenuCustomTemplateDirective, descendants: true }, { propertyName: "_allItems", predicate: GraniteMenuItemComponent, descendants: true }], viewQueries: [{ propertyName: "templateRef", first: true, predicate: TemplateRef, descendants: true }], ngImport: i0 }); }
1156
1217
  }
1157
1218
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: _MenuBaseComponent, decorators: [{
1158
1219
  type: Directive
@@ -1160,6 +1221,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1160
1221
  type: Input
1161
1222
  }], yPosition: [{
1162
1223
  type: Input
1224
+ }], cdkConnectedOverlayPosition: [{
1225
+ type: Input
1163
1226
  }], title: [{
1164
1227
  type: Input
1165
1228
  }], closeLabel: [{
@@ -1182,6 +1245,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1182
1245
  type: Output
1183
1246
  }], opened: [{
1184
1247
  type: Output
1248
+ }], customTemplate: [{
1249
+ type: ContentChild,
1250
+ args: [GraniteMenuCustomTemplateDirective]
1251
+ }], preventParentClose: [{
1252
+ type: Input
1185
1253
  }] } });
1186
1254
 
1187
1255
  class GraniteMenuTouchCloseComponent {
@@ -1251,7 +1319,7 @@ class GraniteMenuComponent extends _MenuBaseComponent {
1251
1319
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: GraniteMenuComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1252
1320
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: GraniteMenuComponent, isStandalone: false, selector: "granite-menu", providers: [
1253
1321
  { provide: GRANITE_MENU_PANEL, useExisting: GraniteMenuComponent },
1254
- ], exportAs: ["graniteMenu"], usesInheritance: true, ngImport: i0, template: "<!--\n Using separate template part for desktop and touch output, because of\n animation triggers and slightly different content.\n-->\n<ng-template>\n <!-- Desktop -->\n <ng-container *ngIf=\"_clientOutput.device === 'desktop'\">\n <div\n #menu\n class=\"granite-menu\"\n [class.is-menu-empty]=\"_isMenuEmpty$ | async\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [@transformMenuDesktop]=\"_transformMenu | async\"\n (@transformMenuDesktop.start)=\"_onAnimationStart($event)\"\n (@transformMenuDesktop.done)=\"_onAnimationDone($event)\"\n [style]=\"styles\"\n (click)=\"_handleClick()\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </div>\n </div>\n </ng-container>\n\n <!-- Touch -->\n <ng-container *ngIf=\"_clientOutput?.device === 'touch'\">\n <div\n #menu\n class=\"granite-menu granite-device-output-touch\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [style]=\"touchStyles\"\n [@transformMenuTouch]=\"_transformMenu | async\"\n (@transformMenuTouch.start)=\"_onAnimationStart($event)\"\n (@transformMenuTouch.done)=\"_onAnimationDone($event)\"\n (click)=\"_handleClick()\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <div *ngIf=\"showTitle\" class=\"header-container\">\n <button\n [disabled]=\"!showBackButton\"\n graniteMenuTouchTitleItem\n (click)=\"_handleBackClick($event)\"\n >\n {{ title }}\n </button>\n </div>\n\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n\n <div class=\"footer-container\"></div>\n </div>\n </div>\n\n <!-- Close button -->\n <div class=\"close\" [@transformCloseButton]=\"_transformMenu | async\">\n <button\n *ngIf=\"showCloseButton\"\n graniteMenuTouchCloseItem\n (click)=\"_handleCloseClick()\"\n >\n {{ closeLabel }}\n </button>\n </div>\n </ng-container>\n\n <!--\n Content template shared between desktop and touch parts, as <ng-content>\n can't be used in two places in the same template\n -->\n <ng-template #content>\n <ng-content></ng-content>\n </ng-template>\n</ng-template>\n", styles: [".granite-menu:not(.granite-device-output-touch){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);min-width:7rem;overflow-x:hidden;overflow-y:hidden}.granite-menu:not(.granite-device-output-touch).ng-animating{pointer-events:none}.granite-menu:not(.granite-device-output-touch):not(.is-menu-empty){min-height:2rem}.granite-menu:not(.granite-device-output-touch):hover{overflow-y:auto}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar{width:var(--granite-spacing-4)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-thumb{background-color:var(--granite-color-border-hard);border-radius:calc(var(--granite-spacing-16) * .125)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-track{background-color:var(--granite-color-background-hover)}.granite-menu.granite-device-output-touch{background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.granite-menu.granite-device-output-touch.ng-animating{pointer-events:none}.granite-menu.granite-device-output-touch:not(.is-menu-empty){min-height:3rem}.granite-menu.granite-device-output-touch:not(.close){margin:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch.close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch .header-container{position:sticky;top:0;background-color:var(--granite-color-background-variant);z-index:1}.granite-menu.granite-device-output-touch .footer-container{position:sticky;bottom:0;height:0}.close:not(:empty){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.close:not(:empty).ng-animating{pointer-events:none}.close:not(:empty):not(.is-menu-empty){min-height:3rem}.close:not(:empty):not(.close){margin:var(--granite-spacing-4)}.close:not(:empty).close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: GraniteMenuTouchCloseComponent, selector: "[graniteMenuTouchCloseItem]", exportAs: ["graniteMenuTouchCloseItem"] }, { kind: "component", type: GraniteMenuTouchTitleItemComponent, selector: "[graniteMenuTouchTitleItem]", exportAs: ["graniteMenuTouchTitleItem"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
1322
+ ], exportAs: ["graniteMenu"], usesInheritance: true, ngImport: i0, template: "<!--\n Using separate template part for desktop and touch output, because of\n animation triggers and slightly different content.\n-->\n<ng-template>\n <!-- Desktop -->\n <ng-container *ngIf=\"_clientOutput.device === 'desktop'\">\n <div\n #menu\n class=\"granite-menu\"\n [class.is-menu-empty]=\"_isMenuEmpty$ | async\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [@transformMenuDesktop]=\"_transformMenu | async\"\n (@transformMenuDesktop.start)=\"_onAnimationStart($event)\"\n (@transformMenuDesktop.done)=\"_onAnimationDone($event)\"\n [style]=\"styles\"\n (click)=\"_handleClick($event)\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </div>\n </div>\n </ng-container>\n\n <!-- Touch -->\n <ng-container *ngIf=\"_clientOutput?.device === 'touch'\">\n <div\n #menu\n class=\"granite-menu granite-device-output-touch\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [style]=\"touchStyles\"\n [@transformMenuTouch]=\"_transformMenu | async\"\n (@transformMenuTouch.start)=\"_onAnimationStart($event)\"\n (@transformMenuTouch.done)=\"_onAnimationDone($event)\"\n (click)=\"_handleClick($event)\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <div *ngIf=\"showTitle\" class=\"header-container\">\n <button\n [disabled]=\"!showBackButton\"\n graniteMenuTouchTitleItem\n (click)=\"_handleBackClick($event)\"\n >\n {{ title }}\n </button>\n </div>\n\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n\n <div class=\"footer-container\"></div>\n </div>\n </div>\n\n <!-- Close button -->\n <div class=\"close\" [@transformCloseButton]=\"_transformMenu | async\">\n <button\n *ngIf=\"showCloseButton\"\n graniteMenuTouchCloseItem\n (click)=\"_handleCloseClick()\"\n >\n {{ closeLabel }}\n </button>\n </div>\n </ng-container>\n\n <!--\n Content template shared between desktop and touch parts, as <ng-content>\n can't be used in two places in the same template\n -->\n <ng-template #content>\n <ng-content></ng-content>\n </ng-template>\n</ng-template>\n", styles: [".granite-menu:not(.granite-device-output-touch){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);min-width:7rem;overflow-x:hidden;overflow-y:hidden}.granite-menu:not(.granite-device-output-touch).ng-animating{pointer-events:none}.granite-menu:not(.granite-device-output-touch):not(.is-menu-empty){min-height:2rem}.granite-menu:not(.granite-device-output-touch):hover{overflow-y:auto}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar{width:var(--granite-spacing-4)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-thumb{background-color:var(--granite-color-border-hard);border-radius:calc(var(--granite-spacing-16) * .125)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-track{background-color:var(--granite-color-background-hover)}.granite-menu.granite-device-output-touch{background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.granite-menu.granite-device-output-touch.ng-animating{pointer-events:none}.granite-menu.granite-device-output-touch:not(.is-menu-empty){min-height:3rem}.granite-menu.granite-device-output-touch:not(.close){margin:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch.close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch .header-container{position:sticky;top:0;background-color:var(--granite-color-background-variant);z-index:1}.granite-menu.granite-device-output-touch .footer-container{position:sticky;bottom:0;height:0}.close:not(:empty){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.close:not(:empty).ng-animating{pointer-events:none}.close:not(:empty):not(.is-menu-empty){min-height:3rem}.close:not(:empty):not(.close){margin:var(--granite-spacing-4)}.close:not(:empty).close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}.granite-menu-custom-template{display:block}.granite-menu-custom-template:not(button){cursor:default}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: GraniteMenuTouchCloseComponent, selector: "[graniteMenuTouchCloseItem]", exportAs: ["graniteMenuTouchCloseItem"] }, { kind: "component", type: GraniteMenuTouchTitleItemComponent, selector: "[graniteMenuTouchTitleItem]", exportAs: ["graniteMenuTouchTitleItem"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }], animations: [
1255
1323
  graniteMenuDesktopAnimations.transformMenuDesktop,
1256
1324
  graniteMenuTouchAnimations.transformMenuTouch,
1257
1325
  graniteMenuTouchAnimations.transformCloseButton,
@@ -1265,7 +1333,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1265
1333
  graniteMenuTouchAnimations.transformCloseButton,
1266
1334
  ], providers: [
1267
1335
  { provide: GRANITE_MENU_PANEL, useExisting: GraniteMenuComponent },
1268
- ], template: "<!--\n Using separate template part for desktop and touch output, because of\n animation triggers and slightly different content.\n-->\n<ng-template>\n <!-- Desktop -->\n <ng-container *ngIf=\"_clientOutput.device === 'desktop'\">\n <div\n #menu\n class=\"granite-menu\"\n [class.is-menu-empty]=\"_isMenuEmpty$ | async\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [@transformMenuDesktop]=\"_transformMenu | async\"\n (@transformMenuDesktop.start)=\"_onAnimationStart($event)\"\n (@transformMenuDesktop.done)=\"_onAnimationDone($event)\"\n [style]=\"styles\"\n (click)=\"_handleClick()\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </div>\n </div>\n </ng-container>\n\n <!-- Touch -->\n <ng-container *ngIf=\"_clientOutput?.device === 'touch'\">\n <div\n #menu\n class=\"granite-menu granite-device-output-touch\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [style]=\"touchStyles\"\n [@transformMenuTouch]=\"_transformMenu | async\"\n (@transformMenuTouch.start)=\"_onAnimationStart($event)\"\n (@transformMenuTouch.done)=\"_onAnimationDone($event)\"\n (click)=\"_handleClick()\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <div *ngIf=\"showTitle\" class=\"header-container\">\n <button\n [disabled]=\"!showBackButton\"\n graniteMenuTouchTitleItem\n (click)=\"_handleBackClick($event)\"\n >\n {{ title }}\n </button>\n </div>\n\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n\n <div class=\"footer-container\"></div>\n </div>\n </div>\n\n <!-- Close button -->\n <div class=\"close\" [@transformCloseButton]=\"_transformMenu | async\">\n <button\n *ngIf=\"showCloseButton\"\n graniteMenuTouchCloseItem\n (click)=\"_handleCloseClick()\"\n >\n {{ closeLabel }}\n </button>\n </div>\n </ng-container>\n\n <!--\n Content template shared between desktop and touch parts, as <ng-content>\n can't be used in two places in the same template\n -->\n <ng-template #content>\n <ng-content></ng-content>\n </ng-template>\n</ng-template>\n", styles: [".granite-menu:not(.granite-device-output-touch){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);min-width:7rem;overflow-x:hidden;overflow-y:hidden}.granite-menu:not(.granite-device-output-touch).ng-animating{pointer-events:none}.granite-menu:not(.granite-device-output-touch):not(.is-menu-empty){min-height:2rem}.granite-menu:not(.granite-device-output-touch):hover{overflow-y:auto}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar{width:var(--granite-spacing-4)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-thumb{background-color:var(--granite-color-border-hard);border-radius:calc(var(--granite-spacing-16) * .125)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-track{background-color:var(--granite-color-background-hover)}.granite-menu.granite-device-output-touch{background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.granite-menu.granite-device-output-touch.ng-animating{pointer-events:none}.granite-menu.granite-device-output-touch:not(.is-menu-empty){min-height:3rem}.granite-menu.granite-device-output-touch:not(.close){margin:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch.close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch .header-container{position:sticky;top:0;background-color:var(--granite-color-background-variant);z-index:1}.granite-menu.granite-device-output-touch .footer-container{position:sticky;bottom:0;height:0}.close:not(:empty){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.close:not(:empty).ng-animating{pointer-events:none}.close:not(:empty):not(.is-menu-empty){min-height:3rem}.close:not(:empty):not(.close){margin:var(--granite-spacing-4)}.close:not(:empty).close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}\n"] }]
1336
+ ], template: "<!--\n Using separate template part for desktop and touch output, because of\n animation triggers and slightly different content.\n-->\n<ng-template>\n <!-- Desktop -->\n <ng-container *ngIf=\"_clientOutput.device === 'desktop'\">\n <div\n #menu\n class=\"granite-menu\"\n [class.is-menu-empty]=\"_isMenuEmpty$ | async\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [@transformMenuDesktop]=\"_transformMenu | async\"\n (@transformMenuDesktop.start)=\"_onAnimationStart($event)\"\n (@transformMenuDesktop.done)=\"_onAnimationDone($event)\"\n [style]=\"styles\"\n (click)=\"_handleClick($event)\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n </div>\n </div>\n </ng-container>\n\n <!-- Touch -->\n <ng-container *ngIf=\"_clientOutput?.device === 'touch'\">\n <div\n #menu\n class=\"granite-menu granite-device-output-touch\"\n tabindex=\"-1\"\n [id]=\"panelId\"\n [style]=\"touchStyles\"\n [@transformMenuTouch]=\"_transformMenu | async\"\n (@transformMenuTouch.start)=\"_onAnimationStart($event)\"\n (@transformMenuTouch.done)=\"_onAnimationDone($event)\"\n (click)=\"_handleClick($event)\"\n (keydown)=\"_handleKeydown($event)\"\n >\n <div class=\"granite-menu-content\">\n <div *ngIf=\"showTitle\" class=\"header-container\">\n <button\n [disabled]=\"!showBackButton\"\n graniteMenuTouchTitleItem\n (click)=\"_handleBackClick($event)\"\n >\n {{ title }}\n </button>\n </div>\n\n <ng-container [ngTemplateOutlet]=\"content\"></ng-container>\n\n <div class=\"footer-container\"></div>\n </div>\n </div>\n\n <!-- Close button -->\n <div class=\"close\" [@transformCloseButton]=\"_transformMenu | async\">\n <button\n *ngIf=\"showCloseButton\"\n graniteMenuTouchCloseItem\n (click)=\"_handleCloseClick()\"\n >\n {{ closeLabel }}\n </button>\n </div>\n </ng-container>\n\n <!--\n Content template shared between desktop and touch parts, as <ng-content>\n can't be used in two places in the same template\n -->\n <ng-template #content>\n <ng-content></ng-content>\n </ng-template>\n</ng-template>\n", styles: [".granite-menu:not(.granite-device-output-touch){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);min-width:7rem;overflow-x:hidden;overflow-y:hidden}.granite-menu:not(.granite-device-output-touch).ng-animating{pointer-events:none}.granite-menu:not(.granite-device-output-touch):not(.is-menu-empty){min-height:2rem}.granite-menu:not(.granite-device-output-touch):hover{overflow-y:auto}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar{width:var(--granite-spacing-4)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-thumb{background-color:var(--granite-color-border-hard);border-radius:calc(var(--granite-spacing-16) * .125)}.granite-menu:not(.granite-device-output-touch)::-webkit-scrollbar-track{background-color:var(--granite-color-background-hover)}.granite-menu.granite-device-output-touch{background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.granite-menu.granite-device-output-touch.ng-animating{pointer-events:none}.granite-menu.granite-device-output-touch:not(.is-menu-empty){min-height:3rem}.granite-menu.granite-device-output-touch:not(.close){margin:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch.close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}.granite-menu.granite-device-output-touch .header-container{position:sticky;top:0;background-color:var(--granite-color-background-variant);z-index:1}.granite-menu.granite-device-output-touch .footer-container{position:sticky;bottom:0;height:0}.close:not(:empty){background-color:var(--granite-color-background-variant);color:var(--granite-color-text);overflow:auto;-webkit-overflow-scrolling:touch;outline:0;-webkit-user-select:none;user-select:none;box-shadow:var(--granite-shadow-l);border-radius:.25rem}.close:not(:empty).ng-animating{pointer-events:none}.close:not(:empty):not(.is-menu-empty){min-height:3rem}.close:not(:empty):not(.close){margin:var(--granite-spacing-4)}.close:not(:empty).close{margin-inline-start:var(--granite-spacing-4);margin-inline-end:var(--granite-spacing-4);margin-block-end:var(--granite-spacing-4)}.granite-menu-custom-template{display:block}.granite-menu-custom-template:not(button){cursor:default}\n"] }]
1269
1337
  }] });
1270
1338
 
1271
1339
  /**
@@ -1493,7 +1561,9 @@ class GraniteMenuTriggerForDirective {
1493
1561
  }
1494
1562
  // If a click closed the menu, we should close the entire chain of nested menus.
1495
1563
  if ((reason === 'click' || reason === 'tab') && this._parentMenu) {
1496
- this._parentMenu.closed.emit(reason);
1564
+ if (!this.menu.preventParentClose) {
1565
+ this._parentMenu.closed.emit(reason);
1566
+ }
1497
1567
  }
1498
1568
  });
1499
1569
  }
@@ -1511,6 +1581,13 @@ class GraniteMenuTriggerForDirective {
1511
1581
  this.menu.parentMenu = this.triggersSubmenu()
1512
1582
  ? this._parentMenu
1513
1583
  : undefined;
1584
+ // Check if the menu is in a custom template
1585
+ if (this._parentMenu && this._viewContainerRef?.element?.nativeElement) {
1586
+ const isInCustomTemplate = this._viewContainerRef.element.nativeElement.closest('.granite-menu-custom-template');
1587
+ if (isInCustomTemplate) {
1588
+ this.menu.preventParentClose = true;
1589
+ }
1590
+ }
1514
1591
  this.menu.direction = this._dir.value === 'rtl' ? 'rtl' : 'ltr';
1515
1592
  if (this._parentMenu) {
1516
1593
  // Menu triggers inherit target device types from their parent.
@@ -1748,26 +1825,51 @@ class GraniteMenuTriggerForDirective {
1748
1825
  * @param positionStrategy Strategy whose position to update.
1749
1826
  */
1750
1827
  _setPosition(positionStrategy) {
1828
+ if (this.menu.cdkConnectedOverlayPosition &&
1829
+ this.menu.cdkConnectedOverlayPosition.length > 0) {
1830
+ positionStrategy.withPositions(this.menu.cdkConnectedOverlayPosition);
1831
+ return;
1832
+ }
1751
1833
  const MENU_PANEL_TOP_PADDING = 0;
1752
- let [originX, originFallbackX] = this.menu.xPosition === 'before' ? ['end', 'start'] : ['start', 'end'];
1753
- const [overlayY, overlayFallbackY] = this.menu.yPosition === 'above' ? ['bottom', 'top'] : ['top', 'bottom'];
1834
+ let [originX, originFallbackX] = this.menu.xPosition === 'before'
1835
+ ? ['end', 'start']
1836
+ : this.menu.xPosition === 'center'
1837
+ ? ['center', 'center']
1838
+ : ['start', 'end'];
1839
+ const [overlayY, overlayFallbackY] = this.menu.yPosition === 'above'
1840
+ ? ['bottom', 'top']
1841
+ : this.menu.yPosition === 'center'
1842
+ ? ['center', 'center']
1843
+ : ['top', 'bottom'];
1754
1844
  let [originY, originFallbackY] = [overlayY, overlayFallbackY];
1755
1845
  let [overlayX, overlayFallbackX] = [originX, originFallbackX];
1756
1846
  let offsetY = 0;
1757
1847
  if (this.triggersSubmenu()) {
1758
1848
  // When the menu is a sub-menu, it should always align itself
1759
1849
  // to the edges of the trigger, instead of overlapping it.
1760
- overlayFallbackX = originX =
1761
- this.menu.xPosition === 'before' ? 'start' : 'end';
1762
- originFallbackX = overlayX = originX === 'end' ? 'start' : 'end';
1850
+ if (this.menu.xPosition === 'center') {
1851
+ overlayFallbackX = originX = 'center';
1852
+ originFallbackX = overlayX = 'center';
1853
+ }
1854
+ else {
1855
+ overlayFallbackX = originX =
1856
+ this.menu.xPosition === 'before' ? 'start' : 'end';
1857
+ originFallbackX = overlayX = originX === 'end' ? 'start' : 'end';
1858
+ }
1763
1859
  offsetY =
1764
1860
  overlayY === 'bottom'
1765
1861
  ? MENU_PANEL_TOP_PADDING
1766
1862
  : -MENU_PANEL_TOP_PADDING;
1767
1863
  }
1768
1864
  else {
1769
- originY = overlayY === 'top' ? 'bottom' : 'top';
1770
- originFallbackY = overlayFallbackY === 'top' ? 'bottom' : 'top';
1865
+ if (this.menu.yPosition === 'center') {
1866
+ originY = 'center';
1867
+ originFallbackY = 'center';
1868
+ }
1869
+ else {
1870
+ originY = overlayY === 'top' ? 'bottom' : 'top';
1871
+ originFallbackY = overlayFallbackY === 'top' ? 'bottom' : 'top';
1872
+ }
1771
1873
  }
1772
1874
  positionStrategy.withPositions([
1773
1875
  { originX, originY, overlayX, overlayY, offsetY },
@@ -2064,13 +2166,15 @@ class GraniteMenuModule {
2064
2166
  GraniteMenuTouchCloseComponent,
2065
2167
  GraniteMenuTouchTitleItemComponent,
2066
2168
  GraniteDividerDirective,
2067
- GraniteTitleDirective], imports: [CommonModule, OverlayModule, PortalModule, GraniteIconModule], exports: [GraniteMenuComponent,
2169
+ GraniteTitleDirective,
2170
+ GraniteMenuCustomTemplateDirective], imports: [CommonModule, OverlayModule, PortalModule, GraniteIconModule], exports: [GraniteMenuComponent,
2068
2171
  GraniteMenuItemComponent,
2069
2172
  GraniteMenuTriggerForDirective,
2070
2173
  GraniteMenuTouchCloseComponent,
2071
2174
  GraniteMenuTouchTitleItemComponent,
2072
2175
  GraniteDividerDirective,
2073
- GraniteTitleDirective] }); }
2176
+ GraniteTitleDirective,
2177
+ GraniteMenuCustomTemplateDirective] }); }
2074
2178
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: GraniteMenuModule, imports: [CommonModule, OverlayModule, PortalModule, GraniteIconModule] }); }
2075
2179
  }
2076
2180
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: GraniteMenuModule, decorators: [{
@@ -2084,6 +2188,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
2084
2188
  GraniteMenuTouchTitleItemComponent,
2085
2189
  GraniteDividerDirective,
2086
2190
  GraniteTitleDirective,
2191
+ GraniteMenuCustomTemplateDirective,
2087
2192
  ],
2088
2193
  imports: [CommonModule, OverlayModule, PortalModule, GraniteIconModule],
2089
2194
  exports: [
@@ -2094,6 +2199,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
2094
2199
  GraniteMenuTouchTitleItemComponent,
2095
2200
  GraniteDividerDirective,
2096
2201
  GraniteTitleDirective,
2202
+ GraniteMenuCustomTemplateDirective,
2097
2203
  ],
2098
2204
  }]
2099
2205
  }] });
@@ -5609,5 +5715,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
5609
5715
  * Generated bundle index. Do not edit.
5610
5716
  */
5611
5717
 
5612
- export { AVATAR_DEFAULT_STATUS, ButtonSelectors, CONTACT_DEFAULT_STATUS, ClientInputDesktopDirective, ClientInputTouchDirective, ClientOutputDesktopDirective, ClientOutputTouchDirective, ContactItemDefaultStatusComponent, GRANITE_CLIENT_INPUT, GRANITE_CLIENT_OUTPUT, GraniteAnchorComponent, GraniteArrangeGridComponent, GraniteArrangeGridItemComponent, GraniteArrangeGridModule, GraniteArrangeGridOrientation, GraniteAvatarComponent, GraniteAvatarDefaultStatusComponent, GraniteAvatarModule, GraniteBadgeComponent, GraniteBadgeHarness, GraniteBadgeModule, GraniteButtonComponent, GraniteButtonModule, GraniteCardActionsComponent, GraniteCardAvatarComponent, GraniteCardBodyComponent, GraniteCardComponent, GraniteCardContentComponent, GraniteCardFooterComponent, GraniteCardHeaderComponent, GraniteCardHeaderSubTitleComponent, GraniteCardHeaderTitleComponent, GraniteCardListComponent, GraniteCardListModule, GraniteCheckboxComponent, GraniteCheckboxGroupComponent, GraniteCheckboxModule, GraniteChipComponent, GraniteChipInputDirective, GraniteChipListComponent, GraniteChipSelectionChangeEvent, GraniteChipsModule, GraniteCollapsibleConditionalBodyDirective, GraniteCollapsibleConditionalHeaderDirective, GraniteCollapsibleGroupComponent, GraniteCollapsibleGroupModule, GraniteContactItemComponent, GraniteContactItemTitleComponent, GraniteContactsComponent, GraniteContactsModule, GraniteContactsProfileComponent, GraniteContactsTriggerForDirective, GraniteCoreModule, GraniteCustomAvatarStatusDirective, GraniteCustomProfileDirective, GraniteCustomStatusDirective, GraniteDividerDirective, GraniteEmptyAvatarComponent, GraniteGridComponent, GraniteGridItemComponent, GraniteGridModule, GraniteHideOnOverflowDirective, GraniteIconComponent, GraniteIconModule, GraniteInputFieldComponent, GraniteInputFieldModule, GraniteLabelComponent, GraniteLabelModule, GraniteMenuComponent, GraniteMenuHarness, GraniteMenuItemComponent, GraniteMenuItemHarness, GraniteMenuModule, GraniteMenuTouchCloseComponent, GraniteMenuTouchTitleItemComponent, GraniteMenuTriggerForDirective, GraniteProgressBarComponent, GraniteProgressBarModule, GraniteRadioButtonComponent, GraniteRadioButtonModule, GraniteRadioGroupComponent, GraniteTitleDirective, GraniteTitlePipe, GraniteToggleSwitchComponent, GraniteToggleSwitchModule, PurePipesModule, deviceDesktop, deviceTouch, disabledMixin, graniteMenuDesktopAnimations, graniteMenuTouchAnimations };
5718
+ export { AVATAR_DEFAULT_STATUS, ButtonSelectors, CONTACT_DEFAULT_STATUS, ClientInputDesktopDirective, ClientInputTouchDirective, ClientOutputDesktopDirective, ClientOutputTouchDirective, ContactItemDefaultStatusComponent, GRANITE_CLIENT_INPUT, GRANITE_CLIENT_OUTPUT, GraniteAnchorComponent, GraniteArrangeGridComponent, GraniteArrangeGridItemComponent, GraniteArrangeGridModule, GraniteArrangeGridOrientation, GraniteAvatarComponent, GraniteAvatarDefaultStatusComponent, GraniteAvatarModule, GraniteBadgeComponent, GraniteBadgeHarness, GraniteBadgeModule, GraniteButtonComponent, GraniteButtonModule, GraniteCardActionsComponent, GraniteCardAvatarComponent, GraniteCardBodyComponent, GraniteCardComponent, GraniteCardContentComponent, GraniteCardFooterComponent, GraniteCardHeaderComponent, GraniteCardHeaderSubTitleComponent, GraniteCardHeaderTitleComponent, GraniteCardListComponent, GraniteCardListModule, GraniteCheckboxComponent, GraniteCheckboxGroupComponent, GraniteCheckboxModule, GraniteChipComponent, GraniteChipInputDirective, GraniteChipListComponent, GraniteChipSelectionChangeEvent, GraniteChipsModule, GraniteCollapsibleConditionalBodyDirective, GraniteCollapsibleConditionalHeaderDirective, GraniteCollapsibleGroupComponent, GraniteCollapsibleGroupModule, GraniteContactItemComponent, GraniteContactItemTitleComponent, GraniteContactsComponent, GraniteContactsModule, GraniteContactsProfileComponent, GraniteContactsTriggerForDirective, GraniteCoreModule, GraniteCustomAvatarStatusDirective, GraniteCustomProfileDirective, GraniteCustomStatusDirective, GraniteDividerDirective, GraniteEmptyAvatarComponent, GraniteGridComponent, GraniteGridItemComponent, GraniteGridModule, GraniteHideOnOverflowDirective, GraniteIconComponent, GraniteIconModule, GraniteInputFieldComponent, GraniteInputFieldModule, GraniteLabelComponent, GraniteLabelModule, GraniteMenuComponent, GraniteMenuCustomTemplateDirective, GraniteMenuHarness, GraniteMenuItemComponent, GraniteMenuItemHarness, GraniteMenuModule, GraniteMenuTouchCloseComponent, GraniteMenuTouchTitleItemComponent, GraniteMenuTriggerForDirective, GraniteProgressBarComponent, GraniteProgressBarModule, GraniteRadioButtonComponent, GraniteRadioButtonModule, GraniteRadioGroupComponent, GraniteTitleDirective, GraniteTitlePipe, GraniteToggleSwitchComponent, GraniteToggleSwitchModule, PurePipesModule, deviceDesktop, deviceTouch, disabledMixin, graniteMenuDesktopAnimations, graniteMenuTouchAnimations };
5613
5719
  //# sourceMappingURL=ifsworld-granite-components.mjs.map