Skip to content

Commit f903572

Browse files
refactor: replace global focusin listener with focusout check (#7858) (#7867)
Co-authored-by: Serhii Kulykov <iamkulykov@gmail.com>
1 parent cb501c6 commit f903572

File tree

4 files changed

+69
-150
lines changed

4 files changed

+69
-150
lines changed

packages/grid-pro/src/vaadin-grid-pro-edit-column-mixin.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,6 @@ export const GridProEditColumnMixin = (superClass) =>
282282
editor.addEventListener('focusout', this._grid.__boundEditorFocusOut);
283283
editor.addEventListener('focusin', this._grid.__boundEditorFocusIn);
284284
editor.addEventListener('internal-tab', this._grid.__boundCancelCellSwitch);
285-
document.body.addEventListener('focusin', this._grid.__boundGlobalFocusIn);
286285
this._setEditorOptions(editor);
287286
this._setEditorValue(editor, get(this.path, model.item));
288287
editor._grid = this._grid;
@@ -300,8 +299,6 @@ export const GridProEditColumnMixin = (superClass) =>
300299
* @protected
301300
*/
302301
_stopCellEdit(cell, model) {
303-
document.body.removeEventListener('focusin', this._grid.__boundGlobalFocusIn);
304-
305302
this._removeEditor(cell, model);
306303
}
307304
};

packages/grid-pro/src/vaadin-grid-pro-inline-editing-mixin.js

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ export const InlineEditingMixin = (superClass) =>
6666
this.__boundEditorFocusOut = this._onEditorFocusOut.bind(this);
6767
this.__boundEditorFocusIn = this._onEditorFocusIn.bind(this);
6868
this.__boundCancelCellSwitch = this._setCancelCellSwitch.bind(this);
69-
this.__boundGlobalFocusIn = this._onGlobalFocusIn.bind(this);
7069

