@ifsworld/granite-components 18.0.1 → 18.0.2-beta.1
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.
|
@@ -664,6 +664,57 @@ const graniteMenuTouchAnimations = {
|
|
|
664
664
|
*/
|
|
665
665
|
const GRANITE_MENU_PANEL = new InjectionToken('GRANITE_MENU_PANEL');
|
|
666
666
|
|
|
667
|
+
/**
|
|
668
|
+
* Utility class to manage a stack of open menus for touch devices.
|
|
669
|
+
* This ensures that when clicking outside or closing, only the topmost menu closes.
|
|
670
|
+
*/
|
|
671
|
+
class MenuStack {
|
|
672
|
+
static { this.stack = []; }
|
|
673
|
+
/**
|
|
674
|
+
* Push a menu onto the stack when it opens
|
|
675
|
+
*/
|
|
676
|
+
static push(menu) {
|
|
677
|
+
const index = this.stack.indexOf(menu);
|
|
678
|
+
if (index === -1) {
|
|
679
|
+
this.stack.push(menu);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Remove a menu from the stack when it closes
|
|
684
|
+
*/
|
|
685
|
+
static pop(menu) {
|
|
686
|
+
const index = this.stack.indexOf(menu);
|
|
687
|
+
if (index !== -1) {
|
|
688
|
+
this.stack.splice(index, 1);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get the topmost menu in the stack
|
|
693
|
+
*/
|
|
694
|
+
static getTop() {
|
|
695
|
+
return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Check if a menu is the topmost one
|
|
699
|
+
*/
|
|
700
|
+
static isTop(menu) {
|
|
701
|
+
const top = this.getTop();
|
|
702
|
+
return top !== null && top.panelId === menu.panelId;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Clear the entire stack (useful for cleanup)
|
|
706
|
+
*/
|
|
707
|
+
static clear() {
|
|
708
|
+
this.stack = [];
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Get the current stack size
|
|
712
|
+
*/
|
|
713
|
+
static size() {
|
|
714
|
+
return this.stack.length;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
667
718
|
/**
|
|
668
719
|
* @license
|
|
669
720
|
* Copyright Google LLC All Rights Reserved.
|
|
@@ -1153,9 +1204,18 @@ class _MenuBaseComponent {
|
|
|
1153
1204
|
}
|
|
1154
1205
|
/**
|
|
1155
1206
|
* Handle click on the close button by emitting on the `closed` emitter
|
|
1156
|
-
* without any particular reason
|
|
1207
|
+
* without any particular reason.
|
|
1208
|
+
* For touch devices, only closes if this is the topmost menu in the stack.
|
|
1157
1209
|
*/
|
|
1158
|
-
_handleCloseClick() {
|
|
1210
|
+
_handleCloseClick(event) {
|
|
1211
|
+
//#region --- Touch device customizations ---
|
|
1212
|
+
if (this._clientOutput?.device === 'touch') {
|
|
1213
|
+
event?.stopPropagation();
|
|
1214
|
+
if (!MenuStack.isTop(this)) {
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
//#endregion --- Touch device customizations ---
|
|
1159
1219
|
this.closed.emit();
|
|
1160
1220
|
}
|
|
1161
1221
|
/**
|
|
@@ -1309,7 +1369,7 @@ class GraniteMenuComponent extends _MenuBaseComponent {
|
|
|
1309
1369
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: GraniteMenuComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1310
1370
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.9", type: GraniteMenuComponent, isStandalone: false, selector: "granite-menu", providers: [
|
|
1311
1371
|
{ provide: GRANITE_MENU_PANEL, useExisting: GraniteMenuComponent },
|
|
1312
|
-
], 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.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.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.AsyncPipe, name: "async" }], animations: [
|
|
1372
|
+
], 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($event)\"\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.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.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.AsyncPipe, name: "async" }], animations: [
|
|
1313
1373
|
graniteMenuDesktopAnimations.transformMenuDesktop,
|
|
1314
1374
|
graniteMenuTouchAnimations.transformMenuTouch,
|
|
1315
1375
|
graniteMenuTouchAnimations.transformCloseButton,
|
|
@@ -1323,7 +1383,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
1323
1383
|
graniteMenuTouchAnimations.transformCloseButton,
|
|
1324
1384
|
], providers: [
|
|
1325
1385
|
{ provide: GRANITE_MENU_PANEL, useExisting: GraniteMenuComponent },
|
|
1326
|
-
], 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"] }]
|
|
1386
|
+
], 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($event)\"\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"] }]
|
|
1327
1387
|
}] });
|
|
1328
1388
|
|
|
1329
1389
|
/** Options for binding a passive event listener. */
|
|
@@ -1484,6 +1544,10 @@ class GraniteMenuTriggerForDirective {
|
|
|
1484
1544
|
if (this._overlayRef) {
|
|
1485
1545
|
//#region --- Touch device customizations ---
|
|
1486
1546
|
this.removeOverlayListeners();
|
|
1547
|
+
// Remove from stack on destroy
|
|
1548
|
+
if (this.menu && this._clientOutput?.device === 'touch') {
|
|
1549
|
+
MenuStack.pop(this.menu);
|
|
1550
|
+
}
|
|
1487
1551
|
//#endregion --- Touch device customizations ---
|
|
1488
1552
|
this._overlayRef.dispose();
|
|
1489
1553
|
this._overlayRef = null;
|
|
@@ -1507,6 +1571,8 @@ class GraniteMenuTriggerForDirective {
|
|
|
1507
1571
|
this.menu._isClosing = true;
|
|
1508
1572
|
// Get rid of the menu and tell any parent to restore its position
|
|
1509
1573
|
if (this._clientOutput.device === 'touch') {
|
|
1574
|
+
// Remove from stack when closing
|
|
1575
|
+
MenuStack.pop(this.menu);
|
|
1510
1576
|
// First we wait for any running animation to complete
|
|
1511
1577
|
const runningAnimationDone = this.menu._isAnimating
|
|
1512
1578
|
? this.menu._animationDone
|
|
@@ -1525,7 +1591,11 @@ class GraniteMenuTriggerForDirective {
|
|
|
1525
1591
|
this._destroyMenu();
|
|
1526
1592
|
}
|
|
1527
1593
|
// If a click closed the menu, we should close the entire chain of nested menus.
|
|
1528
|
-
|
|
1594
|
+
// For touch devices, only propagate if it's not an outside click (handled by stack)
|
|
1595
|
+
const shouldPropagate = this._clientOutput.device !== 'touch' &&
|
|
1596
|
+
(reason === 'click' || reason === 'tab') &&
|
|
1597
|
+
this._parentMenu;
|
|
1598
|
+
if (shouldPropagate) {
|
|
1529
1599
|
if (!this.menu.preventParentClose) {
|
|
1530
1600
|
this._parentMenu.closed.emit(reason);
|
|
1531
1601
|
}
|
|
@@ -1614,6 +1684,12 @@ class GraniteMenuTriggerForDirective {
|
|
|
1614
1684
|
// Subscribe to stream that emits whenever an action that should close the menu occurs
|
|
1615
1685
|
this._closingActionsSubscription = this._menuClosingActions().subscribe(() => this.closeMenu());
|
|
1616
1686
|
this.animateOpenMenu();
|
|
1687
|
+
//#region --- Touch device customizations ---
|
|
1688
|
+
// Add menu to stack when opening on touch devices
|
|
1689
|
+
if (this._clientOutput.device === 'touch') {
|
|
1690
|
+
MenuStack.push(this.menu);
|
|
1691
|
+
}
|
|
1692
|
+
//#endregion --- Touch device customizations ---
|
|
1617
1693
|
this._setIsMenuOpen(true);
|
|
1618
1694
|
this.menu.focusFirstItem(this.openedBy || 'program');
|
|
1619
1695
|
}
|
|
@@ -1873,10 +1949,16 @@ class GraniteMenuTriggerForDirective {
|
|
|
1873
1949
|
// Note: Quick fix. Feature reportedly exists in CDK for Angular 10
|
|
1874
1950
|
// Filter to prevent closing when animating added though. Applied to
|
|
1875
1951
|
// root menu only.
|
|
1876
|
-
|
|
1952
|
+
// For touch devices, handle outside clicks for all menus (not just root)
|
|
1953
|
+
// but only close the topmost menu in the stack
|
|
1954
|
+
const outsideClick = this._clientOutput.device === 'touch'
|
|
1877
1955
|
? fromEvent(this._document, 'click').pipe(filter((e) => e.target !== this._element.nativeElement &&
|
|
1878
|
-
e.target.closest('.granite-menu') === null
|
|
1879
|
-
|
|
1956
|
+
e.target.closest('.granite-menu') === null &&
|
|
1957
|
+
MenuStack.isTop(this.menu)), filter(() => !this.menu._isAnimating))
|
|
1958
|
+
: !this._parentMenu
|
|
1959
|
+
? fromEvent(this._document, 'click').pipe(filter((e) => e.target !== this._element.nativeElement &&
|
|
1960
|
+
e.target.closest('.granite-menu') === null), filter(() => !this.menu._isAnimating))
|
|
1961
|
+
: of(null);
|
|
1880
1962
|
return merge(detachments, hover, parentClose, outsideClick).pipe(filter((event) => event !== null));
|
|
1881
1963
|
}
|
|
1882
1964
|
/**
|