From e2b7327d98fccfbb3769ed9332f4ea563e205443 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Fri, 1 Dec 2023 10:02:46 -0500 Subject: [PATCH] Improve tooltip accessibility (#1749) * always close on escape, even when not focused; #1734 * use fallbacks instead of defaults * add words * add safe trapezoids / hover bridge; fixes #1734 * oh, webkit * remove unused import * cleanup just in case --- cspell.json | 1 + docs/pages/components/popup.md | 134 +++++++++++++++++++ docs/pages/resources/changelog.md | 5 + src/components/menu-item/menu-item.styles.ts | 15 +-- src/components/popup/popup.component.ts | 95 +++++++++++++ src/components/popup/popup.styles.ts | 20 +++ src/components/tooltip/tooltip.component.ts | 16 ++- src/components/tooltip/tooltip.styles.ts | 2 +- src/components/tooltip/tooltip.test.ts | 8 +- 9 files changed, 274 insertions(+), 22 deletions(-) diff --git a/cspell.json b/cspell.json index 78a51f27..e4bf8c63 100644 --- a/cspell.json +++ b/cspell.json @@ -100,6 +100,7 @@ "monospace", "mousedown", "mousemove", + "mouseout", "mouseup", "multiselectable", "nextjs", diff --git a/docs/pages/components/popup.md b/docs/pages/components/popup.md index 16812090..8903918f 100644 --- a/docs/pages/components/popup.md +++ b/docs/pages/components/popup.md @@ -1530,6 +1530,140 @@ const App = () => { }; ``` +### Hover Bridge + +When a gap exists between the anchor and the popup element, this option will add a "hover bridge" that fills the gap using an invisible element. This makes listening for events such as `mouseover` and `mouseout` more sane because the pointer never technically leaves the element. The hover bridge will only be drawn when the popover is active. For demonstration purposes, the bridge in this example is shown in orange. + +```html:preview + + + + + +``` + +```jsx:react +import { useState } from 'react'; +import SlPopup from '@shoelace-style/shoelace/dist/react/popup'; +import SlRange from '@shoelace-style/shoelace/dist/react/range'; +import SlSwitch from '@shoelace-style/shoelace/dist/react/switch'; + +const css = ` + .popup-hover-bridge span[slot='anchor'] { + display: inline-block; + width: 150px; + height: 150px; + border: dashed 2px var(--sl-color-neutral-600); + margin: 50px; + } + + .popup-hover-bridge .box { + width: 100px; + height: 50px; + background: var(--sl-color-primary-600); + border-radius: var(--sl-border-radius-medium); + } + + .popup-hover-bridge sl-range { + max-width: 260px; + margin-top: .5rem; + } + + .popup-hover-bridge sl-popup::part(hover-bridge) { + background: tomato; + opacity: .5; + } +`; + +const App = () => { + const [hoverBridge, setHoverBridge] = useState(true); + const [distance, setDistance] = useState(10); + const [skidding, setSkidding] = useState(0); + + return ( + <> +