7170
this._addEditColumnListener('mousedown', (e) => {
7271
// Prevent grid from resetting navigating state
@@ -305,36 +304,37 @@ export const InlineEditingMixin = (superClass) =>
305304
}
306305

307306
/** @private */
308-
_onEditorFocusOut() {
307+
_onEditorFocusOut(event) {
309308
// Ignore focusout from internal tab event
310-
if (this.__cancelCellSwitch) {
309+
if (this.__cancelCellSwitch || this.__shouldIgnoreFocusOut(event)) {
311310
return;
312311
}
312+
313313
// Schedule stop on editor component focusout
314314
this._debouncerStopEdit = Debouncer.debounce(this._debouncerStopEdit, animationFrame, this._stopEdit.bind(this));
315315
}
316316

317317
/** @private */
318-
_onEditorFocusIn() {
319-
this._cancelStopEdit();
320-
}
321-
322-
/** @private */
323-
_onGlobalFocusIn(e) {
318+
__shouldIgnoreFocusOut(event) {
324319
const edited = this.__edited;
325320
if (edited) {
326-
// Detect focus moving to e.g. vaadin-select-overlay
327-
const overlay = Array.from(e.composedPath()).filter(
328-
(node) => node.nodeType === Node.ELEMENT_NODE && /^vaadin-(?!dialog).*-overlay$/iu.test(node.localName),
329-
)[0];
330-
331-
if (overlay) {
332-
overlay.addEventListener('vaadin-overlay-outside-click', this.__boundEditorFocusOut);
333-
this._cancelStopEdit();
321+
const { cell, column } = this.__edited;
322+
const editor = column._getEditorComponent(cell);
323+
324+
const path = event.composedPath();
325+
const nodes = path.slice(0, path.indexOf(editor) + 1).filter((node) => node.nodeType === Node.ELEMENT_NODE);
326+
// Detect focus moving to e.g. vaadin-select-overlay or vaadin-date-picker-overlay
327+
if (nodes.some((el) => typeof el._shouldRemoveFocus === 'function' && !el._shouldRemoveFocus(event))) {
328+
return true;
334329
}
335330
}
336331
}
337332

333+
/** @private */
334+
_onEditorFocusIn() {
335+
this._cancelStopEdit();
336+
}
337+
338338
/** @private */
339339
_startEdit(cell, column) {
340340
const isCellEditable = this._isCellEditable(cell);
@@ -446,9 +446,21 @@ export const InlineEditingMixin = (superClass) =>
446446
// Do not prevent Tab to allow native input blur and wait for it,
447447
// unless the keydown event is from the edit cell select overlay.
448448
if (e.key === 'Tab' && editor && editor.contains(e.target)) {
449-
await new Promise((resolve) => {
450-
editor.addEventListener('focusout', () => resolve(), { once: true });
449+
const ignore = await new Promise((resolve) => {
450+
editor.addEventListener(
451+
'focusout',
452+
(event) => {
453+
resolve(this.__shouldIgnoreFocusOut(event));
454+
},
455+
{ once: true },
456+
);
451457
});
458+
459+
// Ignore focusout event after which focus stays in the field,
460+
// e.g. Tab between date and time pickers in date-time-picker.
461+
if (ignore) {
462+
return;
463+
}
452464
} else {
453465
e.preventDefault();
454466
}

test/integration/dialog-grid-pro.test.js

Lines changed: 0 additions & 114 deletions
This file was deleted.

test/integration/grid-pro-custom-editor.test.js

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,21 @@ describe('grid-pro custom editor', () => {
105105
expect(cell._content.querySelector('vaadin-date-picker')).to.be.ok;
106106
});
107107

108-
it('should stop editing and update value when closing on outside click', async () => {
108+
it('should not stop editing when clicking inside the overlay but not on focusable element', async () => {
109+
// Open the overlay
110+
await sendKeys({ press: 'ArrowDown' });
111+
await waitForOverlayRender();
112+
113+
// Click between toolbar buttons
114+
const overlayContent = document.querySelector('vaadin-date-picker-overlay-content');
115+
const { x, y } = middleOfNode(overlayContent.shadowRoot.querySelector('[part="toolbar"]'));
116+
await sendMouse({ type: 'click', position: [Math.floor(x), Math.floor(y)] });
117+
await nextRender();
118+
119+
expect(cell._content.querySelector('vaadin-date-picker')).to.be.ok;
120+
});
121+
122+
it('should not stop editing and update value when closing on outside click', async () => {
109123
// Open the overlay
110124
await sendKeys({ press: 'ArrowDown' });
111125
await waitForOverlayRender();
@@ -124,11 +138,9 @@ describe('grid-pro custom editor', () => {
124138
await sendMouse({ type: 'click', position: [10, 10] });
125139
await nextRender();
126140

127-
// TODO: closing occurs in `vaadin-overlay-outside-click` listener added on global `focusin`
128-
// in grid-pro. Consider replacing it with `_shouldRemoveFocus()` check on editor `focusout`
129-
// so that we don't stop editing on outside click, to align with the combo-box behavior.
130-
expect(cell._content.querySelector('vaadin-date-picker')).to.be.not.ok;
131-
expect(cell._content.textContent).to.equal('1984-01-12');
141+
const editor = cell._content.querySelector('vaadin-date-picker');
142+
expect(editor).to.be.ok;
143+
expect(editor.value).to.equal('1984-01-12');
132144
});
133145
});
134146

@@ -214,7 +226,21 @@ describe('grid-pro custom editor', () => {
214226
await sendKeys({ press: 'Enter' });
215227
});
216228

217-
it('should stop editing and update value when closing on date-picker outside click', async () => {
229+
it('should not stop editing when switching between pickers using Tab', async () => {
230+
// Move focus to the time-picker
231+
await sendKeys({ press: 'Tab' });
232+
await nextRender();
233+
expect(cell._content.querySelector('vaadin-date-time-picker')).to.be.ok;
234+
235+
// Move focus to the date-picker
236+
await sendKeys({ down: 'Shift' });
237+
await sendKeys({ press: 'Tab' });
238+
await sendKeys({ up: 'Shift' });
239+
await nextRender();
240+
expect(cell._content.querySelector('vaadin-date-time-picker')).to.be.ok;
241+
});
242+
243+
it('should not stop editing and update value when closing on date-picker outside click', async () => {
218244
await sendKeys({ press: 'ArrowDown' });
219245
await waitForOverlayRender();
220246

@@ -232,16 +258,13 @@ describe('grid-pro custom editor', () => {
232258
await sendMouse({ type: 'click', position: [10, 10] });
233259
await nextRender();
234260

235-
// TODO: closing occurs in `vaadin-overlay-outside-click` listener added on global `focusin`
236-
// in grid-pro. Consider replacing it with `_shouldRemoveFocus()` check on editor `focusout`
237-
// so that we don't stop editing on outside click, to align with the combo-box behavior.
238-
expect(cell._content.querySelector('vaadin-date-time-picker')).to.be.not.ok;
239-
expect(cell._content.textContent).to.equal('1984-01-12T09:00');
261+
const editor = cell._content.querySelector('vaadin-date-time-picker');
262+
expect(editor).to.be.ok;
263+
expect(editor.value).to.equal('1984-01-12T09:00');
240264
});
241265

242266
it('should not stop editing and update value when closing on time-picker outside click', async () => {
243-
// TODO: replace with Tab and add a separate test to not stop editing on Tab
244-
cell._content.querySelector('vaadin-time-picker').focus();
267+
await sendKeys({ press: 'Tab' });
245268

246269
// Open the overlay
247270
await sendKeys({ press: 'ArrowDown' });
@@ -250,6 +273,7 @@ describe('grid-pro custom editor', () => {
250273
await sendKeys({ press: 'ArrowDown' });
251274

252275
await sendMouse({ type: 'click', position: [10, 10] });
276+
await nextRender();
253277

254278
const editor = cell._content.querySelector('vaadin-date-time-picker');
255279
expect(editor).to.be.ok;

0 commit comments

Comments
 (0)