Skip to content

Commit df2ca50

Browse files
authored
chore: comparisons to document.activeElement consider shadowRoot (#6899)
* getRootActiveElement * Change files * comment
1 parent daff330 commit df2ca50

File tree

11 files changed

+45
-37
lines changed

11 files changed

+45
-37
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "comparisons to document.activeElement consider shadowRoot",
4+
"packageName": "@microsoft/fast-foundation",
5+
"email": "[email protected]",
6+
"dependentChangeType": "prerelease"
7+
}

packages/web-components/fast-foundation/docs/api-report.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2363,6 +2363,9 @@ export type GenerateHeaderOptions = ValuesOf<typeof GenerateHeaderOptions>;
23632363
// @public
23642364
export const getDirection: (rootNode: HTMLElement) => Direction;
23652365

2366+
// @public (undocumented)
2367+
export function getRootActiveElement(element: Element): Element | null;
2368+
23662369
// @public
23672370
export const hidden = ":host([hidden]){display:none}";
23682371

packages/web-components/fast-foundation/src/combobox/combobox.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { FASTListboxOption } from "../listbox-option/listbox-option.js";
66
import { DelegatesARIAListbox } from "../listbox/listbox.js";
77
import { StartEnd } from "../patterns/start-end.js";
88
import type { StartEndOptions } from "../patterns/start-end.js";
9+
import { getRootActiveElement } from "../utilities/index.js";
910
import { applyMixins } from "../utilities/apply-mixins.js";
1011
import { FormAssociatedCombobox } from "./combobox.form-associated.js";
1112
import { ComboboxAutocomplete } from "./combobox.options.js";
@@ -337,7 +338,7 @@ export class FASTCombobox extends FormAssociatedCombobox {
337338
* Overrides: `Listbox.focusAndScrollOptionIntoView`
338339
*/
339340
protected focusAndScrollOptionIntoView(): void {
340-
if (this.contains(document.activeElement)) {
341+
if (this.contains(getRootActiveElement(this))) {
341342
this.control.focus();
342343
if (this.firstSelectedOption) {
343344
requestAnimationFrame(() => {

packages/web-components/fast-foundation/src/data-grid/data-grid-cell.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
keyPageUp,
1818
} from "@microsoft/fast-web-utilities";
1919
import { isFocusable } from "tabbable";
20+
import { getRootActiveElement } from "../utilities/index.js";
2021
import type { ColumnDefinition } from "./data-grid.js";
2122
import { DataGridCellTypes } from "./data-grid.options.js";
2223

@@ -200,7 +201,8 @@ export class FASTDataGridCell extends FASTElement {
200201
}
201202

202203
public handleFocusout(e: FocusEvent): void {
203-
if (this !== document.activeElement && !this.contains(document.activeElement)) {
204+
const activeElement: Element | null = getRootActiveElement(this);
205+
if (this !== activeElement && !this.contains(activeElement)) {
204206
this.isActiveCell = false;
205207
}
206208
}
@@ -230,7 +232,7 @@ export class FASTDataGridCell extends FASTElement {
230232
return;
231233
}
232234

233-
const rootActiveElement: Element | null = this.getRootActiveElement();
235+
const rootActiveElement: Element | null = getRootActiveElement(this);
234236

235237
switch (e.key) {
236238
case keyEnter:
@@ -292,16 +294,6 @@ export class FASTDataGridCell extends FASTElement {
292294
}
293295
}
294296

295-
private getRootActiveElement(): Element | null {
296-
const rootNode = this.getRootNode();
297-
298-
if (rootNode instanceof ShadowRoot) {
299-
return rootNode.activeElement;
300-
}
301-
302-
return document.activeElement;
303-
}
304-
305297
private updateCellView(): void {
306298
this.disconnectCellView();
307299

packages/web-components/fast-foundation/src/data-grid/data-grid.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
keyPageDown,
2121
keyPageUp,
2222
} from "@microsoft/fast-web-utilities";
23+
import { getRootActiveElement } from "../utilities/index.js";
2324
import type { FASTDataGridCell } from "./data-grid-cell.js";
2425
import type { FASTDataGridRow } from "./data-grid-row.js";
2526
import {
@@ -173,12 +174,10 @@ export class FASTDataGrid extends FASTElement {
173174
if (this.noTabbing) {
174175
this.setAttribute("tabIndex", "-1");
175176
} else {
177+
const activeElement: Element | null = getRootActiveElement(this);
176178
this.setAttribute(
177179
"tabIndex",
178-
this.contains(document.activeElement) ||
179-
this === document.activeElement
180-
? "-1"
181-
: "0"
180+
this.contains(activeElement) || this === activeElement ? "-1" : "0"
182181
);
183182
}
184183
}
@@ -908,9 +907,10 @@ export class FASTDataGrid extends FASTElement {
908907
};
909908

910909
private queueFocusUpdate(): void {
910+
const activeElement: Element | null = getRootActiveElement(this);
911911
if (
912912
this.isUpdatingFocus &&
913-
(this.contains(document.activeElement) || this === document.activeElement)
913+
(this.contains(activeElement) || this === activeElement)
914914
) {
915915
return;
916916
}

packages/web-components/fast-foundation/src/dialog/dialog.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from "@microsoft/fast-element";
88
import { keyEscape, keyTab } from "@microsoft/fast-web-utilities";
99
import { isTabbable } from "tabbable";
10+
import { getRootActiveElement } from "../utilities/index.js";
1011

1112
/**
1213
* A Switch Custom HTML Element.
@@ -274,7 +275,7 @@ export class FASTDialog extends FASTElement {
274275
// Add an event listener for focusin events if we are trapping focus
275276
document.addEventListener("focusin", this.handleDocumentFocus);
276277
Updates.enqueue(() => {
277-
if (this.shouldForceFocus(document.activeElement)) {
278+
if (this.shouldForceFocus(getRootActiveElement(this))) {
278279
this.focusFirstElement();
279280
}
280281
});

packages/web-components/fast-foundation/src/listbox/listbox.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
keyTab,
1212
uniqueId,
1313
} from "@microsoft/fast-web-utilities";
14+
import { getRootActiveElement } from "../utilities/index.js";
1415
import { FASTListboxOption, isListboxOption } from "../listbox-option/listbox-option.js";
1516
import { ARIAGlobalStatesAndProperties } from "../patterns/index.js";
1617
import { applyMixins } from "../utilities/apply-mixins.js";
@@ -198,7 +199,7 @@ export abstract class FASTListbox extends FASTElement {
198199
// function is typically called from the `openChanged` observer, `DOM.queueUpdate`
199200
// causes the calls to be grouped into the same frame. To prevent this,
200201
// `requestAnimationFrame` is used instead of `DOM.queueUpdate`.
201-
if (this.contains(document.activeElement) && optionToFocus !== null) {
202+
if (this.contains(getRootActiveElement(this)) && optionToFocus !== null) {
202203
optionToFocus.focus();
203204
requestAnimationFrame(() => {
204205
optionToFocus.scrollIntoView({ block: "nearest" });
@@ -409,7 +410,7 @@ export abstract class FASTListbox extends FASTElement {
409410
* @internal
410411
*/
411412
public mousedownHandler(e: MouseEvent): boolean | void {
412-
this.shouldSkipFocus = !this.contains(document.activeElement);
413+
this.shouldSkipFocus = !this.contains(getRootActiveElement(this));
413414
return true;
414415
}
415416

packages/web-components/fast-foundation/src/picker/picker.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
FlyoutPosTop,
3333
FlyoutPosTopFill,
3434
} from "../anchored-region/index.js";
35+
import { getRootActiveElement } from "../utilities/index.js";
3536
import { FASTPickerListItem } from "./picker-list-item.js";
3637
import type { FASTPickerList } from "./picker-list.js";
3738
import { FASTPickerMenuOption } from "./picker-menu-option.js";
@@ -556,7 +557,7 @@ export class FASTPicker extends FormAssociatedPicker {
556557
return;
557558
}
558559

559-
if (open && this.getRootActiveElement() === this.inputElement) {
560+
if (open && getRootActiveElement(this) === this.inputElement) {
560561
this.flyoutOpen = open;
561562
Updates.enqueue(() => {
562563
if (this.menuElement !== undefined) {
@@ -605,7 +606,7 @@ export class FASTPicker extends FormAssociatedPicker {
605606
if (e.defaultPrevented) {
606607
return false;
607608
}
608-
const activeElement = this.getRootActiveElement();
609+
const activeElement = getRootActiveElement(this);
609610
switch (e.key) {
610611
// TODO: what should "home" and "end" keys do, exactly?
611612
//
@@ -811,7 +812,7 @@ export class FASTPicker extends FormAssociatedPicker {
811812
this.maxSelected !== 0 &&
812813
this.selectedItems.length >= this.maxSelected
813814
) {
814-
if (this.getRootActiveElement() === this.inputElement) {
815+
if (getRootActiveElement(this) === this.inputElement) {
815816
const selectedItemInstances: Element[] = Array.from(
816817
this.listElement.querySelectorAll("[role='listitem']")
817818
);
@@ -825,16 +826,6 @@ export class FASTPicker extends FormAssociatedPicker {
825826
}
826827
}
827828

828-
private getRootActiveElement(): Element | null {
829-
const rootNode = this.getRootNode();
830-
831-
if (rootNode instanceof ShadowRoot) {
832-
return rootNode.activeElement;
833-
}
834-
835-
return document.activeElement;
836-
}
837-
838829
/**
839830
* A list item has been invoked.
840831
*/
@@ -901,7 +892,7 @@ export class FASTPicker extends FormAssociatedPicker {
901892
this.listElement.querySelectorAll("[role='listitem']")
902893
);
903894

904-
const activeElement = this.getRootActiveElement();
895+
const activeElement = getRootActiveElement(this);
905896
if (activeElement !== null) {
906897
let currentFocusedItemIndex: number =
907898
selectedItemsAsElements.indexOf(activeElement);

packages/web-components/fast-foundation/src/tooltip/tooltip.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Updates,
1010
} from "@microsoft/fast-element";
1111
import { keyEscape, uniqueId } from "@microsoft/fast-web-utilities";
12+
import { getRootActiveElement } from "../utilities/index.js";
1213
import { TooltipPlacement } from "./tooltip.options.js";
1314

1415
/**
@@ -171,7 +172,7 @@ export class FASTTooltip extends FASTElement {
171172
* @internal
172173
*/
173174
private mouseoverAnchorHandler = (): void => {
174-
if (!document.activeElement?.isSameNode(this.anchorElement)) {
175+
if (!getRootActiveElement(this)?.isSameNode(this.anchorElement)) {
175176
this.showTooltip();
176177
}
177178
};
@@ -183,7 +184,7 @@ export class FASTTooltip extends FASTElement {
183184
*/
184185
private mouseoutAnchorHandler = (e: MouseEvent): void => {
185186
if (
186-
!document.activeElement?.isSameNode(this.anchorElement) &&
187+
!getRootActiveElement(this)?.isSameNode(this.anchorElement) &&
187188
!this.isSameNode(e.relatedTarget as HTMLElement)
188189
) {
189190
this.hideTooltip();

packages/web-components/fast-foundation/src/utilities/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ export {
1818
export { ValuesOf } from "./typings.js";
1919
export { whitespaceFilter } from "./whitespace-filter.js";
2020
export { staticallyCompose, StaticallyComposableHTML } from "./template-helpers.js";
21+
export { getRootActiveElement } from "./root-active-element.js";
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// returns the active element in the shadow context of the element in question.
2+
export function getRootActiveElement(element: Element): Element | null {
3+
const rootNode = element.getRootNode();
4+
5+
if (rootNode instanceof ShadowRoot) {
6+
return rootNode.activeElement;
7+
}
8+
9+
return document.activeElement;
10+
}

0 commit comments

Comments
 (0)