Skip to content
Closed
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
4 changes: 4 additions & 0 deletions packages/breadcrumbs/spec/web-component-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,7 @@ Two shadow slots with the overflow in shadow DOM between them: `<slot name="root
**Q: Why does `i18n.moreItems` default to `'More items'` rather than an empty string?**

The `aria-label` on the overflow button must always be present — screen readers announce the button as just "button" otherwise. Defaulting to an empty string leaves the application responsible for setting a meaningful label before the component is reachable to assistive tech, which is a setup step that's easy to miss. Providing `'More items'` as the default matches the convention for other Vaadin components with auto-generated control labels (Menu Bar, Combo Box clear button) and keeps the unlabeled state out of reach. Applications still localize via `i18n = { moreItems: '…' }` exactly as before.

**Q: Why does the overlay's focus indicator only appear on keyboard opens?**

The container calls `_focus(idx, { focusVisible: isKeyboardActive() })` when moving focus into the open overlay, matching the convention used by `<vaadin-context-menu>`, `<vaadin-menu-bar>`, `<vaadin-select>`, `<vaadin-app-layout>`, `<vaadin-upload>`, and `<vaadin-master-detail-layout>`. A mouse-driven open is a continuation of a pointer interaction — a focus ring appearing under the user's cursor is visual noise. A keyboard-driven open is the user's only signal that focus has moved into a transient popup, so the ring is essential. `isKeyboardActive()` from `@vaadin/a11y-base` reads the cross-component flag set on every keydown and cleared on every mousedown, so the trigger detection is automatic and consistent with the rest of the library.
3 changes: 2 additions & 1 deletion packages/breadcrumbs/src/vaadin-breadcrumbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import './vaadin-breadcrumbs-item.js';
import './vaadin-breadcrumbs-overlay.js';
import { html, LitElement } from 'lit';
import { isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js';
import { KeyboardDirectionMixin } from '@vaadin/a11y-base/src/keyboard-direction-mixin.js';
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
Expand Down Expand Up @@ -302,7 +303,7 @@ class Breadcrumbs extends KeyboardDirectionMixin(
// Focus first non-disabled overlay item
const idx = this._getFocusableIndex();
if (idx >= 0) {
this._focus(idx);
this._focus(idx, { focusVisible: isKeyboardActive() });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ snapshots["vaadin-breadcrumbs overflow opened"] =
Home
</vaadin-breadcrumbs-item>
<vaadin-breadcrumbs-item
focus-ring=""
focused=""
path="/docs"
role="listitem"
Expand Down
30 changes: 29 additions & 1 deletion packages/breadcrumbs/test/overflow.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from '@vaadin/chai-plugins';
import { sendKeys } from '@vaadin/test-runner-commands';
import { fixtureSync, nextRender, nextResize, oneEvent } from '@vaadin/testing-helpers';
import { fixtureSync, mousedown, nextRender, nextResize, oneEvent } from '@vaadin/testing-helpers';
import '../vaadin-breadcrumbs.js';
import { getDeepActiveElement } from '@vaadin/a11y-base/src/focus-utils.js';

Expand Down Expand Up @@ -187,6 +187,34 @@ describe('overflow', () => {
const firstItem = breadcrumbs.querySelector('vaadin-breadcrumbs-item[slot="overlay"]');
expectFocusedItem(firstItem);
});

it('should not set focus-ring on the first overlay item when opening via mouse', async () => {
mousedown(button);
button.click();
await oneEvent(overlay, 'vaadin-overlay-open');

const firstItem = breadcrumbs.querySelector('vaadin-breadcrumbs-item[slot="overlay"]');
expectFocusedItem(firstItem);
expect(firstItem.hasAttribute('focus-ring')).to.be.false;
});

it('should set focus-ring on the first overlay item when opening via Enter', async () => {
button.focus();
await sendKeys({ press: 'Enter' });
await nextRender();

const firstItem = breadcrumbs.querySelector('vaadin-breadcrumbs-item[slot="overlay"]');
expect(firstItem.hasAttribute('focus-ring')).to.be.true;
});

it('should set focus-ring on the first overlay item when opening via Space', async () => {
button.focus();
await sendKeys({ press: 'Space' });
await nextRender();

const firstItem = breadcrumbs.querySelector('vaadin-breadcrumbs-item[slot="overlay"]');
expect(firstItem.hasAttribute('focus-ring')).to.be.true;
});
});

describe('closing', () => {
Expand Down
Loading