diff --git a/change/@fluentui-dom-utilities-781d59ca-cc21-4853-8971-45459939f85d.json b/change/@fluentui-dom-utilities-781d59ca-cc21-4853-8971-45459939f85d.json
new file mode 100644
index 00000000000000..eb6aa4897f80ee
--- /dev/null
+++ b/change/@fluentui-dom-utilities-781d59ca-cc21-4853-8971-45459939f85d.json
@@ -0,0 +1,7 @@
+{
+ "type": "minor",
+ "comment": "feat: add shadow DOM support for DOM APIs",
+ "packageName": "@fluentui/dom-utilities",
+ "email": "seanmonahan@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-focus-a8531b74-9b98-4cb7-8806-cae9890d42ed.json b/change/@fluentui-react-focus-a8531b74-9b98-4cb7-8806-cae9890d42ed.json
new file mode 100644
index 00000000000000..b64655b38e2fec
--- /dev/null
+++ b/change/@fluentui-react-focus-a8531b74-9b98-4cb7-8806-cae9890d42ed.json
@@ -0,0 +1,7 @@
+{
+ "type": "minor",
+ "comment": "feat: add shadow DOM support when traversing the DOM",
+ "packageName": "@fluentui/react-focus",
+ "email": "seanmonahan@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/packages/dom-utilities/etc/dom-utilities.api.md b/packages/dom-utilities/etc/dom-utilities.api.md
index 24373f59c4fcc0..89379ee2bf3f55 100644
--- a/packages/dom-utilities/etc/dom-utilities.api.md
+++ b/packages/dom-utilities/etc/dom-utilities.api.md
@@ -16,9 +16,15 @@ export function elementContainsAttribute(element: HTMLElement, attribute: string
// @public
export function findElementRecursive(element: HTMLElement | null, matchFunction: (element: HTMLElement) => boolean): HTMLElement | null;
+// @public (undocumented)
+export const getActiveElement: (doc: Document) => Element | null;
+
// @public
export function getChildren(parent: HTMLElement, allowVirtualChildren?: boolean): HTMLElement[];
+// @public (undocumented)
+export const getEventTarget: (event: Event) => HTMLElement | null;
+
// @public
export function getParent(child: HTMLElement, allowVirtualParents?: boolean): HTMLElement | null;
@@ -46,7 +52,6 @@ export function setPortalAttribute(element: HTMLElement): void;
// @public
export function setVirtualParent(child: HTMLElement, parent: HTMLElement | null): void;
-
// (No @packageDocumentation comment for this package)
```
diff --git a/packages/dom-utilities/src/getActiveElement.ts b/packages/dom-utilities/src/getActiveElement.ts
new file mode 100644
index 00000000000000..508efba43cb1f2
--- /dev/null
+++ b/packages/dom-utilities/src/getActiveElement.ts
@@ -0,0 +1,9 @@
+export const getActiveElement = (doc: Document): Element | null => {
+ let ae = doc.activeElement;
+
+ while (ae?.shadowRoot) {
+ ae = ae.shadowRoot.activeElement;
+ }
+
+ return ae;
+};
diff --git a/packages/dom-utilities/src/getEventTarget.ts b/packages/dom-utilities/src/getEventTarget.ts
new file mode 100644
index 00000000000000..c9afac3176c4c5
--- /dev/null
+++ b/packages/dom-utilities/src/getEventTarget.ts
@@ -0,0 +1,8 @@
+export const getEventTarget = (event: Event): HTMLElement | null => {
+ let target = event.target as HTMLElement;
+ if (target && target.shadowRoot) {
+ target = event.composedPath()[0] as HTMLElement;
+ }
+
+ return target;
+};
diff --git a/packages/dom-utilities/src/getParent.ts b/packages/dom-utilities/src/getParent.ts
index 9bc8841fbf521d..79698010351045 100644
--- a/packages/dom-utilities/src/getParent.ts
+++ b/packages/dom-utilities/src/getParent.ts
@@ -7,8 +7,28 @@ import { getVirtualParent } from './getVirtualParent';
* @public
*/
export function getParent(child: HTMLElement, allowVirtualParents: boolean = true): HTMLElement | null {
- return (
- child &&
- ((allowVirtualParents && getVirtualParent(child)) || (child.parentNode && (child.parentNode as HTMLElement)))
- );
+ if (!child) {
+ return null;
+ }
+
+ const parent = allowVirtualParents && getVirtualParent(child);
+
+ if (parent) {
+ return parent;
+ }
+
+ // Support looking for parents in shadow DOM
+ if (
+ typeof (child as HTMLSlotElement).assignedElements !== 'function' &&
+ (child as HTMLElement).assignedSlot?.parentNode
+ ) {
+ // Element is slotted
+ return (child as HTMLElement).assignedSlot as HTMLElement;
+ } else if (child.parentNode?.nodeType === 11) {
+ // nodeType 11 is DOCUMENT_FRAGMENT
+ // Element is in shadow root
+ return (child.parentNode as ShadowRoot).host as HTMLElement;
+ } else {
+ return child.parentNode as HTMLElement;
+ }
}
diff --git a/packages/dom-utilities/src/index.ts b/packages/dom-utilities/src/index.ts
index 29a7dd4ffadbc0..9b226dc459d9d6 100644
--- a/packages/dom-utilities/src/index.ts
+++ b/packages/dom-utilities/src/index.ts
@@ -1,13 +1,15 @@
-export * from './IVirtualElement';
-export * from './elementContains';
-export * from './elementContainsAttribute';
-export * from './findElementRecursive';
-export * from './getChildren';
-export * from './getParent';
-export * from './getVirtualParent';
-export * from './isVirtualElement';
-export * from './portalContainsElement';
-export * from './setPortalAttribute';
-export * from './setVirtualParent';
+export type { IVirtualElement } from './IVirtualElement';
+export { elementContains } from './elementContains';
+export { elementContainsAttribute } from './elementContainsAttribute';
+export { findElementRecursive } from './findElementRecursive';
+export { getActiveElement } from './getActiveElement';
+export { getChildren } from './getChildren';
+export { getEventTarget } from './getEventTarget';
+export { getParent } from './getParent';
+export { getVirtualParent } from './getVirtualParent';
+export { isVirtualElement } from './isVirtualElement';
+export { portalContainsElement } from './portalContainsElement';
+export { DATA_PORTAL_ATTRIBUTE, setPortalAttribute } from './setPortalAttribute';
+export { setVirtualParent } from './setVirtualParent';
import './version';
diff --git a/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChartRTL.test.tsx.snap b/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChartRTL.test.tsx.snap
index 4cf9bb7ff8e646..4f1761fabaa468 100644
--- a/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChartRTL.test.tsx.snap
+++ b/packages/react-charting/src/components/DonutChart/__snapshots__/DonutChartRTL.test.tsx.snap
@@ -205,7 +205,7 @@ exports[`Donut chart interactions Should hide callout on mouse leave 1`] = `
opacity: 0.8;
}
>
-
+
-
-
-
+
+
+
@@ -743,9 +743,9 @@ exports[`HorizontalBarChartWithAxis snapShot testing renders hideLegend correctl
x={40}
y={4}
/>
-
-
-
+
+
+
@@ -872,9 +872,9 @@ exports[`HorizontalBarChartWithAxis snapShot testing renders showToolTipForYAxis
x={40}
y={14.25}
/>
-
-
-
+
+
+
@@ -1479,9 +1479,9 @@ exports[`HorizontalBarChartWithAxis snapShot testing renders showYAxisLables cor
x={40}
y={14.25}
/>
-
-
-
+
+
+
diff --git a/packages/react-charting/src/components/HorizontalBarChartWithAxis/__snapshots__/HorizontalBarChartWithAxisRTL.test.tsx.snap b/packages/react-charting/src/components/HorizontalBarChartWithAxis/__snapshots__/HorizontalBarChartWithAxisRTL.test.tsx.snap
index ea3e12d86fd5b2..93de8942dddfa0 100644
--- a/packages/react-charting/src/components/HorizontalBarChartWithAxis/__snapshots__/HorizontalBarChartWithAxisRTL.test.tsx.snap
+++ b/packages/react-charting/src/components/HorizontalBarChartWithAxis/__snapshots__/HorizontalBarChartWithAxisRTL.test.tsx.snap
@@ -219,6 +219,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -305,6 +319,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -391,6 +419,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -477,6 +519,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -746,6 +802,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -832,6 +902,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -918,6 +1002,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1004,6 +1102,20 @@ exports[`Horizontal bar chart with axis - Screen resolution Should remain unchan
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1286,6 +1398,20 @@ exports[`Horizontal bar chart with axis - Theme Should reflect theme change 1`]
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #a19f9d;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #a19f9d;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #a19f9d;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -1372,6 +1498,20 @@ exports[`Horizontal bar chart with axis - Theme Should reflect theme change 1`]
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #a19f9d;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #a19f9d;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #a19f9d;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1458,6 +1598,20 @@ exports[`Horizontal bar chart with axis - Theme Should reflect theme change 1`]
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #a19f9d;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #a19f9d;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #a19f9d;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1544,6 +1698,20 @@ exports[`Horizontal bar chart with axis - Theme Should reflect theme change 1`]
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #a19f9d;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #a19f9d;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #a19f9d;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1814,6 +1982,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -1900,6 +2082,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1986,6 +2182,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -2072,6 +2282,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -2337,6 +2561,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -2421,6 +2659,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -2505,6 +2757,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -2589,6 +2855,20 @@ exports[`Horizontal bar chart with axis rendering Should render the Horizontal b
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -3272,6 +3552,20 @@ exports[`HorizontalBarChartWithAxis - mouse events Should render callout correct
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -3358,6 +3652,20 @@ exports[`HorizontalBarChartWithAxis - mouse events Should render callout correct
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -3444,6 +3752,20 @@ exports[`HorizontalBarChartWithAxis - mouse events Should render callout correct
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -3530,6 +3852,20 @@ exports[`HorizontalBarChartWithAxis - mouse events Should render callout correct
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
diff --git a/packages/react-charting/src/components/LineChart/__snapshots__/LineChartRTL.test.tsx.snap b/packages/react-charting/src/components/LineChart/__snapshots__/LineChartRTL.test.tsx.snap
index 6a03af3b7877ac..a1eca66b8375d4 100644
--- a/packages/react-charting/src/components/LineChart/__snapshots__/LineChartRTL.test.tsx.snap
+++ b/packages/react-charting/src/components/LineChart/__snapshots__/LineChartRTL.test.tsx.snap
@@ -2135,6 +2135,20 @@ exports[`Screen resolution Should remain unchanged on zoom in 1`] = `
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -2221,6 +2235,20 @@ exports[`Screen resolution Should remain unchanged on zoom in 1`] = `
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -2307,6 +2335,20 @@ exports[`Screen resolution Should remain unchanged on zoom in 1`] = `
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
diff --git a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap
index 2caeb768545218..cbfe863ae2432f 100644
--- a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap
+++ b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChart.test.tsx.snap
@@ -104,9 +104,9 @@ exports[`VerticalBarChart snapShot testing Should not render bar labels 1`] = `
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -599,9 +599,9 @@ exports[`VerticalBarChart snapShot testing renders VerticalBarChart correctly 1`
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -1094,9 +1094,9 @@ exports[`VerticalBarChart snapShot testing renders enabledLegendsWrapLines corre
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -1569,9 +1569,9 @@ exports[`VerticalBarChart snapShot testing renders hideLegend correctly 1`] = `
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -1682,9 +1682,9 @@ exports[`VerticalBarChart snapShot testing renders hideTooltip correctly 1`] = `
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -2177,9 +2177,9 @@ exports[`VerticalBarChart snapShot testing renders showXAxisLablesTooltip correc
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -2672,9 +2672,9 @@ exports[`VerticalBarChart snapShot testing renders wrapXAxisLables correctly 1`]
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -3167,9 +3167,9 @@ exports[`VerticalBarChart snapShot testing renders yAxisTickFormat correctly 1`]
transform="translate(40, 0)"
/>
-
-
-
+
+
+
diff --git a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap
index 6c5cd6ca2372fc..9e5508a25af950 100644
--- a/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap
+++ b/packages/react-charting/src/components/VerticalBarChart/__snapshots__/VerticalBarChartRTL.test.tsx.snap
@@ -98,9 +98,9 @@ exports[`Screen resolution Should remain unchanged on zoom in 1`] = `
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -564,9 +564,9 @@ exports[`Screen resolution Should remain unchanged on zoom out 1`] = `
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -1043,9 +1043,9 @@ exports[`Should reflect theme change 1`] = `
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -1521,9 +1521,9 @@ exports[`Vertical bar chart re-rendering Should re-render the vertical bar chart
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -1987,9 +1987,9 @@ exports[`Vertical bar chart rendering Should render the vertical bar chart with
transform="translate(40, 0)"
/>
-
-
-
+
+
+
@@ -2453,10 +2453,10 @@ exports[`Vertical bar chart rendering Should render the vertical bar chart with
transform="translate(40, 0)"
/>
-
-
-
-
+
+
+
+
@@ -3425,6 +3425,20 @@ exports[`VerticalBarChart - mouse events Should render callout correctly on mous
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -3511,6 +3525,20 @@ exports[`VerticalBarChart - mouse events Should render callout correctly on mous
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -3597,6 +3625,20 @@ exports[`VerticalBarChart - mouse events Should render callout correctly on mous
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -4212,6 +4254,20 @@ exports[`VerticalBarChart - mouse events Should render customized callout on mou
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -4298,6 +4354,20 @@ exports[`VerticalBarChart - mouse events Should render customized callout on mou
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -4384,6 +4454,20 @@ exports[`VerticalBarChart - mouse events Should render customized callout on mou
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
diff --git a/packages/react-charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChartRTL.test.tsx.snap b/packages/react-charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChartRTL.test.tsx.snap
index 6b1b55b3ec8594..d043a5f00c04a8 100644
--- a/packages/react-charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChartRTL.test.tsx.snap
+++ b/packages/react-charting/src/components/VerticalStackedBarChart/__snapshots__/VerticalStackedBarChartRTL.test.tsx.snap
@@ -868,6 +868,20 @@ exports[`Vertical stacked bar chart - Screen resolution Should remain unchanged
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -954,6 +968,20 @@ exports[`Vertical stacked bar chart - Screen resolution Should remain unchanged
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1040,6 +1068,20 @@ exports[`Vertical stacked bar chart - Screen resolution Should remain unchanged
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -1126,6 +1168,20 @@ exports[`Vertical stacked bar chart - Screen resolution Should remain unchanged
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -3042,6 +3098,20 @@ exports[`VerticalStackedBarChart - mouse events Should render callout correctly
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -3128,6 +3198,20 @@ exports[`VerticalStackedBarChart - mouse events Should render callout correctly
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -3742,6 +3826,20 @@ exports[`VerticalStackedBarChart - mouse events Should render customized callout
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -3828,6 +3926,20 @@ exports[`VerticalStackedBarChart - mouse events Should render customized callout
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -4474,6 +4586,20 @@ exports[`VerticalStackedBarChart - mouse events Should render customized callout
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="0"
@@ -4560,6 +4686,20 @@ exports[`VerticalStackedBarChart - mouse events Should render customized callout
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
@@ -4646,6 +4786,20 @@ exports[`VerticalStackedBarChart - mouse events Should render customized callout
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){.ms-Fabric--isFocusVisible &:focus:after {
outline-color: #605e5c;
}
+ :host(.ms-Fabric--isFocusVisible) &:focus:after {
+ border: 1px solid transparent;
+ bottom: 1px;
+ content: "";
+ left: 1px;
+ outline: 1px solid #605e5c;
+ position: absolute;
+ right: 1px;
+ top: 1px;
+ z-index: 1;
+ }
+ @media screen and (-ms-high-contrast: active), screen and (forced-colors: active){:host(.ms-Fabric--isFocusVisible) &:focus:after {
+ outline-color: #605e5c;
+ }
data-is-focusable="true"
role="option"
tabindex="-1"
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Box.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Box.Example.tsx
new file mode 100644
index 00000000000000..89c5d56a778da5
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Box.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZoneBoxExample } from '../FocusTrapZone/FocusTrapZone.Box.Example';
+
+export const ShadowDOMFocusTrapZoneBoxExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.BoxClick.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.BoxClick.Example.tsx
new file mode 100644
index 00000000000000..f2e3b35f1607a4
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.BoxClick.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZoneBoxClickExample } from '../FocusTrapZone/FocusTrapZone.Box.Click.Example';
+
+export const ShadowDOMFocusTrapZoneBoxClickExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.ChildShadowDom.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.ChildShadowDom.Example.tsx
new file mode 100644
index 00000000000000..318c88203dd5ab
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.ChildShadowDom.Example.tsx
@@ -0,0 +1,45 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZone, Toggle } from '@fluentui/react';
+
+export const ShadowDOMFocusTrapZoneChildShadowDomExample: React.FunctionComponent = () => {
+ const [disabled, setDisabled] = React.useState(true);
+ const buttonsConstructed = React.useRef(false);
+
+ const setRef = (node: HTMLElement | null) => {
+ if (node && buttonsConstructed.current === false) {
+ const btns = Array.from(node.querySelectorAll('.demo-button'));
+
+ btns.forEach((btn, i) => {
+ // Ideally this would be a custom element but because of the way
+ // Stories are transpiled, classes are converted to ES5 which breaks
+ // custom elements (they require ES6 classes).
+ // This approach gives us a shadow root to demonstrate the feature.
+ const shadowRoot = btn.attachShadow({ mode: 'open' });
+
+ shadowRoot.innerHTML = ``;
+ });
+
+ buttonsConstructed.current = true;
+ }
+ };
+
+ return (
+
+ setDisabled(!disabled)}
+ label="Enable trap zone"
+ onText="On (toggle to exit)"
+ offText="Off"
+ />
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.DialogInPanel.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.DialogInPanel.Example.tsx
new file mode 100644
index 00000000000000..8fec30a225478b
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.DialogInPanel.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZoneDialogInPanelExample } from '../FocusTrapZone/FocusTrapZone.DialogInPanel.Example';
+
+export const ShadowDOMFocusTrapZoneDialogInPanelExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusOnCustomElement.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusOnCustomElement.Example.tsx
new file mode 100644
index 00000000000000..aede2623a22b2a
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusOnCustomElement.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZoneBoxCustomElementExample } from '../FocusTrapZone/FocusTrapZone.Box.FocusOnCustomElement.Example';
+
+export const ShadowDOMFocusTrapZoneBoxCustomElementExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusZone.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusZone.Example.tsx
new file mode 100644
index 00000000000000..a6336f8f3e87d9
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusZone.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZoneFocusZoneExample } from '../FocusTrapZone/FocusTrapZone.FocusZone.Example';
+
+export const ShadowDOMFocusTrapZoneFocusZoneExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Nested.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Nested.Example.tsx
new file mode 100644
index 00000000000000..adba04df00a0ed
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Nested.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZoneNestedExample } from '../FocusTrapZone/FocusTrapZone.Nested.Example';
+
+export const ShadowDOMFocusTrapZoneNestedExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.NestedChildShadowDom.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.NestedChildShadowDom.Example.tsx
new file mode 100644
index 00000000000000..6b9b0cdc4771f8
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.NestedChildShadowDom.Example.tsx
@@ -0,0 +1,73 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusTrapZone, Toggle } from '@fluentui/react';
+
+type ZoneProps = {
+ zoneNumber: number;
+ depth: number;
+};
+
+const Zone: React.FC = ({ zoneNumber, depth, children }) => {
+ const [disabled, setDisabled] = React.useState(true);
+ const buttonsConstructed = React.useRef(false);
+
+ const style = React.useMemo(() => {
+ return {
+ marginLeft: 10 * depth,
+ border: `2px solid ${!disabled ? '#ababab' : 'transparent'}`,
+ padding: 10,
+ };
+ }, [depth, disabled]);
+
+ const setRef = (node: HTMLElement | null) => {
+ if (node && buttonsConstructed.current === false) {
+ const btns = Array.from(node.querySelectorAll(':scope > .demo-button'));
+
+ btns.forEach((btn, i) => {
+ // Ideally this would be a custom element but because of the way
+ // Stories are transpiled, classes are converted to ES5 which breaks
+ // custom elements (they require ES6 classes).
+ // This approach gives us a shadow root to demonstrate the feature.
+ const shadowRoot = btn.attachShadow({ mode: 'open' });
+
+ shadowRoot.innerHTML = ``;
+ });
+
+ buttonsConstructed.current = true;
+ }
+ };
+
+ return (
+
+
setDisabled(!disabled)}
+ label={`Enable trap zone ${zoneNumber}`}
+ onText="On (toggle to exit)"
+ offText="Off"
+ />
+
+
+
+
+
+ {children}
+
+
+ );
+};
+
+export const ShadowDOMFocusTrapZoneNestedChildShadowDomExample: React.FunctionComponent = () => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Disabled.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Disabled.Example.tsx
new file mode 100644
index 00000000000000..75c5a0ceaeceae
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Disabled.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusZoneDisabledExample } from '../../react-focus/FocusZone/FocusZone.Disabled.Example';
+
+export const ShadowDOMFocusZoneDisabledExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.HorizontalMenu.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.HorizontalMenu.Example.tsx
new file mode 100644
index 00000000000000..447f786efa2cb2
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.HorizontalMenu.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusZoneHorizontalMenuExample } from '../../react-focus/FocusZone/FocusZone.HorizontalMenu.Example';
+
+export const ShadowDOMFocusZoneHorizontalMenuExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.HorizontalMenuShadowDom.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.HorizontalMenuShadowDom.Example.tsx
new file mode 100644
index 00000000000000..bd9b8f5b2b5bab
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.HorizontalMenuShadowDom.Example.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusZone, FocusZoneDirection } from '@fluentui/react-focus';
+
+export const ShadowDOMFocusZoneHorizontalMenuShadowDomExample: React.FunctionComponent = () => {
+ const buttonsConstructed = React.useRef(false);
+
+ const setRef = (node: HTMLElement | null) => {
+ if (node && buttonsConstructed.current === false) {
+ const btns = Array.from(node.querySelectorAll('.demo-button'));
+
+ btns.forEach((btn, i) => {
+ // Ideally this would be a custom element but because of the way
+ // Stories are transpiled, classes are converted to ES5 which breaks
+ // custom elements (they require ES6 classes).
+ // This approach gives us a shadow root to demonstrate the feature.
+ const shadowRoot = btn.attachShadow({ mode: 'open', delegatesFocus: true });
+
+ shadowRoot.innerHTML = ``;
+ });
+
+ buttonsConstructed.current = true;
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.List.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.List.Example.tsx
new file mode 100644
index 00000000000000..5a5463608ec2be
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.List.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusZoneListExample } from '../../react-focus/FocusZone/FocusZone.List.Example';
+
+export const ShadowDOMFocusZoneListExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Photos.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Photos.Example.tsx
new file mode 100644
index 00000000000000..08a0647b04e9ab
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Photos.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusZonePhotosExample } from '../../react-focus/FocusZone/FocusZone.Photos.Example';
+
+export const ShadowDOMFocusZonePhotosExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Tabbable.Example.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Tabbable.Example.tsx
new file mode 100644
index 00000000000000..f1ddacd6f9dfda
--- /dev/null
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Tabbable.Example.tsx
@@ -0,0 +1,11 @@
+import * as React from 'react';
+import { Shadow } from './ShadowHelper';
+import { FocusZoneTabbableExample } from '../../react-focus/FocusZone/FocusZone.Tabbable.Example';
+
+export const ShadowDOMFocusZoneTabbableExample: React.FunctionComponent = () => {
+ return (
+
+
+
+ );
+};
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.doc.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.doc.tsx
index f3aef77f1987bb..a61025548b2042 100644
--- a/packages/react-examples/src/react/ShadowDOM/ShadowDOM.doc.tsx
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowDOM.doc.tsx
@@ -20,6 +20,19 @@ import { ShadowDOMDropdownExample } from './ShadowDOM.Dropdown.Example';
import { ShadowDOMExtendedPeoplePickerExample } from './ShadowDOM.ExtendedPeoplePicker.Example';
import { ShadowDOMFacepileExample } from './ShadowDOM.Facepile.Example';
import { ShadowDOMFloatingPeoplePickerExample } from './ShadowDOM.FloatingPeoplePicker.Example';
+import { ShadowDOMFocusTrapZoneBoxExample } from './ShadowDOM.FocusTrapZone.Box.Example';
+import { ShadowDOMFocusTrapZoneBoxClickExample } from './ShadowDOM.FocusTrapZone.BoxClick.Example';
+import { ShadowDOMFocusTrapZoneDialogInPanelExample } from './ShadowDOM.FocusTrapZone.DialogInPanel.Example';
+import { ShadowDOMFocusTrapZoneBoxCustomElementExample } from './ShadowDOM.FocusTrapZone.FocusOnCustomElement.Example';
+import { ShadowDOMFocusTrapZoneFocusZoneExample } from './ShadowDOM.FocusTrapZone.FocusZone.Example';
+import { ShadowDOMFocusTrapZoneNestedExample } from './ShadowDOM.FocusTrapZone.Nested.Example';
+import { ShadowDOMFocusTrapZoneChildShadowDomExample } from './ShadowDOM.FocusTrapZone.ChildShadowDom.Example';
+import { ShadowDOMFocusTrapZoneNestedChildShadowDomExample } from './ShadowDOM.FocusTrapZone.NestedChildShadowDom.Example';
+import { ShadowDOMFocusZoneDisabledExample } from './ShadowDOM.FocusZone.Disabled.Example';
+import { ShadowDOMFocusZoneHorizontalMenuExample } from './ShadowDOM.FocusZone.HorizontalMenu.Example';
+import { ShadowDOMFocusZoneListExample } from './ShadowDOM.FocusZone.List.Example';
+import { ShadowDOMFocusZonePhotosExample } from './ShadowDOM.FocusZone.Photos.Example';
+import { ShadowDOMFocusZoneTabbableExample } from './ShadowDOM.FocusZone.Tabbable.Example';
import { ShadowDOMGroupedListExample } from './ShadowDOM.GroupedList.Example';
import { ShadowDOMHoverCardExample } from './ShadowDOM.HoverCard.Example';
import { ShadowDOMIconExample } from './ShadowDOM.Icon.Example';
@@ -135,6 +148,45 @@ const ShadowDOMFacepileExampleCode =
const ShadowDOMFloatingPeoplePickerExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FloatingPeoplePicker.Example.tsx') as string;
+const ShadowDOMFocusTrapZoneBoxExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Box.Example.tsx') as string;
+
+const ShadowDOMFocusTrapZoneBoxClickExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.BoxClick.Example.tsx') as string;
+
+const ShadowDOMFocusTrapZoneDialogInPanelExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.DialogInPanel.Example.tsx') as string;
+
+const ShadowDOMFocusTrapZoneBoxCustomElementExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusOnCustomElement.Example.tsx') as string;
+
+const ShadowDOMFocusTrapZoneFocusZoneExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.FocusZone.Example.tsx') as string;
+
+const ShadowDOMFocusTrapZoneNestedExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.Nested.Example.tsx') as string;
+
+const ShadowDOMFocusTrapZoneChildShadowDomExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.ChildShadowDom.Example.tsx') as string;
+
+const ShadowDOMFocusTrapZoneNestedChildShadowDomExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusTrapZone.NestedChildShadowDom.Example.tsx') as string;
+
+const ShadowDOMFocusZoneDisabledExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Disabled.Example.tsx') as string;
+
+const ShadowDOMFocusZoneHorizontalMenuExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.HorizontalMenu.Example.tsx') as string;
+
+const ShadowDOMFocusZoneListExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.List.Example.tsx') as string;
+
+const ShadowDOMFocusZonePhotosExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Photos.Example.tsx') as string;
+
+const ShadowDOMFocusZoneTabbableExampleCode =
+ require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.FocusZone.Tabbable.Example.tsx') as string;
+
const ShadowDOMGroupedListExampleCode =
require('!raw-loader?esModule=false!@fluentui/react-examples/src/react/ShadowDOM/ShadowDOM.GroupedList.Example.tsx') as string;
@@ -399,6 +451,71 @@ export const ShadowDOMPageProps = (): IDocPageProps => ({
code: ShadowDOMFloatingPeoplePickerExampleCode,
view: ,
},
+ {
+ title: 'FocusTrapZoneBox',
+ code: ShadowDOMFocusTrapZoneBoxExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusTrapZoneBoxClick',
+ code: ShadowDOMFocusTrapZoneBoxClickExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusTrapZoneDialogInPanel',
+ code: ShadowDOMFocusTrapZoneDialogInPanelExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusTrapZoneFocusOnCustomElement',
+ code: ShadowDOMFocusTrapZoneBoxCustomElementExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusTrapZoneFocusZone',
+ code: ShadowDOMFocusTrapZoneFocusZoneExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusTrapZoneNested',
+ code: ShadowDOMFocusTrapZoneNestedExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusTrapZoneChildShadowDom',
+ code: ShadowDOMFocusTrapZoneChildShadowDomExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusTrapZoneNestedChildShadowDom',
+ code: ShadowDOMFocusTrapZoneNestedChildShadowDomExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusZoneDisabled',
+ code: ShadowDOMFocusZoneDisabledExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusZoneHorizontalMenu',
+ code: ShadowDOMFocusZoneHorizontalMenuExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusZoneList',
+ code: ShadowDOMFocusZoneListExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusZonePhotos',
+ code: ShadowDOMFocusZonePhotosExampleCode,
+ view: ,
+ },
+ {
+ title: 'FocusZoneTabbable',
+ code: ShadowDOMFocusZoneTabbableExampleCode,
+ view: ,
+ },
{
title: 'GroupedList',
code: ShadowDOMGroupedListExampleCode,
diff --git a/packages/react-examples/src/react/ShadowDOM/ShadowHelper.tsx b/packages/react-examples/src/react/ShadowDOM/ShadowHelper.tsx
index 5a02ae5818804c..e47cf461569d56 100644
--- a/packages/react-examples/src/react/ShadowDOM/ShadowHelper.tsx
+++ b/packages/react-examples/src/react/ShadowDOM/ShadowHelper.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import { MergeStylesRootProvider, MergeStylesShadowRootProvider } from '@fluentui/react';
+import { FocusRectsProvider, MergeStylesRootProvider, MergeStylesShadowRootProvider } from '@fluentui/react';
import root from 'react-shadow';
export type ShadowProps = {
@@ -21,9 +21,13 @@ export const Shadow: React.FC = ({ window, children }) => {
return (
-
- {children}
-
+
+
+
+ {children}
+
+
+
);
};
diff --git a/packages/react-focus/etc/react-focus.api.md b/packages/react-focus/etc/react-focus.api.md
index bb83efb7bd7a68..420c1dc237d940 100644
--- a/packages/react-focus/etc/react-focus.api.md
+++ b/packages/react-focus/etc/react-focus.api.md
@@ -5,6 +5,7 @@
```ts
import type { IRefObject } from '@fluentui/utilities';
+import { MergeStylesShadowRootContextValue } from '@fluentui/utilities/lib/shadowDom/MergeStylesShadowRootContext';
import type { Point } from '@fluentui/utilities';
import * as React_2 from 'react';
@@ -20,6 +21,8 @@ export class FocusZone extends React_2.Component implements IFo
// (undocumented)
componentWillUnmount(): void;
// (undocumented)
+ static contextType: React_2.Context;
+ // (undocumented)
get defaultFocusElement(): HTMLElement | null;
// (undocumented)
static defaultProps: IFocusZoneProps;
diff --git a/packages/react-focus/src/components/FocusZone/FocusZone.tsx b/packages/react-focus/src/components/FocusZone/FocusZone.tsx
index a8ae33009abcc4..77597c7c73c3ea 100644
--- a/packages/react-focus/src/components/FocusZone/FocusZone.tsx
+++ b/packages/react-focus/src/components/FocusZone/FocusZone.tsx
@@ -24,6 +24,7 @@ import {
findScrollableParent,
createMergedRef,
isElementVisibleAndNotHidden,
+ MergeStylesShadowRootContext,
} from '@fluentui/utilities';
import { mergeStyles } from '@fluentui/merge-styles';
import { getTheme } from '@fluentui/style-utilities';
@@ -110,6 +111,8 @@ const ALLOWED_INPUT_TYPES = ['text', 'number', 'password', 'email', 'tel', 'url'
const ALLOW_VIRTUAL_ELEMENTS = false;
export class FocusZone extends React.Component implements IFocusZone {
+ public static contextType = MergeStylesShadowRootContext;
+
public static defaultProps: IFocusZoneProps = {
isCircularNavigation: false,
direction: FocusZoneDirection.bidirectional,
@@ -148,6 +151,8 @@ export class FocusZone extends React.Component implements IFocu
private _shouldRaiseClicksOnEnter: boolean;
private _shouldRaiseClicksOnSpace: boolean;
+ private _inShadowRoot: boolean;
+
/** Used for testing purposes only. */
public static getOuterZones(): number {
return _outerZones.size;
@@ -197,6 +202,8 @@ export class FocusZone extends React.Component implements IFocu
public componentDidMount(): void {
const { current: root } = this._root;
+ this._inShadowRoot = !!this.context?.shadowRoot;
+
_allInstances[this._id] = this;
if (root) {
@@ -238,6 +245,7 @@ export class FocusZone extends React.Component implements IFocu
public componentDidUpdate(): void {
const { current: root } = this._root;
const doc = this._getDocument();
+ this._inShadowRoot = !!this.context?.shadowRoot;
// If either _activeElement or _defaultFocusElement are no longer contained by _root,
// reset those variables (and update tab indexes) to avoid memory leaks
@@ -362,7 +370,7 @@ export class FocusZone extends React.Component implements IFocu
!forceIntoFirstElement &&
this._activeElement &&
elementContains(this._root.current, this._activeElement) &&
- isElementTabbable(this._activeElement) &&
+ isElementTabbable(this._activeElement, undefined, this._inShadowRoot) &&
(!bypassHiddenElements || isElementVisibleAndNotHidden(this._activeElement))
) {
this._activeElement.focus();
@@ -489,7 +497,10 @@ export class FocusZone extends React.Component implements IFocu
let parentElement = ev.target as HTMLElement;
while (parentElement && parentElement !== this._root.current) {
- if (isElementTabbable(parentElement) && this._isImmediateDescendantOfZone(parentElement)) {
+ if (
+ isElementTabbable(parentElement, undefined, this._inShadowRoot) &&
+ this._isImmediateDescendantOfZone(parentElement)
+ ) {
newActiveElement = parentElement;
break;
}
@@ -506,7 +517,7 @@ export class FocusZone extends React.Component implements IFocu
defaultTabbableElement(this._root.current);
// try to focus defaultTabbable element
- if (maybeElementToFocus && isElementTabbable(maybeElementToFocus)) {
+ if (maybeElementToFocus && isElementTabbable(maybeElementToFocus, undefined, this._inShadowRoot)) {
newActiveElement = maybeElementToFocus;
maybeElementToFocus.focus();
} else {
@@ -606,7 +617,7 @@ export class FocusZone extends React.Component implements IFocu
while (path.length) {
target = path.pop() as HTMLElement;
- if (target && isElementTabbable(target)) {
+ if (target && isElementTabbable(target, undefined, this._inShadowRoot)) {
this._setActiveElement(target, true);
}
@@ -1330,7 +1341,7 @@ export class FocusZone extends React.Component implements IFocu
// If active element changes state to disabled, set it to null.
// Otherwise, we lose keyboard accessibility to other elements in focus zone.
- if (this._activeElement && !isElementTabbable(this._activeElement)) {
+ if (this._activeElement && !isElementTabbable(this._activeElement, undefined, this._inShadowRoot)) {
this._activeElement = null;
}
@@ -1345,7 +1356,7 @@ export class FocusZone extends React.Component implements IFocu
child.setAttribute(TABINDEX, '-1');
}
- if (isElementTabbable(child)) {
+ if (isElementTabbable(child, undefined, this._inShadowRoot)) {
if (this.props.disabled) {
child.setAttribute(TABINDEX, '-1');
} else if (
diff --git a/packages/react/src/Utilities.ts b/packages/react/src/Utilities.ts
index 14d5396471fe77..b6b50f744c7dda 100644
--- a/packages/react/src/Utilities.ts
+++ b/packages/react/src/Utilities.ts
@@ -68,10 +68,12 @@ export {
focusFirstChild,
formProperties,
format,
+ getActiveElement,
getChildren,
getDistanceBetweenPoints,
getDocument,
getElementIndexPath,
+ getEventTarget,
getFirstFocusable,
getFirstTabbable,
getFirstVisibleElementFromSelector,
diff --git a/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx b/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx
index b4b6c6e9a75ef6..1b406e624121e9 100644
--- a/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx
+++ b/packages/react/src/components/FocusTrapZone/FocusTrapZone.tsx
@@ -1,5 +1,7 @@
import * as React from 'react';
import {
+ getActiveElement,
+ getEventTarget,
elementContains,
getNativeProps,
divProperties,
@@ -10,6 +12,7 @@ import {
getPropsWithDefaults,
modalize,
on,
+ useHasMergeStylesShadowRootContext,
} from '../../Utilities';
import { useId, useConst, useMergedRefs, useEventCallback, usePrevious, useUnmount } from '@fluentui/react-hooks';
import { useDocument } from '../../WindowProvider';
@@ -62,6 +65,7 @@ export const FocusTrapZone: React.FunctionComponent & {
const lastBumper = React.useRef(null);
const mergedRootRef = useMergedRefs(root, ref) as React.Ref;
const doc = useDocument();
+ const inShadow = useHasMergeStylesShadowRootContext();
const isFirstRender = usePrevious(false) ?? true;
@@ -147,6 +151,10 @@ export const FocusTrapZone: React.FunctionComponent & {
false,
false,
true,
+ undefined,
+ undefined,
+ undefined,
+ inShadow,
);
}
@@ -163,8 +171,8 @@ export const FocusTrapZone: React.FunctionComponent & {
const nextFocusable =
isFirstBumper === internalState.hasFocus
- ? getLastTabbable(root.current, lastBumper.current!, true, false)
- : getFirstTabbable(root.current, firstBumper.current!, true, false);
+ ? getLastTabbable(root.current, lastBumper.current!, true, false, inShadow)
+ : getFirstTabbable(root.current, firstBumper.current!, true, false, inShadow);
if (nextFocusable) {
if (nextFocusable === firstBumper.current || nextFocusable === lastBumper.current) {
@@ -187,7 +195,7 @@ export const FocusTrapZone: React.FunctionComponent & {
// even when it's not. Using document.activeElement is another way
// for us to be able to get what the relatedTarget without relying
// on the event
- relatedTarget = doc!.activeElement as Element;
+ relatedTarget = getActiveElement(doc!) as Element;
}
if (!elementContains(root.current, relatedTarget as HTMLElement)) {
internalState.hasFocus = false;
@@ -209,7 +217,7 @@ export const FocusTrapZone: React.FunctionComponent & {
if (ev.target !== ev.currentTarget && !(ev.target === firstBumper.current || ev.target === lastBumper.current)) {
// every time focus changes within the trap zone, remember the focused element so that
// it can be restored if focus leaves the pane and returns via keystroke (i.e. via a call to this.focus(true))
- internalState.previouslyFocusedElementInTrapZone = ev.target as HTMLElement;
+ internalState.previouslyFocusedElementInTrapZone = getEventTarget(ev.nativeEvent) as HTMLElement;
}
};
@@ -221,12 +229,16 @@ export const FocusTrapZone: React.FunctionComponent & {
return;
}
+ // Do not use getActiveElement() here.
+ // When the FTZ is in shadow DOM focus returns to the
+ // shadow host rather than body so we need to be
+ // able to inspect that
const activeElement = doc.activeElement as HTMLElement;
if (
!disableRestoreFocus &&
typeof elementToFocusOnDismiss?.focus === 'function' &&
// only restore focus if the current focused element is within the FTZ, or if nothing is focused
- (elementContains(root.current, activeElement) || activeElement === doc.body)
+ (elementContains(root.current, activeElement) || activeElement === doc.body || activeElement.shadowRoot)
) {
focusElementAsync(elementToFocusOnDismiss);
}
@@ -239,11 +251,11 @@ export const FocusTrapZone: React.FunctionComponent & {
return;
}
if (internalState.focusStackId === FocusTrapZone.focusStack!.slice(-1)[0]) {
- const targetElement = ev.target as HTMLElement | null;
+ const targetElement = getEventTarget(ev);
if (targetElement && !elementContains(root.current, targetElement)) {
- if (doc && doc.activeElement === doc.body) {
+ if (doc && getActiveElement(doc) === doc.body) {
setTimeout(() => {
- if (doc && doc.activeElement === doc.body) {
+ if (doc && getActiveElement(doc) === doc.body) {
focusFTZ();
internalState.hasFocus = true; // set focus here since we stop event propagation
}
@@ -287,7 +299,7 @@ export const FocusTrapZone: React.FunctionComponent & {
// Transition from forceFocusInsideTrap / FTZ disabled to enabled (or initial mount)
FocusTrapZone.focusStack!.push(internalState.focusStackId);
- const elementToFocusOnDismiss = props.elementToFocusOnDismiss || (doc!.activeElement as HTMLElement | null);
+ const elementToFocusOnDismiss = props.elementToFocusOnDismiss || (getActiveElement(doc!) as HTMLElement | null);
if (!disableFirstFocus && !elementContains(root.current, elementToFocusOnDismiss)) {
focusFTZ();
diff --git a/packages/utilities/etc/utilities.api.md b/packages/utilities/etc/utilities.api.md
index 88b77c71fba5c9..6baf063273d003 100644
--- a/packages/utilities/etc/utilities.api.md
+++ b/packages/utilities/etc/utilities.api.md
@@ -9,7 +9,9 @@ import { elementContains } from '@fluentui/dom-utilities';
import { elementContainsAttribute } from '@fluentui/dom-utilities';
import type { ExtendedCSSStyleSheet } from '@fluentui/merge-styles';
import { findElementRecursive } from '@fluentui/dom-utilities';
+import { getActiveElement } from '@fluentui/dom-utilities';
import { getChildren } from '@fluentui/dom-utilities';
+import { getEventTarget } from '@fluentui/dom-utilities';
import { getParent } from '@fluentui/dom-utilities';
import { getVirtualParent } from '@fluentui/dom-utilities';
import type { IProcessedStyleSet } from '@fluentui/merge-styles';
@@ -296,7 +298,7 @@ export function focusAsync(element: HTMLElement | {
} | undefined | null): void;
// @public
-export function focusFirstChild(rootElement: HTMLElement, bypassHiddenElements?: boolean): boolean;
+export function focusFirstChild(rootElement: HTMLElement, bypassHiddenElements?: boolean, includeShadowRoots?: boolean): boolean;
// @public
export const FocusRects: React_2.FunctionComponent<{
@@ -321,6 +323,8 @@ export function format(s: string, ...values: any[]): string;
// @public
export const formProperties: Record;
+export { getActiveElement }
+
export { getChildren }
// @public
@@ -332,11 +336,13 @@ export function getDocument(rootElement?: HTMLElement | null): Document | undefi
// @public
export function getElementIndexPath(fromElement: HTMLElement, toElement: HTMLElement): number[];
+export { getEventTarget }
+
// @public
-export function getFirstFocusable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean): HTMLElement | null;
+export function getFirstFocusable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean, includeShadowRoots?: boolean): HTMLElement | null;
// @public
-export function getFirstTabbable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean, checkNode?: boolean): HTMLElement | null;
+export function getFirstTabbable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean, checkNode?: boolean, includeShadowRoots?: boolean): HTMLElement | null;
// @public
export function getFirstVisibleElementFromSelector(selector: string): Element | undefined;
@@ -354,10 +360,10 @@ export function getInitials(displayName: string | undefined | null, isRtl: boole
export function getLanguage(persistenceType?: 'localStorage' | 'sessionStorage' | 'none'): string | null;
// @public
-export function getLastFocusable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean): HTMLElement | null;
+export function getLastFocusable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean, includeShadowRoots?: boolean): HTMLElement | null;
// @public
-export function getLastTabbable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean, checkNode?: boolean): HTMLElement | null;
+export function getLastTabbable(rootElement: HTMLElement, currentElement: HTMLElement, includeElementsInFocusZones?: boolean, checkNode?: boolean, includeShadowRoots?: boolean): HTMLElement | null;
// @public
export function getNativeElementProps>(tagName: string, props: {}, excludedPropNames?: string[]): TAttributes;
@@ -366,12 +372,12 @@ export function getNativeElementProps>(props: Record, allowedPropNames: string[] | Record, excludedPropNames?: string[]): T;
// @public
-export function getNextElement(rootElement: HTMLElement, currentElement: HTMLElement | null, checkNode?: boolean, suppressParentTraversal?: boolean, suppressChildTraversal?: boolean, includeElementsInFocusZones?: boolean, allowFocusRoot?: boolean, tabbable?: boolean, bypassHiddenElements?: boolean): HTMLElement | null;
+export function getNextElement(rootElement: HTMLElement, currentElement: HTMLElement | null, checkNode?: boolean, suppressParentTraversal?: boolean, suppressChildTraversal?: boolean, includeElementsInFocusZones?: boolean, allowFocusRoot?: boolean, tabbable?: boolean, bypassHiddenElements?: boolean, includeShadowRoots?: boolean): HTMLElement | null;
export { getParent }
// @public
-export function getPreviousElement(rootElement: HTMLElement, currentElement: HTMLElement | null, checkNode?: boolean, suppressParentTraversal?: boolean, traverseChildren?: boolean, includeElementsInFocusZones?: boolean, allowFocusRoot?: boolean, tabbable?: boolean): HTMLElement | null;
+export function getPreviousElement(rootElement: HTMLElement, currentElement: HTMLElement | null, checkNode?: boolean, suppressParentTraversal?: boolean, traverseChildren?: boolean, includeElementsInFocusZones?: boolean, allowFocusRoot?: boolean, tabbable?: boolean, includeShadowRoots?: boolean): HTMLElement | null;
// @public
export function getPropsWithDefaults(defaultProps: Partial, propsWithoutDefaults: TProps): TProps;
@@ -807,7 +813,7 @@ export function isElementFocusSubZone(element?: HTMLElement): boolean;
export function isElementFocusZone(element?: HTMLElement): boolean;
// @public
-export function isElementTabbable(element: HTMLElement, checkTabIndex?: boolean): boolean;
+export function isElementTabbable(element: HTMLElement, checkTabIndex?: boolean, checkShadowRoot?: boolean): boolean;
// @public
export function isElementVisible(element: HTMLElement | undefined | null): boolean;
@@ -1021,6 +1027,11 @@ export function mergeSettings(oldSettings?: ISettings, newSettings?: ISettings |
// @public
export const MergeStylesRootProvider: React_2.FC;
+// Warning: (ae-forgotten-export) The symbol "MergeStylesShadowRootContextValue" needs to be exported by the entry point index.d.ts
+//
+// @public (undocumented)
+export const MergeStylesShadowRootContext: React_2.Context;
+
// Warning: (ae-forgotten-export) The symbol "MergeStylesShadowRootProviderProps" needs to be exported by the entry point index.d.ts
//
// @public
@@ -1294,8 +1305,6 @@ export const useIsomorphicLayoutEffect: typeof React_2.useEffect;
// @public
export const useMergeStylesRootStylesheets: () => Map;
-// Warning: (ae-forgotten-export) The symbol "MergeStylesShadowRootContextValue" needs to be exported by the entry point index.d.ts
-//
// @public
export const useMergeStylesShadowRootContext: () => MergeStylesShadowRootContextValue | undefined;
diff --git a/packages/utilities/src/dom.ts b/packages/utilities/src/dom.ts
index a1ceab1d134723..8738039b4d6ff5 100644
--- a/packages/utilities/src/dom.ts
+++ b/packages/utilities/src/dom.ts
@@ -2,8 +2,10 @@ export type { IVirtualElement } from './dom/IVirtualElement';
export { elementContains } from './dom/elementContains';
export { elementContainsAttribute } from './dom/elementContainsAttribute';
export { findElementRecursive } from './dom/findElementRecursive';
+export { getActiveElement } from './dom/getActiveElement';
export { getChildren } from './dom/getChildren';
export { getDocument } from './dom/getDocument';
+export { getEventTarget } from './dom/getEventTarget';
export { getFirstVisibleElementFromSelector } from './dom/getFirstVisibleElementFromSelector';
export { getParent } from './dom/getParent';
export { getRect } from './dom/getRect';
diff --git a/packages/utilities/src/dom/getActiveElement.ts b/packages/utilities/src/dom/getActiveElement.ts
new file mode 100644
index 00000000000000..2d5cba107205f2
--- /dev/null
+++ b/packages/utilities/src/dom/getActiveElement.ts
@@ -0,0 +1 @@
+export { getActiveElement } from '@fluentui/dom-utilities';
diff --git a/packages/utilities/src/dom/getEventTarget.ts b/packages/utilities/src/dom/getEventTarget.ts
new file mode 100644
index 00000000000000..d08503065355ce
--- /dev/null
+++ b/packages/utilities/src/dom/getEventTarget.ts
@@ -0,0 +1 @@
+export { getEventTarget } from '@fluentui/dom-utilities';
diff --git a/packages/utilities/src/focus.test.tsx b/packages/utilities/src/focus.test.tsx
index 3c20ef0f3fa286..1084976d414c9a 100644
--- a/packages/utilities/src/focus.test.tsx
+++ b/packages/utilities/src/focus.test.tsx
@@ -19,6 +19,35 @@ function renderIntoDocument(element: React.ReactElement<{}>, container: HTMLElem
return renderedDOM;
}
+// JSDOM does not currently set `delegatesFocus`
+// https://github.com/jsdom/jsdom/blob/b7683ed68ebe259cd2c68e5faf12d484a785f45f/lib/jsdom/living/nodes/Element-impl.js#L420-L424
+function createDivWithShadowRoot(initOptions: ShadowRootInit): HTMLElement {
+ const div = {
+ getAttribute: (qualifiedName: string): string | null => null,
+ shadowRoot: {
+ mode: initOptions.mode,
+ delegatesFocus: initOptions.delegatesFocus,
+ },
+ };
+
+ return div as HTMLElement;
+}
+
+function makeShadowDiv(innerHTML: string): React.FC {
+ const ShadowDiv = () => {
+ const setRef = (node: HTMLElement | null) => {
+ if (node) {
+ node.attachShadow({ mode: 'open' });
+ node.shadowRoot!.innerHTML = innerHTML;
+ }
+ };
+
+ return ;
+ };
+
+ return ShadowDiv;
+}
+
describe('isElementVisible', () => {
let testContainer: HTMLElement | undefined;
@@ -153,6 +182,25 @@ describe('isElementTabbable', () => {
expect(isElementTabbable(button, true)).toEqual(false);
});
+
+ it('returns true for elements with shadowRoot.delegatesFocus=true', () => {
+ const div = createDivWithShadowRoot({ mode: 'open', delegatesFocus: true });
+
+ expect(div.shadowRoot?.delegatesFocus).toEqual(true);
+ expect(isElementTabbable(div)).toEqual(true);
+ });
+
+ it('returns true for elements with shadowRoot.delegatesFocus=false', () => {
+ const div = createDivWithShadowRoot({ mode: 'open' });
+
+ expect(isElementTabbable(div)).toEqual(false);
+ });
+
+ it('returns true for elements with shadowRoot.delegatesFocus=true when set to ignore shadow roots', () => {
+ const div = createDivWithShadowRoot({ mode: 'open', delegatesFocus: true });
+
+ expect(isElementTabbable(div, undefined, false)).toEqual(false);
+ });
});
describe('focusAsync', () => {
@@ -339,6 +387,36 @@ describe('getFirstTabbable', () => {
expect(getFirstTabbable(parent, buttonA, true, false)).toEqual(buttonB);
});
+ it('focuses on the next tabbable item in shadow DOM', () => {
+ testContainer = createTestContainer();
+
+ const innerHTML = `
+
+
+
+ `;
+ const ShadowDiv = makeShadowDiv(innerHTML);
+
+ const container = renderIntoDocument(
+
+
+
,
+ testContainer,
+ );
+
+ const parent = container.querySelector('.parent') as HTMLElement;
+ const buttonA = parent?.shadowRoot?.querySelector('.a') as HTMLElement;
+ const buttonB = parent?.shadowRoot?.querySelector('.b') as HTMLElement;
+
+ expect(getFirstTabbable(parent, buttonA, true, false, true)).toEqual(buttonB);
+ });
+
it('does not focus on an item with tabIndex of -1', () => {
testContainer = createTestContainer();
const container = renderIntoDocument(
@@ -401,6 +479,36 @@ describe('getLastTabbable', () => {
expect(getLastTabbable(parent, buttonC, true, false)).toEqual(buttonB);
});
+ it('focuses on the last tabbable item in shadow DOM', () => {
+ testContainer = createTestContainer();
+
+ const innerHTML = `
+
+
+
+ `;
+ const ShadowDiv = makeShadowDiv(innerHTML);
+
+ const container = renderIntoDocument(
+
+
+
,
+ testContainer,
+ );
+
+ const parent = container.querySelector('.parent') as HTMLElement;
+ const buttonB = parent?.shadowRoot?.querySelector('.b') as HTMLElement;
+ const buttonC = parent?.shadowRoot?.querySelector('.c') as HTMLElement;
+
+ expect(getLastTabbable(parent, buttonC, true, false, true)).toEqual(buttonB);
+ });
+
it('does not focus on an item with tabIndex of -1', () => {
testContainer = createTestContainer();
const container = renderIntoDocument(
diff --git a/packages/utilities/src/focus.ts b/packages/utilities/src/focus.ts
index e3ab748badb23b..62d053b862091d 100644
--- a/packages/utilities/src/focus.ts
+++ b/packages/utilities/src/focus.ts
@@ -18,6 +18,7 @@ export function getFirstFocusable(
rootElement: HTMLElement,
currentElement: HTMLElement,
includeElementsInFocusZones?: boolean,
+ includeShadowRoots?: boolean,
): HTMLElement | null {
return getNextElement(
rootElement,
@@ -26,6 +27,10 @@ export function getFirstFocusable(
false /*suppressParentTraversal*/,
false /*suppressChildTraversal*/,
includeElementsInFocusZones,
+ undefined,
+ undefined,
+ undefined,
+ includeShadowRoots,
);
}
@@ -38,6 +43,7 @@ export function getLastFocusable(
rootElement: HTMLElement,
currentElement: HTMLElement,
includeElementsInFocusZones?: boolean,
+ includeShadowRoots?: boolean,
): HTMLElement | null {
return getPreviousElement(
rootElement,
@@ -46,6 +52,9 @@ export function getLastFocusable(
false /*suppressParentTraversal*/,
true /*traverseChildren*/,
includeElementsInFocusZones,
+ undefined,
+ undefined,
+ includeShadowRoots,
);
}
@@ -64,6 +73,7 @@ export function getFirstTabbable(
currentElement: HTMLElement,
includeElementsInFocusZones?: boolean,
checkNode: boolean = true,
+ includeShadowRoots?: boolean,
): HTMLElement | null {
return getNextElement(
rootElement,
@@ -74,6 +84,8 @@ export function getFirstTabbable(
includeElementsInFocusZones,
false /*allowFocusRoot*/,
true /*tabbable*/,
+ undefined,
+ includeShadowRoots,
);
}
@@ -92,6 +104,7 @@ export function getLastTabbable(
currentElement: HTMLElement,
includeElementsInFocusZones?: boolean,
checkNode: boolean = true,
+ includeShadowRoots?: boolean,
): HTMLElement | null {
return getPreviousElement(
rootElement,
@@ -102,6 +115,7 @@ export function getLastTabbable(
includeElementsInFocusZones,
false /*allowFocusRoot*/,
true /*tabbable*/,
+ includeShadowRoots,
);
}
@@ -113,7 +127,11 @@ export function getLastTabbable(
* @param bypassHiddenElements - If true, focus will be not be set on hidden elements.
* @returns True if focus was set, false if it was not.
*/
-export function focusFirstChild(rootElement: HTMLElement, bypassHiddenElements?: boolean): boolean {
+export function focusFirstChild(
+ rootElement: HTMLElement,
+ bypassHiddenElements?: boolean,
+ includeShadowRoots?: boolean,
+): boolean {
let element: HTMLElement | null = getNextElement(
rootElement,
rootElement,
@@ -124,6 +142,7 @@ export function focusFirstChild(rootElement: HTMLElement, bypassHiddenElements?:
undefined,
undefined,
bypassHiddenElements,
+ includeShadowRoots,
);
if (element) {
@@ -148,6 +167,7 @@ export function getPreviousElement(
includeElementsInFocusZones?: boolean,
allowFocusRoot?: boolean,
tabbable?: boolean,
+ includeShadowRoots?: boolean,
): HTMLElement | null {
if (!currentElement || (!allowFocusRoot && currentElement === rootElement)) {
return null;
@@ -161,19 +181,23 @@ export function getPreviousElement(
isCurrentElementVisible &&
(includeElementsInFocusZones || !(isElementFocusZone(currentElement) || isElementFocusSubZone(currentElement)))
) {
+ const lastElementChild = (currentElement.lastElementChild ||
+ (includeShadowRoots && currentElement.shadowRoot?.lastElementChild)) as HTMLElement;
+
const childMatch = getPreviousElement(
rootElement,
- currentElement.lastElementChild as HTMLElement,
+ lastElementChild,
true,
true,
true,
includeElementsInFocusZones,
allowFocusRoot,
tabbable,
+ includeShadowRoots,
);
if (childMatch) {
- if ((tabbable && isElementTabbable(childMatch, true)) || !tabbable) {
+ if ((tabbable && isElementTabbable(childMatch, true, includeShadowRoots)) || !tabbable) {
return childMatch;
}
@@ -186,6 +210,7 @@ export function getPreviousElement(
includeElementsInFocusZones,
allowFocusRoot,
tabbable,
+ includeShadowRoots,
);
if (childMatchSiblingMatch) {
return childMatchSiblingMatch;
@@ -207,6 +232,7 @@ export function getPreviousElement(
includeElementsInFocusZones,
allowFocusRoot,
tabbable,
+ includeShadowRoots,
);
if (childMatchParentMatch) {
@@ -219,7 +245,7 @@ export function getPreviousElement(
}
// Check the current node, if it's not the first traversal.
- if (checkNode && isCurrentElementVisible && isElementTabbable(currentElement, tabbable)) {
+ if (checkNode && isCurrentElementVisible && isElementTabbable(currentElement, tabbable, includeShadowRoots)) {
return currentElement;
}
@@ -233,6 +259,7 @@ export function getPreviousElement(
includeElementsInFocusZones,
allowFocusRoot,
tabbable,
+ includeShadowRoots,
);
if (siblingMatch) {
@@ -250,6 +277,7 @@ export function getPreviousElement(
includeElementsInFocusZones,
allowFocusRoot,
tabbable,
+ includeShadowRoots,
);
}
@@ -273,6 +301,7 @@ export function getNextElement(
allowFocusRoot?: boolean,
tabbable?: boolean,
bypassHiddenElements?: boolean,
+ includeShadowRoots?: boolean,
): HTMLElement | null {
if (!currentElement || (currentElement === rootElement && suppressChildTraversal && !allowFocusRoot)) {
return null;
@@ -283,7 +312,7 @@ export function getNextElement(
let isCurrentElementVisible = checkElementVisibility(currentElement);
// Check the current node, if it's not the first traversal.
- if (checkNode && isCurrentElementVisible && isElementTabbable(currentElement, tabbable)) {
+ if (checkNode && isCurrentElementVisible && isElementTabbable(currentElement, tabbable, includeShadowRoots)) {
return currentElement;
}
@@ -293,9 +322,12 @@ export function getNextElement(
isCurrentElementVisible &&
(includeElementsInFocusZones || !(isElementFocusZone(currentElement) || isElementFocusSubZone(currentElement)))
) {
+ const firstElementchild = (currentElement.firstElementChild ||
+ (includeShadowRoots && currentElement.shadowRoot?.firstElementChild)) as HTMLElement;
+
const childMatch = getNextElement(
rootElement,
- currentElement.firstElementChild as HTMLElement,
+ firstElementchild,
true,
true,
false,
@@ -303,6 +335,7 @@ export function getNextElement(
allowFocusRoot,
tabbable,
bypassHiddenElements,
+ includeShadowRoots,
);
if (childMatch) {
@@ -325,6 +358,7 @@ export function getNextElement(
allowFocusRoot,
tabbable,
bypassHiddenElements,
+ includeShadowRoots,
);
if (siblingMatch) {
@@ -342,6 +376,7 @@ export function getNextElement(
allowFocusRoot,
tabbable,
bypassHiddenElements,
+ includeShadowRoots,
);
}
@@ -398,7 +433,11 @@ export function isElementVisibleAndNotHidden(element: HTMLElement | undefined |
*
* @public
*/
-export function isElementTabbable(element: HTMLElement, checkTabIndex?: boolean): boolean {
+export function isElementTabbable(
+ element: HTMLElement,
+ checkTabIndex?: boolean,
+ checkShadowRoot: boolean = true,
+): boolean {
// If this element is null or is disabled, it is not considered tabbable.
if (!element || (element as HTMLButtonElement).disabled) {
return false;
@@ -417,6 +456,7 @@ export function isElementTabbable(element: HTMLElement, checkTabIndex?: boolean)
let isFocusableAttribute = element.getAttribute ? element.getAttribute(IS_FOCUSABLE_ATTRIBUTE) : null;
let isTabIndexSet = tabIndexAttributeValue !== null && tabIndex >= 0;
+ let delegatesFocus = checkShadowRoot && element.shadowRoot ? !!element.shadowRoot.delegatesFocus : false;
const result =
!!element &&
@@ -427,7 +467,8 @@ export function isElementTabbable(element: HTMLElement, checkTabIndex?: boolean)
element.tagName === 'TEXTAREA' ||
element.tagName === 'SELECT' ||
isFocusableAttribute === 'true' ||
- isTabIndexSet);
+ isTabIndexSet ||
+ delegatesFocus);
return checkTabIndex ? tabIndex !== -1 && result : result;
}
diff --git a/packages/utilities/src/index.ts b/packages/utilities/src/index.ts
index 508d80d2012ee0..cdc08863809e29 100644
--- a/packages/utilities/src/index.ts
+++ b/packages/utilities/src/index.ts
@@ -83,8 +83,10 @@ export {
elementContains,
elementContainsAttribute,
findElementRecursive,
+ getActiveElement,
getChildren,
getDocument,
+ getEventTarget,
getFirstVisibleElementFromSelector,
getParent,
getRect,
@@ -237,6 +239,7 @@ import './version';
export type { IStyleFunctionOrObject, Omit } from '@fluentui/merge-styles';
export {
+ MergeStylesShadowRootContext,
MergeStylesShadowRootProvider,
useAdoptedStylesheet,
useHasMergeStylesShadowRootContext,
diff --git a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx
index db64ef7be0f227..115db9ec0f0777 100644
--- a/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx
+++ b/packages/utilities/src/shadowDom/MergeStylesShadowRootContext.tsx
@@ -24,7 +24,9 @@ export type MergeStylesShadowRootContextValue = {
shadowRoot?: ShadowRoot | null;
};
-const MergeStylesShadowRootContext = React.createContext(undefined);
+export const MergeStylesShadowRootContext = React.createContext(
+ undefined,
+);
export type MergeStylesShadowRootProviderProps = {
/**