Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions packages/button/src/ButtonBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,6 @@ export class ButtonBase extends ObserveSlotText(LikeAnchor(Focusable), '', [
if (!this.hasAttribute('tabindex')) {
this.setAttribute('tabindex', '0');
}
if (changed.has('label')) {
if (this.label) {
this.setAttribute('aria-label', this.label);
} else {
this.removeAttribute('aria-label');
}
}
this.manageAnchor();
this.addEventListener('keydown', this.handleKeydown);
this.addEventListener('keypress', this.handleKeypress);
Expand All @@ -254,6 +247,14 @@ export class ButtonBase extends ObserveSlotText(LikeAnchor(Focusable), '', [

// Set up focus delegation
this.anchorElement.addEventListener('focus', this.proxyFocus);

if (changed.has('label')) {
if (this.label) {
this.setAttribute('aria-label', this.label);
} else {
this.removeAttribute('aria-label');
}
}
}
}
protected override update(changes: PropertyValues): void {
Expand Down
1 change: 0 additions & 1 deletion packages/card/src/spectrum-card.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ governing permissions and limitations under the License.
:host {
box-sizing: border-box;
min-inline-size: var(--mod-card-minimum-width, var(--spectrum-card-minimum-width));
block-size: 100%;
border: var(--spectrum-card-border-width) solid transparent;
border-radius: var(--spectrum-card-corner-radius);
border-color: var(--mod-card-border-color, var(--spectrum-card-border-color));
Expand Down
32 changes: 5 additions & 27 deletions packages/dialog/src/DialogBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import '@spectrum-web-components/underlay/sp-underlay.js';
import '@spectrum-web-components/button/sp-button.js';

// Leveraged in build systems that use aliasing to prevent multiple registrations: https://github.com/adobe/spectrum-web-components/pull/3225
import '@spectrum-web-components/dialog/sp-dialog.js';
// Get around lint error by importing locally for now. Not required for actual change.
import '../sp-dialog.js';
import modalWrapperStyles from '@spectrum-web-components/modal/src/modal-wrapper.css.js';
import modalStyles from '@spectrum-web-components/modal/src/modal.css.js';
import { Dialog } from './Dialog.js';
Expand Down Expand Up @@ -155,41 +156,18 @@ export class DialogBase extends FocusVisiblePolyfillMixin(SpectrumElement) {
this.handleTransitionEvent(event);
}

private get hasTransitionDuration(): boolean {
const modal = this.shadowRoot.querySelector('.modal') as HTMLElement;

const modalTransitionDurations =
window.getComputedStyle(modal).transitionDuration;
for (const duration of modalTransitionDurations.split(','))
if (parseFloat(duration) > 0) return true;

const underlay = this.shadowRoot.querySelector(
'sp-underlay'
) as HTMLElement;

if (underlay) {
const underlayTransitionDurations =
window.getComputedStyle(underlay).transitionDuration;
for (const duration of underlayTransitionDurations.split(','))
if (parseFloat(duration) > 0) return true;
}

return false;
}

protected override update(changes: PropertyValues<this>): void {
if (changes.has('open') && changes.get('open') !== undefined) {
const hasTransitionDuration = this.hasTransitionDuration;
this.animating = true;
this.transitionPromise = new Promise((res) => {
this.resolveTransitionPromise = () => {
this.animating = false;
if (!this.open && hasTransitionDuration)
this.dispatchClosed();
res();
};
});
if (!this.open && !hasTransitionDuration) this.dispatchClosed();
if (!this.open) {
this.dispatchClosed();
}
}
super.update(changes);
}
Expand Down
11 changes: 9 additions & 2 deletions packages/menu/src/MenuItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,20 +472,27 @@ export class MenuItem extends LikeAnchor(
super.firstUpdated(changes);
this.setAttribute('tabindex', '-1');
this.addEventListener('keydown', this.handleKeydown);
this.addEventListener('mouseover', this.handleMouseover);
this.addEventListener('mouseenter', this.handleMouseenter);
this.addEventListener('mouseleave', this.handleMouseleave);
this.addEventListener('pointerdown', this.handlePointerdown);
this.addEventListener('pointerenter', this.closeOverlaysForRoot);
if (!this.hasAttribute('id')) {
this.id = `sp-menu-item-${randomID()}`;
}
}
handleMouseover(event: MouseEvent): void {
handleMouseenter(event: MouseEvent): void {
const target = event.target as HTMLElement;
if (target === this) {
this.focus();
this.focused = false;
}
}
handleMouseleave(event: MouseEvent): void {
const target = event.target as HTMLElement;
if (target === this) {
this.blur();
}
}
/**
* forward key info from keydown event to parent menu
*/
Expand Down
1 change: 1 addition & 0 deletions packages/number-field/src/NumberField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,7 @@ export class NumberField extends TextfieldBase {
}

protected override updated(changes: PropertyValues<this>): void {
super.updated(changes);
if (!this.inputElement || !this.isConnected) {
// Prevent race conditions if inputElement is removed from DOM while a queued update is still running.
return;
Expand Down
20 changes: 10 additions & 10 deletions packages/overlay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ Some Overlays will always be passed focus (e.g. modal or page Overlays). When th

The `trigger` option accepts an `HTMLElement` or a `VirtualTrigger` from which to position the Overlay.

- You can import the `VirtualTrigger` class from the overlay package to create a virtual trigger that can be used to position an Overlay. This is useful when you want to position an Overlay relative to a point on the screen that is not an element in the DOM, like the mouse cursor.
- You can import the `VirtualTrigger` class from the overlay package to create a virtual trigger that can be used to position an Overlay. This is useful when you want to position an Overlay relative to a point on the screen that is not an element in the DOM, like the mouse cursor.

The `type` of an Overlay outlines a number of things about the interaction model within which it works:

Expand Down Expand Up @@ -408,8 +408,8 @@ The `overlay` value in this case will hold a reference to the actual `<sp-overla

"Fully" in this context means that all CSS transitions that have dispatched `transitionrun` events on the direct children of the `<sp-overlay>` element have successfully dispatched their `transitionend` or `transitioncancel` event. Keep in mind the following:

- `transition*` events bubble; this means that while transition events on light DOM content of those direct children will be heard, those events will not be taken into account
- `transition*` events are not composed; this means that transition events on shadow DOM content of the direct children will not propagate to a level in the DOM where they can be heard
- `transition*` events bubble; this means that while transition events on light DOM content of those direct children will be heard, those events will not be taken into account
- `transition*` events are not composed; this means that transition events on shadow DOM content of the direct children will not propagate to a level in the DOM where they can be heard

This means that in both cases, if the transition is meant to be a part of the opening or closing of the overlay in question you will need to redispatch the `transitionrun`, `transitionend`, and `transitioncancel` events from that transition from the closest direct child of the `<sp-overlay>`.

Expand Down Expand Up @@ -772,9 +772,9 @@ When nesting multiple overlays, it is important to ensure that the nested overla

The overlay manages focus based on its type:

- For `modal` and `page` types, focus is always trapped within the overlay
- For `auto` and `manual` types, focus behavior is controlled by the `receives-focus` attribute
- For `hint` type, focus remains on the trigger element
- For `modal` and `page` types, focus is always trapped within the overlay
- For `auto` and `manual` types, focus behavior is controlled by the `receives-focus` attribute
- For `hint` type, focus remains on the trigger element

Example of proper focus management:

Expand Down Expand Up @@ -840,10 +840,10 @@ Example of proper focus management:

#### Screen reader considerations

- Use `aria-haspopup` on trigger elements to indicate the type of overlay
- Provide descriptive labels using `aria-label` or `aria-labelledby`
- Use proper heading structure within overlays
- Ensure error messages are announced using `aria-live`
- Use `aria-haspopup` on trigger elements to indicate the type of overlay
- Provide descriptive labels using `aria-label` or `aria-labelledby`
- Use proper heading structure within overlays
- Ensure error messages are announced using `aria-live`

Example of a tooltip with proper screen reader support:

Expand Down
3 changes: 1 addition & 2 deletions packages/overlay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,7 @@
"@spectrum-web-components/base": "1.7.0",
"@spectrum-web-components/reactive-controllers": "1.7.0",
"@spectrum-web-components/shared": "1.7.0",
"@spectrum-web-components/theme": "1.7.0",
"focus-trap": "^7.6.4"
"@spectrum-web-components/theme": "1.7.0"
},
"types": "./src/index.d.ts",
"customElements": "custom-elements.json",
Expand Down
5 changes: 5 additions & 0 deletions packages/overlay/src/AbstractOverlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ export class AbstractOverlay extends SpectrumElement {
return;
}
/* c8 ignore next 3 */
protected async manageDialogOpen(): Promise<void> {
return;
}
/* c8 ignore next 3 */
protected async managePopoverOpen(): Promise<void> {
return;
}
Expand Down Expand Up @@ -240,6 +244,7 @@ export class AbstractOverlay extends SpectrumElement {
content?: HTMLElement,
optionsV1?: OverlayOptionsV1
): Promise<Overlay | (() => void)> {
/* eslint-disable */
await import('@spectrum-web-components/overlay/sp-overlay.js');
const v2 = arguments.length === 2;
const overlayContent = content || triggerOrContent;
Expand Down
13 changes: 13 additions & 0 deletions packages/overlay/src/HoverController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
InteractionController,
InteractionTypes,
lastInteractionType,
SAFARI_FOCUS_RING_CLASS,
} from './InteractionController.js';

const HOVER_DELAY = 300;
Expand All @@ -36,6 +37,7 @@ export class HoverController extends InteractionController {
handleKeyup(event: KeyboardEvent): void {
if (event.code === 'Tab' || event.code === 'Escape') {
this.open = true;
this.removeSafariFocusRingClass();
}
}

Expand All @@ -48,14 +50,17 @@ export class HoverController extends InteractionController {
isWebKit() &&
this.target[lastInteractionType] === InteractionTypes.click
) {
this.target.classList.add(SAFARI_FOCUS_RING_CLASS);
return;
}

this.open = true;
this.focusedin = true;
this.removeSafariFocusRingClass();
}

handleTargetFocusout(): void {
this.removeSafariFocusRingClass();
this.focusedin = false;
if (this.pointerentered) return;
this.open = false;
Expand Down Expand Up @@ -199,4 +204,12 @@ export class HoverController extends InteractionController {
{ signal }
);
}

private removeSafariFocusRingClass(): void {
if (
isWebKit() &&
this.target.classList.contains(SAFARI_FOCUS_RING_CLASS)
)
this.target.classList.remove(SAFARI_FOCUS_RING_CLASS);
}
}
2 changes: 2 additions & 0 deletions packages/overlay/src/InteractionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum InteractionTypes {
}

export const lastInteractionType = Symbol('lastInteractionType');
export const SAFARI_FOCUS_RING_CLASS = 'remove-focus-ring-safari-hack';

export type ControllerOptions = {
overlay?: AbstractOverlay;
Expand Down Expand Up @@ -74,6 +75,7 @@ export class InteractionController implements ReactiveController {
this.overlay.open = true;
this.target[lastInteractionType] = this.type;
});
/* eslint-disable */
import('@spectrum-web-components/overlay/sp-overlay.js');
}

Expand Down
Loading