diff --git a/components/menu/demo/horizontal.ts b/components/menu/demo/horizontal.ts
index f71481e782f..aee6072e36a 100755
--- a/components/menu/demo/horizontal.ts
+++ b/components/menu/demo/horizontal.ts
@@ -35,6 +35,12 @@ import { NzMenuModule } from 'ng-zorro-antd/menu';
Option 6
+
+
+
- Option 5
diff --git a/components/menu/doc/index.en-US.md b/components/menu/doc/index.en-US.md
index 1883fb9bdef..97ddbdca0a4 100755
--- a/components/menu/doc/index.en-US.md
+++ b/components/menu/doc/index.en-US.md
@@ -70,15 +70,16 @@ You can set the title of `[nz-submenu]` in the following ways.
SubTitle
```
-| Param | Description | Type | Default value |
-| ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------- | -------------- |
-| `[nzPlacement]` | placement of pop menu | `'bottomLeft' \| 'bottomCenter' \| 'bottomRight' \| 'topLeft' \| 'topCenter' \| 'topRight'` | `'bottomLeft'` |
-| `[nzOpen]` | whether sub menu is open or not, double binding | `boolean` | `false` |
-| `[nzDisabled]` | whether sub menu is disabled or not | `boolean` | `false` |
-| `[nzTitle]` | set submenu title | `string \| TemplateRef` | - |
-| `[nzIcon]` | `icon` type in title | `string` | - |
-| `[nzMenuClassName]` | Custom the submenu container's class name | `string` | - |
-| `(nzOpenChange)` | nzOpen callback | `EventEmitter` | - |
+| Param | Description | Type | Default value |
+| -------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------- | -------------- |
+| `[nzPlacement]` | placement of pop menu | `'bottomLeft' \| 'bottomCenter' \| 'bottomRight' \| 'topLeft' \| 'topCenter' \| 'topRight'` | `'bottomLeft'` |
+| `[nzOpen]` | whether sub menu is open or not, double binding | `boolean` | `false` |
+| `[nzDisabled]` | whether sub menu is disabled or not | `boolean` | `false` |
+| `[nzTitle]` | set submenu title | `string \| TemplateRef` | - |
+| `[nzIcon]` | `icon` type in title | `string` | - |
+| `[nzMenuClassName]` | Custom the submenu container's class name | `string` | - |
+| `[nzTriggerSubMenuAction]` | Which action can trigger submenu open/close | `'hover' \| 'click'` | `'hover'` |
+| `(nzOpenChange)` | nzOpen callback | `EventEmitter` | - |
### [nz-menu-group]:standalone
diff --git a/components/menu/doc/index.zh-CN.md b/components/menu/doc/index.zh-CN.md
index 57a4f44ca1f..ebf935a257c 100755
--- a/components/menu/doc/index.zh-CN.md
+++ b/components/menu/doc/index.zh-CN.md
@@ -71,15 +71,16 @@ import { NzMenuModule } from 'ng-zorro-antd/menu';
SubTitle
```
-| 参数 | 说明 | 类型 | 默认值 |
-| ------------------- | -------------------- | ------------------------------------------------------------------------------------------- | -------------- |
-| `[nzPlacement]` | 菜单弹出位置 | `'bottomLeft' \| 'bottomCenter' \| 'bottomRight' \| 'topLeft' \| 'topCenter' \| 'topRight'` | `'bottomLeft'` |
-| `[nzOpen]` | 是否展开,可双向绑定 | `boolean` | `false` |
-| `[nzDisabled]` | 是否禁用 | `boolean` | `false` |
-| `[nzTitle]` | 标题内容 | `string \| TemplateRef` | - |
-| `[nzIcon]` | 标题中 `icon` 类型 | `string` | - |
-| `[nzMenuClassName]` | 自定义子菜单容器类名 | `string` | - |
-| `(nzOpenChange)` | 展开回调 | `EventEmitter` | - |
+| 参数 | 说明 | 类型 | 默认值 |
+| -------------------------- | --------------------------- | ------------------------------------------------------------------------------------------- | -------------- |
+| `[nzPlacement]` | 菜单弹出位置 | `'bottomLeft' \| 'bottomCenter' \| 'bottomRight' \| 'topLeft' \| 'topCenter' \| 'topRight'` | `'bottomLeft'` |
+| `[nzOpen]` | 是否展开,可双向绑定 | `boolean` | `false` |
+| `[nzDisabled]` | 是否禁用 | `boolean` | `false` |
+| `[nzTitle]` | 标题内容 | `string \| TemplateRef` | - |
+| `[nzIcon]` | 标题中 `icon` 类型 | `string` | - |
+| `[nzMenuClassName]` | 自定义子菜单容器类名 | `string` | - |
+| `[nzTriggerSubMenuAction]` | SubMenu 展开/关闭的触发行为 | `'hover' \| 'click'` | `'hover'` |
+| `(nzOpenChange)` | 展开回调 | `EventEmitter` | - |
### [nz-menu-group]:standalone
diff --git a/components/menu/menu.spec.ts b/components/menu/menu.spec.ts
index 917dbb9c5c2..51f25654c7f 100644
--- a/components/menu/menu.spec.ts
+++ b/components/menu/menu.spec.ts
@@ -9,6 +9,7 @@ import { NzButtonModule } from 'ng-zorro-antd/button';
import { dispatchFakeEvent } from 'ng-zorro-antd/core/testing';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { provideNzIconsTesting } from 'ng-zorro-antd/icon/testing';
+import { NzSubmenuTrigger } from 'ng-zorro-antd/menu/menu.types';
import { NzMenuItemComponent } from './menu-item.component';
import { NzMenuDirective } from './menu.directive';
@@ -271,6 +272,42 @@ describe('menu', () => {
expect(mouseenterCallback).toHaveBeenCalledWith(true);
expect(mouseenterCallback).toHaveBeenCalledTimes(1);
});
+ it('should have "hover" as default trigger', () => {
+ fixture.detectChanges();
+ const mouseenterCallback = jasmine.createSpy('mouseenter callback');
+ const subs = testComponent.subs.toArray();
+ const title = submenu.nativeElement.querySelector('.ant-menu-submenu-title');
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (subs[0].nzSubmenuService as any).isMouseEnterTitleOrOverlay$.subscribe(mouseenterCallback);
+ dispatchFakeEvent(title, 'mouseenter');
+ fixture.detectChanges();
+ expect(mouseenterCallback).toHaveBeenCalledWith(true);
+ expect(mouseenterCallback).toHaveBeenCalledTimes(1);
+ });
+ it('should have not open with mouse hover if trigger is set to "click"', () => {
+ testComponent.nzTriggerSubMenuAction = 'click';
+ fixture.detectChanges();
+ const mouseenterCallback = jasmine.createSpy('mouseenter callback');
+ const subs = testComponent.subs.toArray();
+ const title = submenu.nativeElement.querySelector('.ant-menu-submenu-title');
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (subs[0].nzSubmenuService as any).isMouseEnterTitleOrOverlay$.subscribe(mouseenterCallback);
+ dispatchFakeEvent(title, 'mouseenter');
+ fixture.detectChanges();
+ expect(mouseenterCallback).toHaveBeenCalledTimes(0);
+ });
+ it('should open with mouse click if trigger is set to "click"', () => {
+ testComponent.nzTriggerSubMenuAction = 'click';
+ fixture.detectChanges();
+ const mouseenterCallback = jasmine.createSpy('mouseenter callback');
+ const subs = testComponent.subs.toArray();
+ const title = submenu.nativeElement.querySelector('.ant-menu-submenu-title');
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (subs[0].nzSubmenuService as any).isMouseEnterTitleOrOverlay$.subscribe(mouseenterCallback);
+ title.click();
+ fixture.detectChanges();
+ expect(mouseenterCallback).toHaveBeenCalledTimes(1);
+ });
it('should submenu mouseleave work', () => {
fixture.detectChanges();
const mouseleaveCallback = jasmine.createSpy('mouseleave callback');
@@ -524,7 +561,13 @@ describe('menu', () => {
imports: [NzIconModule, NzMenuModule],
template: `
- -
+
-
Navigation Three - Submenu
@@ -567,6 +610,7 @@ export class NzTestMenuHorizontalComponent {
width = 200;
open = false;
disabled = false;
+ nzTriggerSubMenuAction: NzSubmenuTrigger = 'hover';
@ViewChildren(NzSubMenuComponent) subs!: QueryList;
@ViewChild('menuitem', { static: false, read: ElementRef }) menuitem!: ElementRef;
@ViewChild('menuitem1', { static: false, read: ElementRef }) menuitem1!: ElementRef;
diff --git a/components/menu/menu.types.ts b/components/menu/menu.types.ts
index 6fc0fe3eabb..017fbc6814d 100644
--- a/components/menu/menu.types.ts
+++ b/components/menu/menu.types.ts
@@ -5,3 +5,4 @@
export type NzMenuModeType = 'vertical' | 'horizontal' | 'inline';
export type NzMenuThemeType = 'light' | 'dark';
+export type NzSubmenuTrigger = 'hover' | 'click';
diff --git a/components/menu/submenu-non-inline-child.component.ts b/components/menu/submenu-non-inline-child.component.ts
index 2fe0c51fc36..49d671f98be 100644
--- a/components/menu/submenu-non-inline-child.component.ts
+++ b/components/menu/submenu-non-inline-child.component.ts
@@ -24,7 +24,7 @@ import { takeUntil } from 'rxjs/operators';
import { slideMotion, zoomBigMotion } from 'ng-zorro-antd/core/animation';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
-import { NzMenuModeType, NzMenuThemeType } from './menu.types';
+import { NzMenuModeType, NzMenuThemeType, NzSubmenuTrigger } from './menu.types';
@Component({
selector: '[nz-submenu-none-inline-child]',
@@ -68,6 +68,7 @@ export class NzSubmenuNoneInlineChildComponent implements OnDestroy, OnInit, OnC
@Input() templateOutlet: TemplateRef | null = null;
@Input() isMenuInsideDropDown = false;
@Input() mode: NzMenuModeType = 'vertical';
+ @Input() nzTriggerSubMenuAction: NzSubmenuTrigger = 'hover';
@Input() position = 'right';
@Input() nzDisabled = false;
@Input() nzOpen = false;
@@ -76,7 +77,7 @@ export class NzSubmenuNoneInlineChildComponent implements OnDestroy, OnInit, OnC
constructor(private directionality: Directionality) {}
setMouseState(state: boolean): void {
- if (!this.nzDisabled) {
+ if (!this.nzDisabled && this.nzTriggerSubMenuAction === 'hover') {
this.subMenuMouseState.next(state);
}
}
diff --git a/components/menu/submenu-title.component.ts b/components/menu/submenu-title.component.ts
index 645ce755ffd..cd0a7ce01ff 100644
--- a/components/menu/submenu-title.component.ts
+++ b/components/menu/submenu-title.component.ts
@@ -22,7 +22,7 @@ import { takeUntil } from 'rxjs/operators';
import { NzOutletModule } from 'ng-zorro-antd/core/outlet';
import { NzIconModule } from 'ng-zorro-antd/icon';
-import { NzMenuModeType } from './menu.types';
+import { NzMenuModeType, NzSubmenuTrigger } from './menu.types';
@Component({
selector: '[nz-submenu-title]',
@@ -71,6 +71,7 @@ export class NzSubMenuTitleComponent implements OnDestroy, OnInit {
@Input() nzDisabled = false;
@Input() paddingLeft: number | null = null;
@Input() mode: NzMenuModeType = 'vertical';
+ @Input() nzTriggerSubMenuAction: NzSubmenuTrigger = 'hover';
@Output() readonly toggleSubMenu = new EventEmitter();
@Output() readonly subMenuMouseState = new EventEmitter();
@@ -95,12 +96,13 @@ export class NzSubMenuTitleComponent implements OnDestroy, OnInit {
}
setMouseState(state: boolean): void {
- if (!this.nzDisabled) {
+ if (!this.nzDisabled && this.nzTriggerSubMenuAction === 'hover') {
this.subMenuMouseState.next(state);
}
}
clickTitle(): void {
- if (this.mode === 'inline' && !this.nzDisabled) {
+ if ((this.mode === 'inline' || this.nzTriggerSubMenuAction === 'click') && !this.nzDisabled) {
+ this.subMenuMouseState.next(true);
this.toggleSubMenu.emit();
}
}
diff --git a/components/menu/submenu.component.ts b/components/menu/submenu.component.ts
index 96dc89304ad..f6ef969c7b6 100644
--- a/components/menu/submenu.component.ts
+++ b/components/menu/submenu.component.ts
@@ -37,7 +37,7 @@ import { getPlacementName, POSITION_MAP, POSITION_TYPE_HORIZONTAL } from 'ng-zor
import { NzMenuItemComponent } from './menu-item.component';
import { MenuService } from './menu.service';
import { NzIsMenuInsideDropDownToken } from './menu.token';
-import { NzMenuModeType, NzMenuThemeType } from './menu.types';
+import { NzMenuModeType, NzMenuThemeType, NzSubmenuTrigger } from './menu.types';
import { NzSubmenuInlineChildComponent } from './submenu-inline-child.component';
import { NzSubmenuNoneInlineChildComponent } from './submenu-non-inline-child.component';
import { NzSubMenuTitleComponent } from './submenu-title.component';
@@ -76,6 +76,7 @@ const listOfHorizontalPositions = [
[nzDisabled]="nzDisabled"
[isMenuInsideDropDown]="isMenuInsideDropDown"
[paddingLeft]="nzPaddingLeft || inlinePaddingLeft"
+ [nzTriggerSubMenuAction]="nzTriggerSubMenuAction"
(subMenuMouseState)="setMouseEnterState($event)"
(toggleSubMenu)="toggleSubMenu()"
>
@@ -102,6 +103,7 @@ const listOfHorizontalPositions = [
[cdkConnectedOverlayWidth]="triggerWidth!"
[cdkConnectedOverlayOpen]="nzOpen"
[cdkConnectedOverlayTransformOriginOn]="'.ant-menu-submenu'"
+ (overlayOutsideClick)="setMouseEnterState(false)"
>
| null = null;
@Input() nzIcon: string | null = null;
+ @Input() nzTriggerSubMenuAction: NzSubmenuTrigger = 'hover';
@Input({ transform: booleanAttribute }) nzOpen = false;
@Input({ transform: booleanAttribute }) nzDisabled = false;
@Input() nzPlacement: POSITION_TYPE_HORIZONTAL = 'bottomLeft';