diff --git a/packages/dashboard/src/keyboard-controller.js b/packages/dashboard/src/keyboard-controller.js index 44af126ae2..63137858ce 100644 --- a/packages/dashboard/src/keyboard-controller.js +++ b/packages/dashboard/src/keyboard-controller.js @@ -18,7 +18,6 @@ export class KeyboardController { this.host = host; host.addEventListener('focusout', (e) => this.__focusout(e)); - host.addEventListener('focusin', (e) => this.__focusin(e)); host.addEventListener('keydown', (e) => this.__keydown(e)); } @@ -36,13 +35,6 @@ export class KeyboardController { } } - /** @private */ - __focusin(e) { - if (e.target === this.host) { - this.host.__focused = true; - } - } - /** @private */ __keydown(e) { if (e.metaKey || e.ctrlKey || !this.host.__selected) { diff --git a/packages/dashboard/src/vaadin-dashboard-item-mixin.js b/packages/dashboard/src/vaadin-dashboard-item-mixin.js index 795395e098..4fe587e69d 100644 --- a/packages/dashboard/src/vaadin-dashboard-item-mixin.js +++ b/packages/dashboard/src/vaadin-dashboard-item-mixin.js @@ -132,8 +132,19 @@ export const DashboardItemMixin = (superClass) => aria-label=${this.__i18n[i18nSelectTitleForEditingProperty]} aria-describedby="title" aria-pressed="${!!this.__selected}" + @focus="${() => { + this.__focused = true; + }}" + @blur="${() => { + this.__focused = false; + }}" @click="${() => { - this.__selected = true; + const wasSelected = this.__selected; + this.__selected = !wasSelected; + this.__focused = wasSelected; + if (this.__selected) { + this.$['drag-handle'].focus(); + } }}" > `; diff --git a/packages/dashboard/src/vaadin-dashboard-section.js b/packages/dashboard/src/vaadin-dashboard-section.js index 90442f44df..c4410a3267 100644 --- a/packages/dashboard/src/vaadin-dashboard-section.js +++ b/packages/dashboard/src/vaadin-dashboard-section.js @@ -179,9 +179,11 @@ class DashboardSection extends DashboardItemMixin(ElementMixin(ThemableMixin(Pol /** @protected */ render() { return html` - ${this.__renderFocusButton('selectSection')} ${this.__renderMoveControls()} + ${this.__renderMoveControls()}
+ ${this.__renderFocusButton('selectSection')} +
${this.__renderDragHandle()}

${this.sectionTitle}

diff --git a/packages/dashboard/src/vaadin-dashboard-widget.js b/packages/dashboard/src/vaadin-dashboard-widget.js index 28e3afbb37..8a53ebd88e 100644 --- a/packages/dashboard/src/vaadin-dashboard-widget.js +++ b/packages/dashboard/src/vaadin-dashboard-widget.js @@ -209,9 +209,11 @@ class DashboardWidget extends DashboardItemMixin(ElementMixin(ThemableMixin(Poly /** @protected */ render() { return html` - ${this.__renderFocusButton('selectWidget')} ${this.__renderMoveControls()} ${this.__renderResizeControls()} + ${this.__renderMoveControls()} ${this.__renderResizeControls()}
+ ${this.__renderFocusButton('selectWidget')} +
${this.__renderDragHandle()} ${this.__nestedHeadingLevel diff --git a/packages/dashboard/src/vaadin-dashboard.js b/packages/dashboard/src/vaadin-dashboard.js index 67514154c8..46c4baf398 100644 --- a/packages/dashboard/src/vaadin-dashboard.js +++ b/packages/dashboard/src/vaadin-dashboard.js @@ -259,7 +259,7 @@ class Dashboard extends DashboardLayoutMixin(ElementMixin(ThemableMixin(PolylitM let wrappers = [...hostElement.children].filter((el) => el.localName === WRAPPER_LOCAL_NAME); let previousWrapper = null; - const focusedWrapper = wrappers.find((wrapper) => wrapper.querySelector('[focused]')); + const focusedWrapper = wrappers.find((wrapper) => wrapper.querySelector(':focus')); const focusedWrapperWillBeRemoved = focusedWrapper && !this.__isActiveWrapper(focusedWrapper); const wrapperClosestToRemovedFocused = focusedWrapperWillBeRemoved && this.__getClosestActiveWrapper(focusedWrapper); @@ -320,7 +320,7 @@ class Dashboard extends DashboardLayoutMixin(ElementMixin(ThemableMixin(PolylitM this.__focusWrapperContent(wrapperClosestToRemovedFocused || this.querySelector(WRAPPER_LOCAL_NAME)); } - const focusedItem = this.querySelector('[focused]'); + const focusedItem = this.querySelector(':focus'); if (focusedItem && this.__outsideViewport(focusedItem)) { // If the focused wrapper is not in the viewport, scroll it into view focusedItem.scrollIntoView(); diff --git a/packages/dashboard/test/dashboard-keyboard.test.ts b/packages/dashboard/test/dashboard-keyboard.test.ts index bd865511c4..112b21efd5 100644 --- a/packages/dashboard/test/dashboard-keyboard.test.ts +++ b/packages/dashboard/test/dashboard-keyboard.test.ts @@ -79,11 +79,29 @@ describe('dashboard - keyboard interaction', () => { const widget = getElementFromCell(dashboard, 0, 0)!; await sendKeys({ press: 'Tab' }); await sendKeys({ press: 'Space' }); - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; expect(dashboard.hasAttribute('item-selected')).to.be.true; }); + it('should unselect the widget on focus button click', async () => { + const widget = getElementFromCell(dashboard, 0, 0)!; + await sendKeys({ press: 'Tab' }); + await sendKeys({ press: 'Space' }); + + // Focus the focus-button with shift + tab + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); + + // Click the focus-button + await sendKeys({ press: 'Space' }); + + expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('selected')).to.be.false; + expect(dashboard.hasAttribute('item-selected')).to.be.false; + }); + it('should dispatch a selection event', async () => { const spy = sinon.spy(); dashboard.addEventListener('dashboard-item-selected-changed', spy); @@ -448,7 +466,7 @@ describe('dashboard - keyboard interaction', () => { // Enter move mode await sendKeys({ press: 'Space' }); - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; expect(widget.hasAttribute('move-mode')).to.be.true; }); @@ -471,7 +489,7 @@ describe('dashboard - keyboard interaction', () => { (getDraggable(widget) as HTMLElement).click(); await nextFrame(); - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; expect(widget.hasAttribute('move-mode')).to.be.true; }); @@ -501,7 +519,7 @@ describe('dashboard - keyboard interaction', () => { await sendKeys({ press: 'Escape' }); expect(widget.hasAttribute('move-mode')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; }); it('should dispatch a move mode event', async () => { @@ -519,7 +537,7 @@ describe('dashboard - keyboard interaction', () => { await sendKeys({ press: 'Space' }); expect(widget.hasAttribute('move-mode')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; }); it('should focus drag handle on exit move mode', async () => { @@ -594,7 +612,7 @@ describe('dashboard - keyboard interaction', () => { expect(widget.hasAttribute('move-mode')).to.be.true; expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; }); it('should not lose move mode when the focused forward button is hidden', async () => { @@ -624,7 +642,7 @@ describe('dashboard - keyboard interaction', () => { expect(widget.hasAttribute('move-mode')).to.be.true; expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; }); it('should deselect the section on blur', async () => { @@ -678,7 +696,7 @@ describe('dashboard - keyboard interaction', () => { const widget = getElementFromCell(dashboard, 0, 0)!; expect(widget.contains(document.activeElement)).to.be.true; expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; dashboard.editable = false; await nextFrame(); expect(widget.contains(document.activeElement)).to.be.false; @@ -697,7 +715,7 @@ describe('dashboard - keyboard interaction', () => { await sendKeys({ press: 'Tab' }); await sendKeys({ press: 'Space' }); - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; expect(widget.hasAttribute('resize-mode')).to.be.true; }); @@ -707,7 +725,7 @@ describe('dashboard - keyboard interaction', () => { (getResizeHandle(widget) as HTMLElement).click(); await nextFrame(); - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; expect(widget.hasAttribute('resize-mode')).to.be.true; }); @@ -739,7 +757,7 @@ describe('dashboard - keyboard interaction', () => { await sendKeys({ press: 'Escape' }); expect(widget.hasAttribute('resize-mode')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; }); it('should dispatch a resize mode event', async () => { @@ -756,7 +774,7 @@ describe('dashboard - keyboard interaction', () => { await sendKeys({ press: 'Space' }); expect(widget.hasAttribute('resize-mode')).to.be.false; expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; }); it('should focus resize handle on exit resize mode', async () => { diff --git a/packages/dashboard/test/dashboard-widget-reordering.test.ts b/packages/dashboard/test/dashboard-widget-reordering.test.ts index 23adc00c84..ade35e4724 100644 --- a/packages/dashboard/test/dashboard-widget-reordering.test.ts +++ b/packages/dashboard/test/dashboard-widget-reordering.test.ts @@ -474,7 +474,7 @@ describe('dashboard - widget reordering', () => { (getDraggable(widget) as HTMLElement).click(); await nextFrame(); expect(widget.hasAttribute('selected')).to.be.true; - expect(widget.hasAttribute('focused')).to.be.true; + expect(widget.hasAttribute('focused')).to.be.false; expect(widget.hasAttribute('move-mode')).to.be.true; fireDragStart(widget); diff --git a/packages/dashboard/theme/lumo/vaadin-dashboard-widget-styles.js b/packages/dashboard/theme/lumo/vaadin-dashboard-widget-styles.js index d6497f6933..bde5410b51 100644 --- a/packages/dashboard/theme/lumo/vaadin-dashboard-widget-styles.js +++ b/packages/dashboard/theme/lumo/vaadin-dashboard-widget-styles.js @@ -40,6 +40,10 @@ const dashboardWidgetAndSection = css` margin: calc(var(--_focus-ring-spacing-offset) * -1); } + :host([selected])::before { + outline: 1px solid var(--_focus-ring-color); + } + :host([focused])::before { outline: var(--_focus-ring-width) solid var(--_focus-ring-color); }