@ifsworld/granite-components 18.1.0-beta.1 → 18.1.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.
@@ -962,6 +962,8 @@ class _MenuBaseComponent {
962
962
  this._transformMenu = new BehaviorSubject(transformMenuDefault);
963
963
  /** Emits whenever an animation on the menu completes. */
964
964
  this._animationDone = new Subject();
965
+ /** Whether this menu participates in the menu stack. */
966
+ this._usesMenuStack = false;
965
967
  this._changeDetectorRef = inject(ChangeDetectorRef);
966
968
  this._menuStack = inject(GraniteMenuStackService);
967
969
  this._menuEmpty$ = new BehaviorSubject(false);
@@ -1208,7 +1210,7 @@ class _MenuBaseComponent {
1208
1210
  //#region --- Touch device customizations ---
1209
1211
  if (this._clientOutput?.device === 'touch') {
1210
1212
  event?.stopPropagation();
1211
- if (!this._menuStack.isTop(this)) {
1213
+ if (this._usesMenuStack && !this._menuStack.isTop(this)) {
1212
1214
  return;
1213
1215
  }
1214
1216
  }
@@ -1401,6 +1403,7 @@ class GraniteMenuTriggerForDirective {
1401
1403
  // Tracking input type is necessary so it's possible to only auto-focus
1402
1404
  // the first item of the list when the menu is opened via the keyboard
1403
1405
  this.openedBy = null;
1406
+ this._isInCustomTemplate = false;
1404
1407
  this._hoverSubscription = Subscription.EMPTY;
1405
1408
  this._menuCloseSubscription = Subscription.EMPTY;
1406
1409
  this._closingActionsSubscription = Subscription.EMPTY;
@@ -1542,9 +1545,11 @@ class GraniteMenuTriggerForDirective {
1542
1545
  if (this._overlayRef) {
1543
1546
  //#region --- Touch device customizations ---
1544
1547
  this.removeOverlayListeners();
1545
- // Remove from stack on destroy
1546
- if (this.menu && this._clientOutput?.device === 'touch') {
1548
+ if (this.menu &&
1549
+ this._clientOutput?.device === 'touch' &&
1550
+ this._isInCustomTemplate) {
1547
1551
  this._menuStack.pop(this.menu);
1552
+ this.menu._usesMenuStack = false;
1548
1553
  }
1549
1554
  //#endregion --- Touch device customizations ---
1550
1555
  this._overlayRef.dispose();
@@ -1569,8 +1574,10 @@ class GraniteMenuTriggerForDirective {
1569
1574
  this.menu._isClosing = true;
1570
1575
  // Get rid of the menu and tell any parent to restore its position
1571
1576
  if (this._clientOutput.device === 'touch') {
1572
- // Remove from stack when closing
1573
- this._menuStack.pop(this.menu);
1577
+ if (this._isInCustomTemplate) {
1578
+ this._menuStack.pop(this.menu);
1579
+ this.menu._usesMenuStack = false;
1580
+ }
1574
1581
  // First we wait for any running animation to complete
1575
1582
  const runningAnimationDone = this.menu._isAnimating
1576
1583
  ? this.menu._animationDone
@@ -1588,11 +1595,11 @@ class GraniteMenuTriggerForDirective {
1588
1595
  //#endregion --- Touch device customizations ---
1589
1596
  this._destroyMenu();
1590
1597
  }
1591
- // If a click closed the menu, we should close the entire chain of nested menus.
1592
- // For touch devices, only propagate if it's not an outside click (handled by stack)
1593
- const shouldPropagate = this._clientOutput.device !== 'touch' &&
1594
- (reason === 'click' || reason === 'tab') &&
1595
- this._parentMenu;
1598
+ const isTouch = this._clientOutput.device === 'touch';
1599
+ const shouldPropagate = this._parentMenu &&
1600
+ (isTouch
1601
+ ? !this._isInCustomTemplate && reason !== 'keydown'
1602
+ : reason === 'click' || reason === 'tab');
1596
1603
  if (shouldPropagate) {
1597
1604
  if (!this.menu.preventParentClose) {
1598
1605
  this._parentMenu.closed.emit(reason);
@@ -1614,12 +1621,11 @@ class GraniteMenuTriggerForDirective {
1614
1621
  this.menu.parentMenu = this.triggersSubmenu()
1615
1622
  ? this._parentMenu
1616
1623
  : undefined;
1617
- // Check if the menu is in a custom template
1618
- if (this._parentMenu && this._viewContainerRef?.element?.nativeElement) {
1619
- const isInCustomTemplate = this._viewContainerRef.element.nativeElement.closest('.granite-menu-custom-template');
1620
- if (isInCustomTemplate) {
1621
- this.menu.preventParentClose = true;
1622
- }
1624
+ const isInCustomTemplate = !!(this._parentMenu &&
1625
+ this._viewContainerRef?.element?.nativeElement?.closest('.granite-menu-custom-template'));
1626
+ this._isInCustomTemplate = isInCustomTemplate;
1627
+ if (isInCustomTemplate) {
1628
+ this.menu.preventParentClose = true;
1623
1629
  }
1624
1630
  this.menu.direction = this._dir.value === 'rtl' ? 'rtl' : 'ltr';
1625
1631
  if (this._parentMenu) {
@@ -1645,6 +1651,12 @@ class GraniteMenuTriggerForDirective {
1645
1651
  if (this._clientOutput.device === 'touch') {
1646
1652
  panelClass.push('granite-overlay-pane-fill-width-bottom');
1647
1653
  }
1654
+ if (isInCustomTemplate) {
1655
+ panelClass.push('from-custom-template');
1656
+ }
1657
+ if (this.menu.customTemplate) {
1658
+ panelClass.push('has-custom-template');
1659
+ }
1648
1660
  // setting scrollStrategy options to overlay
1649
1661
  const scrollStrategy = this.setScrollStrategyToOverylay(this._clientOutput.device, this.menu.scrollStrategy);
1650
1662
  const hasBackdrop = this._clientOutput.device === 'touch' && !this.triggersSubmenu();
@@ -1672,6 +1684,18 @@ class GraniteMenuTriggerForDirective {
1672
1684
  if (this._clientOutput.device === 'touch') {
1673
1685
  this.menu._panelAnimationState = 'void';
1674
1686
  }
1687
+ if (isInCustomTemplate) {
1688
+ this._overlayRef.addPanelClass('from-custom-template');
1689
+ }
1690
+ else {
1691
+ this._overlayRef.removePanelClass('from-custom-template');
1692
+ }
1693
+ if (this.menu.customTemplate) {
1694
+ this._overlayRef.addPanelClass('has-custom-template');
1695
+ }
1696
+ else {
1697
+ this._overlayRef.removePanelClass('has-custom-template');
1698
+ }
1675
1699
  }
1676
1700
  // Create portal from associated menu's template
1677
1701
  if (!this._portal || this._portal.templateRef !== this.menu.templateRef) {
@@ -1683,9 +1707,10 @@ class GraniteMenuTriggerForDirective {
1683
1707
  this._closingActionsSubscription = this._menuClosingActions().subscribe(() => this.closeMenu());
1684
1708
  this.animateOpenMenu();
1685
1709
  //#region --- Touch device customizations ---
1686
- // Add menu to stack when opening on touch devices
1687
- if (this._clientOutput.device === 'touch') {
1710
+ // Add menu to stack when opening on touch devices from custom template
1711
+ if (this._clientOutput.device === 'touch' && isInCustomTemplate) {
1688
1712
  this._menuStack.push(this.menu);
1713
+ this.menu._usesMenuStack = true;
1689
1714
  }
1690
1715
  //#endregion --- Touch device customizations ---
1691
1716
  this._setIsMenuOpen(true);
@@ -1949,13 +1974,20 @@ class GraniteMenuTriggerForDirective {
1949
1974
  // root menu only.
1950
1975
  // For touch devices, handle outside clicks for all menus (not just root)
1951
1976
  // but only close the topmost menu in the stack
1952
- const outsideClick = this._clientOutput.device === 'touch'
1953
- ? fromEvent(this._document, 'click').pipe(filter((e) => e.target !== this._element.nativeElement &&
1954
- e.target.closest('.granite-menu') === null &&
1955
- this._menuStack.isTop(this.menu)), filter(() => !this.menu._isAnimating))
1977
+ const isTouchCustom = this._clientOutput.device === 'touch' && this._isInCustomTemplate;
1978
+ const outsideClick = isTouchCustom
1979
+ ? fromEvent(this._document, 'click').pipe(filter((e) => {
1980
+ const el = e.target;
1981
+ return (el !== this._element.nativeElement &&
1982
+ el.closest('.granite-menu') === null &&
1983
+ this._menuStack.isTop(this.menu));
1984
+ }), filter(() => !this.menu._isAnimating))
1956
1985
  : !this._parentMenu
1957
- ? fromEvent(this._document, 'click').pipe(filter((e) => e.target !== this._element.nativeElement &&
1958
- e.target.closest('.granite-menu') === null), filter(() => !this.menu._isAnimating))
1986
+ ? fromEvent(this._document, 'click').pipe(filter((e) => {
1987
+ const el = e.target;
1988
+ return (el !== this._element.nativeElement &&
1989
+ el.closest('.granite-menu') === null);
1990
+ }), filter(() => !this.menu._isAnimating), filter(() => !this.menu.customTemplate || this._menuStack.size() === 0))
1959
1991
  : of(null);
1960
1992
  return merge(detachments, hover, parentClose, outsideClick).pipe(filter((event) => event !== null));
1961
1993
  }