kopia lustrzana https://github.com/shoelace-style/shoelace
				
				
				
			Add safe triangle for submenu selection (#1600)
* add safe triangle; fixes #1550 * make z-index relative to submenu * refactor submenu propertiespull/1599/head^2
							rodzic
							
								
									a697b356ac
								
							
						
					
					
						commit
						236fbd7109
					
				|  | @ -18,6 +18,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti | |||
| - Fixed a bug [in the localize dependency](https://github.com/shoelace-style/localize/issues/20) that caused underscores in language codes to throw a `RangeError` | ||||
| - Fixed a bug in the focus trapping utility used by modals that caused unexpected focus behavior. [#1583] | ||||
| - Fixed a bug in `<sl-copy-button>` that prevented exported tooltip parts from being styled [#1586] | ||||
| - Improved submenu selection by implementing the [safe triangle](https://www.smashingmagazine.com/2023/08/better-context-menus-safe-triangles/) method [#1550] | ||||
| - Updated `@shoelace-style/localize` to 3.1.0 | ||||
| 
 | ||||
| ## 2.9.0 | ||||
|  |  | |||
|  | @ -7,6 +7,14 @@ export default css` | |||
|   :host { | ||||
|     --submenu-offset: -2px; | ||||
| 
 | ||||
|     /* Private */ | ||||
|     --safe-triangle-cursor-x: 0; | ||||
|     --safe-triangle-cursor-y: 0; | ||||
|     --safe-triangle-submenu-start-x: 0; | ||||
|     --safe-triangle-submenu-start-y: 0; | ||||
|     --safe-triangle-submenu-end-x: 0; | ||||
|     --safe-triangle-submenu-end-y: 0; | ||||
| 
 | ||||
|     display: block; | ||||
|   } | ||||
| 
 | ||||
|  | @ -64,6 +72,22 @@ export default css` | |||
|     margin-inline-start: var(--sl-spacing-x-small); | ||||
|   } | ||||
| 
 | ||||
|   /* Safe triangle */ | ||||
|   .menu-item--submenu-expanded::after { | ||||
|     content: ''; | ||||
|     position: fixed; | ||||
|     z-index: calc(var(--sl-z-index-dropdown) - 1); | ||||
|     top: 0; | ||||
|     right: 0; | ||||
|     bottom: 0; | ||||
|     left: 0; | ||||
|     clip-path: polygon( | ||||
|       var(--safe-triangle-cursor-x) var(--safe-triangle-cursor-y), | ||||
|       var(--safe-triangle-submenu-start-x) var(--safe-triangle-submenu-start-y), | ||||
|       var(--safe-triangle-submenu-end-x) var(--safe-triangle-submenu-end-y) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   :host(:focus-visible) { | ||||
|     outline: none; | ||||
|   } | ||||
|  |  | |||
|  | @ -49,6 +49,7 @@ export class SubmenuController implements ReactiveController { | |||
| 
 | ||||
|   private addListeners() { | ||||
|     if (!this.isConnected) { | ||||
|       this.host.addEventListener('mousemove', this.handleMouseMove); | ||||
|       this.host.addEventListener('mouseover', this.handleMouseOver); | ||||
|       this.host.addEventListener('keydown', this.handleKeyDown); | ||||
|       this.host.addEventListener('click', this.handleClick); | ||||
|  | @ -61,6 +62,7 @@ export class SubmenuController implements ReactiveController { | |||
|     if (!this.isPopupConnected) { | ||||
|       if (this.popupRef.value) { | ||||
|         this.popupRef.value.addEventListener('mouseover', this.handlePopupMouseover); | ||||
|         this.popupRef.value.addEventListener('sl-reposition', this.handlePopupReposition); | ||||
|         this.isPopupConnected = true; | ||||
|       } | ||||
|     } | ||||
|  | @ -68,6 +70,7 @@ export class SubmenuController implements ReactiveController { | |||
| 
 | ||||
|   private removeListeners() { | ||||
|     if (this.isConnected) { | ||||
|       this.host.removeEventListener('mousemove', this.handleMouseMove); | ||||
|       this.host.removeEventListener('mouseover', this.handleMouseOver); | ||||
|       this.host.removeEventListener('keydown', this.handleKeyDown); | ||||
|       this.host.removeEventListener('click', this.handleClick); | ||||
|  | @ -77,11 +80,18 @@ export class SubmenuController implements ReactiveController { | |||
|     if (this.isPopupConnected) { | ||||
|       if (this.popupRef.value) { | ||||
|         this.popupRef.value.removeEventListener('mouseover', this.handlePopupMouseover); | ||||
|         this.popupRef.value.removeEventListener('sl-reposition', this.handlePopupReposition); | ||||
|         this.isPopupConnected = false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // Set the safe triangle cursor position
 | ||||
|   private handleMouseMove = (event: MouseEvent) => { | ||||
|     this.host.style.setProperty('--safe-triangle-cursor-x', `${event.clientX}px`); | ||||
|     this.host.style.setProperty('--safe-triangle-cursor-y', `${event.clientY}px`); | ||||
|   }; | ||||
| 
 | ||||
|   private handleMouseOver = () => { | ||||
|     if (this.hasSlotController.test('submenu')) { | ||||
|       this.enableSubmenu(); | ||||
|  | @ -188,6 +198,24 @@ export class SubmenuController implements ReactiveController { | |||
|     event.stopPropagation(); | ||||
|   }; | ||||
| 
 | ||||
|   // Set the safe triangle values for the submenu when the position changes
 | ||||
|   private handlePopupReposition = () => { | ||||
|     const submenuSlot: HTMLSlotElement | null = this.host.renderRoot.querySelector("slot[name='submenu']"); | ||||
|     const menu = submenuSlot?.assignedElements({ flatten: true }).filter(el => el.localName === 'sl-menu')[0]; | ||||
|     const isRtl = this.localize.dir() === 'rtl'; | ||||
| 
 | ||||
|     if (!menu) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const { left, top, width, height } = menu.getBoundingClientRect(); | ||||
| 
 | ||||
|     this.host.style.setProperty('--safe-triangle-submenu-start-x', `${isRtl ? left + width : left}px`); | ||||
|     this.host.style.setProperty('--safe-triangle-submenu-start-y', `${top}px`); | ||||
|     this.host.style.setProperty('--safe-triangle-submenu-end-x', `${isRtl ? left + width : left}px`); | ||||
|     this.host.style.setProperty('--safe-triangle-submenu-end-y', `${top + height}px`); | ||||
|   }; | ||||
| 
 | ||||
|   private setSubmenuState(state: boolean) { | ||||
|     if (this.popupRef.value) { | ||||
|       if (this.popupRef.value.active !== state) { | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Cory LaViska
						Cory LaViska