Add safe triangle for submenu selection (#1600)

* add safe triangle; fixes #1550

* make z-index relative to submenu

* refactor submenu properties
pull/1599/head^2
Cory LaViska 2023-10-12 11:03:41 -05:00 zatwierdzone przez GitHub
rodzic a697b356ac
commit 236fbd7109
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 53 dodań i 0 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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) {