@fluentui/react-tabster 9.24.5 → 9.24.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -2
- package/lib/focus/focusVisiblePolyfill.js +2 -0
- package/lib/focus/focusVisiblePolyfill.js.map +1 -1
- package/lib/hooks/useTabster.js +2 -2
- package/lib/hooks/useTabster.js.map +1 -1
- package/lib-commonjs/focus/focusVisiblePolyfill.js +2 -0
- package/lib-commonjs/focus/focusVisiblePolyfill.js.map +1 -1
- package/lib-commonjs/hooks/useTabster.js +1 -1
- package/lib-commonjs/hooks/useTabster.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -1,12 +1,30 @@
|
|
1
1
|
# Change Log - @fluentui/react-tabster
|
2
2
|
|
3
|
-
This log was last generated on Wed,
|
3
|
+
This log was last generated on Wed, 14 May 2025 18:45:48 GMT and should not be manually modified.
|
4
4
|
|
5
5
|
<!-- Start content -->
|
6
6
|
|
7
|
+
## [9.24.7](https://github.com/microsoft/fluentui/tree/@fluentui/react-tabster_v9.24.7)
|
8
|
+
|
9
|
+
Wed, 14 May 2025 18:45:48 GMT
|
10
|
+
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tabster_v9.24.6..@fluentui/react-tabster_v9.24.7)
|
11
|
+
|
12
|
+
### Patches
|
13
|
+
|
14
|
+
- Apply focus visible attribute on navigation state change ([PR #34426](https://github.com/microsoft/fluentui/pull/34426) by lingfangao@hotmail.com)
|
15
|
+
|
16
|
+
## [9.24.6](https://github.com/microsoft/fluentui/tree/@fluentui/react-tabster_v9.24.6)
|
17
|
+
|
18
|
+
Thu, 24 Apr 2025 09:59:45 GMT
|
19
|
+
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tabster_v9.24.5..@fluentui/react-tabster_v9.24.6)
|
20
|
+
|
21
|
+
### Patches
|
22
|
+
|
23
|
+
- fix: use useLayoutEffect() for Tabster creation ([PR #34315](https://github.com/microsoft/fluentui/pull/34315) by olfedias@microsoft.com)
|
24
|
+
|
7
25
|
## [9.24.5](https://github.com/microsoft/fluentui/tree/@fluentui/react-tabster_v9.24.5)
|
8
26
|
|
9
|
-
Wed, 16 Apr 2025 19:
|
27
|
+
Wed, 16 Apr 2025 19:42:18 GMT
|
10
28
|
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tabster_v9.24.4..@fluentui/react-tabster_v9.24.5)
|
11
29
|
|
12
30
|
### Patches
|
@@ -30,6 +30,8 @@ import { FOCUS_VISIBLE_ATTR } from './constants';
|
|
30
30
|
keyborg.subscribe((isNavigatingWithKeyboard)=>{
|
31
31
|
if (!isNavigatingWithKeyboard) {
|
32
32
|
disposeCurrentElement();
|
33
|
+
} else {
|
34
|
+
registerElementIfNavigating(targetWindow.document.activeElement);
|
33
35
|
}
|
34
36
|
});
|
35
37
|
// Keyborg's focusin event is delegated so it's only registered once on the window
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/focus/focusVisiblePolyfill.ts"],"sourcesContent":["import { isHTMLElement } from '@fluentui/react-utilities';\nimport { KEYBORG_FOCUSIN, KeyborgFocusInEvent, createKeyborg, disposeKeyborg } from 'keyborg';\n\nimport { FOCUS_VISIBLE_ATTR } from './constants';\n\n/**\n * Because `addEventListener` type override falls back to 2nd definition (evt name is unknown string literal)\n * evt is being typed as a base class of MouseEvent -> `Event`.\n * This type is used to override `listener` calls to make TS happy\n */\ntype ListenerOverride = (evt: Event) => void;\n\ntype FocusVisibleState = {\n /**\n * Current element with focus visible in state\n */\n current: HTMLElement | undefined;\n};\n\ntype HTMLElementWithFocusVisibleScope = {\n focusVisible: boolean | undefined;\n} & HTMLElement;\n\n/**\n * @internal\n * @param scope - Applies the ponyfill to all DOM children\n * @param targetWindow - window\n */\nexport function applyFocusVisiblePolyfill(scope: HTMLElement, targetWindow: Window): () => void {\n if (alreadyInScope(scope)) {\n // Focus visible polyfill already applied at this scope\n return () => undefined;\n }\n\n const state: FocusVisibleState = {\n current: undefined,\n };\n\n const keyborg = createKeyborg(targetWindow);\n\n function registerElementIfNavigating(el: EventTarget | HTMLElement | null) {\n if (keyborg.isNavigatingWithKeyboard() && isHTMLElement(el)) {\n state.current = el;\n el.setAttribute(FOCUS_VISIBLE_ATTR, '');\n }\n }\n\n function disposeCurrentElement() {\n if (state.current) {\n state.current.removeAttribute(FOCUS_VISIBLE_ATTR);\n state.current = undefined;\n }\n }\n\n // When navigation mode changes remove the focus-visible selector\n keyborg.subscribe(isNavigatingWithKeyboard => {\n if (!isNavigatingWithKeyboard) {\n disposeCurrentElement();\n }\n });\n\n // Keyborg's focusin event is delegated so it's only registered once on the window\n // and contains metadata about the focus event\n const keyborgListener = (e: KeyborgFocusInEvent) => {\n disposeCurrentElement();\n const target = e.composedPath()[0];\n registerElementIfNavigating(target);\n };\n\n // Make sure that when focus leaves the scope, the focus visible class is removed\n const blurListener = (e: FocusEvent) => {\n if (!e.relatedTarget || (isHTMLElement(e.relatedTarget) && !scope.contains(e.relatedTarget))) {\n disposeCurrentElement();\n }\n };\n\n scope.addEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.addEventListener('focusout', blurListener);\n (scope as HTMLElementWithFocusVisibleScope).focusVisible = true;\n\n if (scope.contains(targetWindow.document.activeElement)) {\n registerElementIfNavigating(targetWindow.document.activeElement);\n }\n\n // Return disposer\n return () => {\n disposeCurrentElement();\n\n scope.removeEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.removeEventListener('focusout', blurListener);\n delete (scope as HTMLElementWithFocusVisibleScope).focusVisible;\n\n disposeKeyborg(keyborg);\n };\n}\n\nfunction alreadyInScope(el: HTMLElement | null | undefined): boolean {\n if (!el) {\n return false;\n }\n\n if ((el as HTMLElementWithFocusVisibleScope).focusVisible) {\n return true;\n }\n\n return alreadyInScope(el?.parentElement);\n}\n"],"names":["isHTMLElement","KEYBORG_FOCUSIN","createKeyborg","disposeKeyborg","FOCUS_VISIBLE_ATTR","applyFocusVisiblePolyfill","scope","targetWindow","alreadyInScope","undefined","state","current","keyborg","registerElementIfNavigating","el","isNavigatingWithKeyboard","setAttribute","disposeCurrentElement","removeAttribute","subscribe","keyborgListener","e","target","composedPath","blurListener","relatedTarget","contains","addEventListener","focusVisible","
|
1
|
+
{"version":3,"sources":["../src/focus/focusVisiblePolyfill.ts"],"sourcesContent":["import { isHTMLElement } from '@fluentui/react-utilities';\nimport { KEYBORG_FOCUSIN, KeyborgFocusInEvent, createKeyborg, disposeKeyborg } from 'keyborg';\n\nimport { FOCUS_VISIBLE_ATTR } from './constants';\n\n/**\n * Because `addEventListener` type override falls back to 2nd definition (evt name is unknown string literal)\n * evt is being typed as a base class of MouseEvent -> `Event`.\n * This type is used to override `listener` calls to make TS happy\n */\ntype ListenerOverride = (evt: Event) => void;\n\ntype FocusVisibleState = {\n /**\n * Current element with focus visible in state\n */\n current: HTMLElement | undefined;\n};\n\ntype HTMLElementWithFocusVisibleScope = {\n focusVisible: boolean | undefined;\n} & HTMLElement;\n\n/**\n * @internal\n * @param scope - Applies the ponyfill to all DOM children\n * @param targetWindow - window\n */\nexport function applyFocusVisiblePolyfill(scope: HTMLElement, targetWindow: Window): () => void {\n if (alreadyInScope(scope)) {\n // Focus visible polyfill already applied at this scope\n return () => undefined;\n }\n\n const state: FocusVisibleState = {\n current: undefined,\n };\n\n const keyborg = createKeyborg(targetWindow);\n\n function registerElementIfNavigating(el: EventTarget | HTMLElement | null) {\n if (keyborg.isNavigatingWithKeyboard() && isHTMLElement(el)) {\n state.current = el;\n el.setAttribute(FOCUS_VISIBLE_ATTR, '');\n }\n }\n\n function disposeCurrentElement() {\n if (state.current) {\n state.current.removeAttribute(FOCUS_VISIBLE_ATTR);\n state.current = undefined;\n }\n }\n\n // When navigation mode changes remove the focus-visible selector\n keyborg.subscribe(isNavigatingWithKeyboard => {\n if (!isNavigatingWithKeyboard) {\n disposeCurrentElement();\n } else {\n registerElementIfNavigating(targetWindow.document.activeElement);\n }\n });\n\n // Keyborg's focusin event is delegated so it's only registered once on the window\n // and contains metadata about the focus event\n const keyborgListener = (e: KeyborgFocusInEvent) => {\n disposeCurrentElement();\n const target = e.composedPath()[0];\n registerElementIfNavigating(target);\n };\n\n // Make sure that when focus leaves the scope, the focus visible class is removed\n const blurListener = (e: FocusEvent) => {\n if (!e.relatedTarget || (isHTMLElement(e.relatedTarget) && !scope.contains(e.relatedTarget))) {\n disposeCurrentElement();\n }\n };\n\n scope.addEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.addEventListener('focusout', blurListener);\n (scope as HTMLElementWithFocusVisibleScope).focusVisible = true;\n\n if (scope.contains(targetWindow.document.activeElement)) {\n registerElementIfNavigating(targetWindow.document.activeElement);\n }\n\n // Return disposer\n return () => {\n disposeCurrentElement();\n\n scope.removeEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.removeEventListener('focusout', blurListener);\n delete (scope as HTMLElementWithFocusVisibleScope).focusVisible;\n\n disposeKeyborg(keyborg);\n };\n}\n\nfunction alreadyInScope(el: HTMLElement | null | undefined): boolean {\n if (!el) {\n return false;\n }\n\n if ((el as HTMLElementWithFocusVisibleScope).focusVisible) {\n return true;\n }\n\n return alreadyInScope(el?.parentElement);\n}\n"],"names":["isHTMLElement","KEYBORG_FOCUSIN","createKeyborg","disposeKeyborg","FOCUS_VISIBLE_ATTR","applyFocusVisiblePolyfill","scope","targetWindow","alreadyInScope","undefined","state","current","keyborg","registerElementIfNavigating","el","isNavigatingWithKeyboard","setAttribute","disposeCurrentElement","removeAttribute","subscribe","document","activeElement","keyborgListener","e","target","composedPath","blurListener","relatedTarget","contains","addEventListener","focusVisible","removeEventListener","parentElement"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,aAAa,QAAQ,4BAA4B;AAC1D,SAASC,eAAe,EAAuBC,aAAa,EAAEC,cAAc,QAAQ,UAAU;AAE9F,SAASC,kBAAkB,QAAQ,cAAc;AAoBjD;;;;CAIC,GACD,OAAO,SAASC,0BAA0BC,KAAkB,EAAEC,YAAoB;IAChF,IAAIC,eAAeF,QAAQ;QACzB,uDAAuD;QACvD,OAAO,IAAMG;IACf;IAEA,MAAMC,QAA2B;QAC/BC,SAASF;IACX;IAEA,MAAMG,UAAUV,cAAcK;IAE9B,SAASM,4BAA4BC,EAAoC;QACvE,IAAIF,QAAQG,wBAAwB,MAAMf,cAAcc,KAAK;YAC3DJ,MAAMC,OAAO,GAAGG;YAChBA,GAAGE,YAAY,CAACZ,oBAAoB;QACtC;IACF;IAEA,SAASa;QACP,IAAIP,MAAMC,OAAO,EAAE;YACjBD,MAAMC,OAAO,CAACO,eAAe,CAACd;YAC9BM,MAAMC,OAAO,GAAGF;QAClB;IACF;IAEA,iEAAiE;IACjEG,QAAQO,SAAS,CAACJ,CAAAA;QAChB,IAAI,CAACA,0BAA0B;YAC7BE;QACF,OAAO;YACLJ,4BAA4BN,aAAaa,QAAQ,CAACC,aAAa;QACjE;IACF;IAEA,kFAAkF;IAClF,8CAA8C;IAC9C,MAAMC,kBAAkB,CAACC;QACvBN;QACA,MAAMO,SAASD,EAAEE,YAAY,EAAE,CAAC,EAAE;QAClCZ,4BAA4BW;IAC9B;IAEA,iFAAiF;IACjF,MAAME,eAAe,CAACH;QACpB,IAAI,CAACA,EAAEI,aAAa,IAAK3B,cAAcuB,EAAEI,aAAa,KAAK,CAACrB,MAAMsB,QAAQ,CAACL,EAAEI,aAAa,GAAI;YAC5FV;QACF;IACF;IAEAX,MAAMuB,gBAAgB,CAAC5B,iBAAiBqB;IACxChB,MAAMuB,gBAAgB,CAAC,YAAYH;IAClCpB,MAA2CwB,YAAY,GAAG;IAE3D,IAAIxB,MAAMsB,QAAQ,CAACrB,aAAaa,QAAQ,CAACC,aAAa,GAAG;QACvDR,4BAA4BN,aAAaa,QAAQ,CAACC,aAAa;IACjE;IAEA,kBAAkB;IAClB,OAAO;QACLJ;QAEAX,MAAMyB,mBAAmB,CAAC9B,iBAAiBqB;QAC3ChB,MAAMyB,mBAAmB,CAAC,YAAYL;QACtC,OAAO,AAACpB,MAA2CwB,YAAY;QAE/D3B,eAAeS;IACjB;AACF;AAEA,SAASJ,eAAeM,EAAkC;IACxD,IAAI,CAACA,IAAI;QACP,OAAO;IACT;IAEA,IAAI,AAACA,GAAwCgB,YAAY,EAAE;QACzD,OAAO;IACT;IAEA,OAAOtB,eAAeM,eAAAA,yBAAAA,GAAIkB,aAAa;AACzC"}
|
package/lib/hooks/useTabster.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import * as React from 'react';
|
2
2
|
import { createTabster, disposeTabster } from 'tabster';
|
3
3
|
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';
|
4
|
-
import { getParent, usePrevious } from '@fluentui/react-utilities';
|
4
|
+
import { getParent, useIsomorphicLayoutEffect, usePrevious } from '@fluentui/react-utilities';
|
5
5
|
const DEFAULT_FACTORY = (tabster)=>{
|
6
6
|
return tabster;
|
7
7
|
};
|
@@ -29,7 +29,7 @@ const DEFAULT_FACTORY = (tabster)=>{
|
|
29
29
|
export function useTabster(factory = DEFAULT_FACTORY) {
|
30
30
|
const { targetDocument } = useFluent();
|
31
31
|
const factoryResultRef = React.useRef(null);
|
32
|
-
|
32
|
+
useIsomorphicLayoutEffect(()=>{
|
33
33
|
const tabster = createTabsterWithConfig(targetDocument);
|
34
34
|
if (tabster) {
|
35
35
|
factoryResultRef.current = factory(tabster);
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useTabster.ts"],"sourcesContent":["import * as React from 'react';\nimport { createTabster, disposeTabster, Types as TabsterTypes } from 'tabster';\nimport { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';\nimport { getParent, usePrevious } from '@fluentui/react-utilities';\n\ninterface WindowWithTabsterShadowDOMAPI extends Window {\n __tabsterShadowDOMAPI?: TabsterTypes.DOMAPI;\n}\n\ntype UseTabsterFactory<FactoryResult> = (tabster: TabsterTypes.TabsterCore) => FactoryResult;\n\nconst DEFAULT_FACTORY: UseTabsterFactory<TabsterTypes.TabsterCore> = tabster => {\n return tabster;\n};\n\n/**\n * Creates a tabster instance with the provided configuration\n *\n * @internal\n * @param targetDocument\n */\nexport function createTabsterWithConfig(targetDocument: Document | undefined): TabsterTypes.TabsterCore | undefined {\n const defaultView = targetDocument?.defaultView || undefined;\n const shadowDOMAPI = (defaultView as WindowWithTabsterShadowDOMAPI | undefined)?.__tabsterShadowDOMAPI;\n\n if (defaultView) {\n return createTabster(defaultView, {\n autoRoot: {},\n controlTab: false,\n getParent,\n checkUncontrolledTrappingFocus: element =>\n !!element.firstElementChild?.hasAttribute('data-is-focus-trap-zone-bumper'),\n DOMAPI: shadowDOMAPI,\n });\n }\n}\n\n/**\n * Tries to get a tabster instance on the current window or creates a new one\n * Since Tabster is single instance only, feel free to call this hook to ensure Tabster exists if necessary\n *\n * @internal\n * @returns Tabster a ref to core instance or a factory result\n */\nexport function useTabster(): React.RefObject<TabsterTypes.TabsterCore | null>;\nexport function useTabster<FactoryResult>(\n factory: UseTabsterFactory<FactoryResult>,\n): React.RefObject<FactoryResult | null>;\n\nexport function useTabster<FactoryResult>(factory = DEFAULT_FACTORY) {\n const { targetDocument } = useFluent();\n const factoryResultRef = React.useRef<FactoryResult | null>(null);\n\n
|
1
|
+
{"version":3,"sources":["../src/hooks/useTabster.ts"],"sourcesContent":["import * as React from 'react';\nimport { createTabster, disposeTabster, Types as TabsterTypes } from 'tabster';\nimport { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';\nimport { getParent, useIsomorphicLayoutEffect, usePrevious } from '@fluentui/react-utilities';\n\ninterface WindowWithTabsterShadowDOMAPI extends Window {\n __tabsterShadowDOMAPI?: TabsterTypes.DOMAPI;\n}\n\ntype UseTabsterFactory<FactoryResult> = (tabster: TabsterTypes.TabsterCore) => FactoryResult;\n\nconst DEFAULT_FACTORY: UseTabsterFactory<TabsterTypes.TabsterCore> = tabster => {\n return tabster;\n};\n\n/**\n * Creates a tabster instance with the provided configuration\n *\n * @internal\n * @param targetDocument\n */\nexport function createTabsterWithConfig(targetDocument: Document | undefined): TabsterTypes.TabsterCore | undefined {\n const defaultView = targetDocument?.defaultView || undefined;\n const shadowDOMAPI = (defaultView as WindowWithTabsterShadowDOMAPI | undefined)?.__tabsterShadowDOMAPI;\n\n if (defaultView) {\n return createTabster(defaultView, {\n autoRoot: {},\n controlTab: false,\n getParent,\n checkUncontrolledTrappingFocus: element =>\n !!element.firstElementChild?.hasAttribute('data-is-focus-trap-zone-bumper'),\n DOMAPI: shadowDOMAPI,\n });\n }\n}\n\n/**\n * Tries to get a tabster instance on the current window or creates a new one\n * Since Tabster is single instance only, feel free to call this hook to ensure Tabster exists if necessary\n *\n * @internal\n * @returns Tabster a ref to core instance or a factory result\n */\nexport function useTabster(): React.RefObject<TabsterTypes.TabsterCore | null>;\nexport function useTabster<FactoryResult>(\n factory: UseTabsterFactory<FactoryResult>,\n): React.RefObject<FactoryResult | null>;\n\nexport function useTabster<FactoryResult>(factory = DEFAULT_FACTORY) {\n const { targetDocument } = useFluent();\n const factoryResultRef = React.useRef<FactoryResult | null>(null);\n\n useIsomorphicLayoutEffect(() => {\n const tabster = createTabsterWithConfig(targetDocument);\n\n if (tabster) {\n factoryResultRef.current = factory(tabster) as FactoryResult;\n\n return () => {\n disposeTabster(tabster);\n factoryResultRef.current = null;\n };\n }\n }, [targetDocument, factory]);\n\n if (process.env.NODE_ENV !== 'production') {\n // eslint-disable-next-line\n const previousFactory = usePrevious(factory);\n\n if (previousFactory !== null && previousFactory !== factory) {\n throw new Error(\n [\n '@fluentui/react-tabster: ',\n 'The factory function passed to useTabster has changed. This should not ever happen.',\n ].join('\\n'),\n );\n }\n }\n\n return factoryResultRef;\n}\n"],"names":["React","createTabster","disposeTabster","useFluent_unstable","useFluent","getParent","useIsomorphicLayoutEffect","usePrevious","DEFAULT_FACTORY","tabster","createTabsterWithConfig","targetDocument","defaultView","undefined","shadowDOMAPI","__tabsterShadowDOMAPI","autoRoot","controlTab","checkUncontrolledTrappingFocus","element","firstElementChild","hasAttribute","DOMAPI","useTabster","factory","factoryResultRef","useRef","current","process","env","NODE_ENV","previousFactory","Error","join"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,YAAYA,WAAW,QAAQ;AAC/B,SAASC,aAAa,EAAEC,cAAc,QAA+B,UAAU;AAC/E,SAASC,sBAAsBC,SAAS,QAAQ,kCAAkC;AAClF,SAASC,SAAS,EAAEC,yBAAyB,EAAEC,WAAW,QAAQ,4BAA4B;AAQ9F,MAAMC,kBAA+DC,CAAAA;IACnE,OAAOA;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASC,wBAAwBC,cAAoC;IAC1E,MAAMC,cAAcD,CAAAA,2BAAAA,qCAAAA,eAAgBC,WAAW,KAAIC;IACnD,MAAMC,eAAgBF,wBAAAA,kCAAD,AAACA,YAA2DG,qBAAqB;IAEtG,IAAIH,aAAa;QACf,OAAOX,cAAcW,aAAa;YAChCI,UAAU,CAAC;YACXC,YAAY;YACZZ;YACAa,gCAAgCC,CAAAA;oBAC5BA;uBAAF,CAAC,GAACA,6BAAAA,QAAQC,iBAAiB,cAAzBD,iDAAAA,2BAA2BE,YAAY,CAAC;;YAC5CC,QAAQR;QACV;IACF;AACF;AAcA,OAAO,SAASS,WAA0BC,UAAUhB,eAAe;IACjE,MAAM,EAAEG,cAAc,EAAE,GAAGP;IAC3B,MAAMqB,mBAAmBzB,MAAM0B,MAAM,CAAuB;IAE5DpB,0BAA0B;QACxB,MAAMG,UAAUC,wBAAwBC;QAExC,IAAIF,SAAS;YACXgB,iBAAiBE,OAAO,GAAGH,QAAQf;YAEnC,OAAO;gBACLP,eAAeO;gBACfgB,iBAAiBE,OAAO,GAAG;YAC7B;QACF;IACF,GAAG;QAAChB;QAAgBa;KAAQ;IAE5B,IAAII,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzC,2BAA2B;QAC3B,MAAMC,kBAAkBxB,YAAYiB;QAEpC,IAAIO,oBAAoB,QAAQA,oBAAoBP,SAAS;YAC3D,MAAM,IAAIQ,MACR;gBACE;gBACA;aACD,CAACC,IAAI,CAAC;QAEX;IACF;IAEA,OAAOR;AACT"}
|
@@ -36,6 +36,8 @@ function applyFocusVisiblePolyfill(scope, targetWindow) {
|
|
36
36
|
keyborg.subscribe((isNavigatingWithKeyboard)=>{
|
37
37
|
if (!isNavigatingWithKeyboard) {
|
38
38
|
disposeCurrentElement();
|
39
|
+
} else {
|
40
|
+
registerElementIfNavigating(targetWindow.document.activeElement);
|
39
41
|
}
|
40
42
|
});
|
41
43
|
// Keyborg's focusin event is delegated so it's only registered once on the window
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/focus/focusVisiblePolyfill.ts"],"sourcesContent":["import { isHTMLElement } from '@fluentui/react-utilities';\nimport { KEYBORG_FOCUSIN, KeyborgFocusInEvent, createKeyborg, disposeKeyborg } from 'keyborg';\n\nimport { FOCUS_VISIBLE_ATTR } from './constants';\n\n/**\n * Because `addEventListener` type override falls back to 2nd definition (evt name is unknown string literal)\n * evt is being typed as a base class of MouseEvent -> `Event`.\n * This type is used to override `listener` calls to make TS happy\n */\ntype ListenerOverride = (evt: Event) => void;\n\ntype FocusVisibleState = {\n /**\n * Current element with focus visible in state\n */\n current: HTMLElement | undefined;\n};\n\ntype HTMLElementWithFocusVisibleScope = {\n focusVisible: boolean | undefined;\n} & HTMLElement;\n\n/**\n * @internal\n * @param scope - Applies the ponyfill to all DOM children\n * @param targetWindow - window\n */\nexport function applyFocusVisiblePolyfill(scope: HTMLElement, targetWindow: Window): () => void {\n if (alreadyInScope(scope)) {\n // Focus visible polyfill already applied at this scope\n return () => undefined;\n }\n\n const state: FocusVisibleState = {\n current: undefined,\n };\n\n const keyborg = createKeyborg(targetWindow);\n\n function registerElementIfNavigating(el: EventTarget | HTMLElement | null) {\n if (keyborg.isNavigatingWithKeyboard() && isHTMLElement(el)) {\n state.current = el;\n el.setAttribute(FOCUS_VISIBLE_ATTR, '');\n }\n }\n\n function disposeCurrentElement() {\n if (state.current) {\n state.current.removeAttribute(FOCUS_VISIBLE_ATTR);\n state.current = undefined;\n }\n }\n\n // When navigation mode changes remove the focus-visible selector\n keyborg.subscribe(isNavigatingWithKeyboard => {\n if (!isNavigatingWithKeyboard) {\n disposeCurrentElement();\n }\n });\n\n // Keyborg's focusin event is delegated so it's only registered once on the window\n // and contains metadata about the focus event\n const keyborgListener = (e: KeyborgFocusInEvent) => {\n disposeCurrentElement();\n const target = e.composedPath()[0];\n registerElementIfNavigating(target);\n };\n\n // Make sure that when focus leaves the scope, the focus visible class is removed\n const blurListener = (e: FocusEvent) => {\n if (!e.relatedTarget || (isHTMLElement(e.relatedTarget) && !scope.contains(e.relatedTarget))) {\n disposeCurrentElement();\n }\n };\n\n scope.addEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.addEventListener('focusout', blurListener);\n (scope as HTMLElementWithFocusVisibleScope).focusVisible = true;\n\n if (scope.contains(targetWindow.document.activeElement)) {\n registerElementIfNavigating(targetWindow.document.activeElement);\n }\n\n // Return disposer\n return () => {\n disposeCurrentElement();\n\n scope.removeEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.removeEventListener('focusout', blurListener);\n delete (scope as HTMLElementWithFocusVisibleScope).focusVisible;\n\n disposeKeyborg(keyborg);\n };\n}\n\nfunction alreadyInScope(el: HTMLElement | null | undefined): boolean {\n if (!el) {\n return false;\n }\n\n if ((el as HTMLElementWithFocusVisibleScope).focusVisible) {\n return true;\n }\n\n return alreadyInScope(el?.parentElement);\n}\n"],"names":["applyFocusVisiblePolyfill","scope","targetWindow","alreadyInScope","undefined","state","current","keyborg","createKeyborg","registerElementIfNavigating","el","isNavigatingWithKeyboard","isHTMLElement","setAttribute","FOCUS_VISIBLE_ATTR","disposeCurrentElement","removeAttribute","subscribe","keyborgListener","e","target","composedPath","blurListener","relatedTarget","contains","addEventListener","KEYBORG_FOCUSIN","focusVisible","
|
1
|
+
{"version":3,"sources":["../src/focus/focusVisiblePolyfill.ts"],"sourcesContent":["import { isHTMLElement } from '@fluentui/react-utilities';\nimport { KEYBORG_FOCUSIN, KeyborgFocusInEvent, createKeyborg, disposeKeyborg } from 'keyborg';\n\nimport { FOCUS_VISIBLE_ATTR } from './constants';\n\n/**\n * Because `addEventListener` type override falls back to 2nd definition (evt name is unknown string literal)\n * evt is being typed as a base class of MouseEvent -> `Event`.\n * This type is used to override `listener` calls to make TS happy\n */\ntype ListenerOverride = (evt: Event) => void;\n\ntype FocusVisibleState = {\n /**\n * Current element with focus visible in state\n */\n current: HTMLElement | undefined;\n};\n\ntype HTMLElementWithFocusVisibleScope = {\n focusVisible: boolean | undefined;\n} & HTMLElement;\n\n/**\n * @internal\n * @param scope - Applies the ponyfill to all DOM children\n * @param targetWindow - window\n */\nexport function applyFocusVisiblePolyfill(scope: HTMLElement, targetWindow: Window): () => void {\n if (alreadyInScope(scope)) {\n // Focus visible polyfill already applied at this scope\n return () => undefined;\n }\n\n const state: FocusVisibleState = {\n current: undefined,\n };\n\n const keyborg = createKeyborg(targetWindow);\n\n function registerElementIfNavigating(el: EventTarget | HTMLElement | null) {\n if (keyborg.isNavigatingWithKeyboard() && isHTMLElement(el)) {\n state.current = el;\n el.setAttribute(FOCUS_VISIBLE_ATTR, '');\n }\n }\n\n function disposeCurrentElement() {\n if (state.current) {\n state.current.removeAttribute(FOCUS_VISIBLE_ATTR);\n state.current = undefined;\n }\n }\n\n // When navigation mode changes remove the focus-visible selector\n keyborg.subscribe(isNavigatingWithKeyboard => {\n if (!isNavigatingWithKeyboard) {\n disposeCurrentElement();\n } else {\n registerElementIfNavigating(targetWindow.document.activeElement);\n }\n });\n\n // Keyborg's focusin event is delegated so it's only registered once on the window\n // and contains metadata about the focus event\n const keyborgListener = (e: KeyborgFocusInEvent) => {\n disposeCurrentElement();\n const target = e.composedPath()[0];\n registerElementIfNavigating(target);\n };\n\n // Make sure that when focus leaves the scope, the focus visible class is removed\n const blurListener = (e: FocusEvent) => {\n if (!e.relatedTarget || (isHTMLElement(e.relatedTarget) && !scope.contains(e.relatedTarget))) {\n disposeCurrentElement();\n }\n };\n\n scope.addEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.addEventListener('focusout', blurListener);\n (scope as HTMLElementWithFocusVisibleScope).focusVisible = true;\n\n if (scope.contains(targetWindow.document.activeElement)) {\n registerElementIfNavigating(targetWindow.document.activeElement);\n }\n\n // Return disposer\n return () => {\n disposeCurrentElement();\n\n scope.removeEventListener(KEYBORG_FOCUSIN, keyborgListener as ListenerOverride);\n scope.removeEventListener('focusout', blurListener);\n delete (scope as HTMLElementWithFocusVisibleScope).focusVisible;\n\n disposeKeyborg(keyborg);\n };\n}\n\nfunction alreadyInScope(el: HTMLElement | null | undefined): boolean {\n if (!el) {\n return false;\n }\n\n if ((el as HTMLElementWithFocusVisibleScope).focusVisible) {\n return true;\n }\n\n return alreadyInScope(el?.parentElement);\n}\n"],"names":["applyFocusVisiblePolyfill","scope","targetWindow","alreadyInScope","undefined","state","current","keyborg","createKeyborg","registerElementIfNavigating","el","isNavigatingWithKeyboard","isHTMLElement","setAttribute","FOCUS_VISIBLE_ATTR","disposeCurrentElement","removeAttribute","subscribe","document","activeElement","keyborgListener","e","target","composedPath","blurListener","relatedTarget","contains","addEventListener","KEYBORG_FOCUSIN","focusVisible","removeEventListener","disposeKeyborg","parentElement"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;+BA4BgBA;;;eAAAA;;;gCA5Bc;yBACsD;2BAEjD;AAyB5B,SAASA,0BAA0BC,KAAkB,EAAEC,YAAoB;IAChF,IAAIC,eAAeF,QAAQ;QACzB,uDAAuD;QACvD,OAAO,IAAMG;IACf;IAEA,MAAMC,QAA2B;QAC/BC,SAASF;IACX;IAEA,MAAMG,UAAUC,IAAAA,sBAAa,EAACN;IAE9B,SAASO,4BAA4BC,EAAoC;QACvE,IAAIH,QAAQI,wBAAwB,MAAMC,IAAAA,6BAAa,EAACF,KAAK;YAC3DL,MAAMC,OAAO,GAAGI;YAChBA,GAAGG,YAAY,CAACC,6BAAkB,EAAE;QACtC;IACF;IAEA,SAASC;QACP,IAAIV,MAAMC,OAAO,EAAE;YACjBD,MAAMC,OAAO,CAACU,eAAe,CAACF,6BAAkB;YAChDT,MAAMC,OAAO,GAAGF;QAClB;IACF;IAEA,iEAAiE;IACjEG,QAAQU,SAAS,CAACN,CAAAA;QAChB,IAAI,CAACA,0BAA0B;YAC7BI;QACF,OAAO;YACLN,4BAA4BP,aAAagB,QAAQ,CAACC,aAAa;QACjE;IACF;IAEA,kFAAkF;IAClF,8CAA8C;IAC9C,MAAMC,kBAAkB,CAACC;QACvBN;QACA,MAAMO,SAASD,EAAEE,YAAY,EAAE,CAAC,EAAE;QAClCd,4BAA4Ba;IAC9B;IAEA,iFAAiF;IACjF,MAAME,eAAe,CAACH;QACpB,IAAI,CAACA,EAAEI,aAAa,IAAKb,IAAAA,6BAAa,EAACS,EAAEI,aAAa,KAAK,CAACxB,MAAMyB,QAAQ,CAACL,EAAEI,aAAa,GAAI;YAC5FV;QACF;IACF;IAEAd,MAAM0B,gBAAgB,CAACC,wBAAe,EAAER;IACxCnB,MAAM0B,gBAAgB,CAAC,YAAYH;IAClCvB,MAA2C4B,YAAY,GAAG;IAE3D,IAAI5B,MAAMyB,QAAQ,CAACxB,aAAagB,QAAQ,CAACC,aAAa,GAAG;QACvDV,4BAA4BP,aAAagB,QAAQ,CAACC,aAAa;IACjE;IAEA,kBAAkB;IAClB,OAAO;QACLJ;QAEAd,MAAM6B,mBAAmB,CAACF,wBAAe,EAAER;QAC3CnB,MAAM6B,mBAAmB,CAAC,YAAYN;QACtC,OAAO,AAACvB,MAA2C4B,YAAY;QAE/DE,IAAAA,uBAAc,EAACxB;IACjB;AACF;AAEA,SAASJ,eAAeO,EAAkC;IACxD,IAAI,CAACA,IAAI;QACP,OAAO;IACT;IAEA,IAAI,AAACA,GAAwCmB,YAAY,EAAE;QACzD,OAAO;IACT;IAEA,OAAO1B,eAAeO,eAAAA,yBAAAA,GAAIsB,aAAa;AACzC"}
|
@@ -43,7 +43,7 @@ function createTabsterWithConfig(targetDocument) {
|
|
43
43
|
function useTabster(factory = DEFAULT_FACTORY) {
|
44
44
|
const { targetDocument } = (0, _reactsharedcontexts.useFluent_unstable)();
|
45
45
|
const factoryResultRef = _react.useRef(null);
|
46
|
-
|
46
|
+
(0, _reactutilities.useIsomorphicLayoutEffect)(()=>{
|
47
47
|
const tabster = createTabsterWithConfig(targetDocument);
|
48
48
|
if (tabster) {
|
49
49
|
factoryResultRef.current = factory(tabster);
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useTabster.ts"],"sourcesContent":["import * as React from 'react';\nimport { createTabster, disposeTabster, Types as TabsterTypes } from 'tabster';\nimport { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';\nimport { getParent, usePrevious } from '@fluentui/react-utilities';\n\ninterface WindowWithTabsterShadowDOMAPI extends Window {\n __tabsterShadowDOMAPI?: TabsterTypes.DOMAPI;\n}\n\ntype UseTabsterFactory<FactoryResult> = (tabster: TabsterTypes.TabsterCore) => FactoryResult;\n\nconst DEFAULT_FACTORY: UseTabsterFactory<TabsterTypes.TabsterCore> = tabster => {\n return tabster;\n};\n\n/**\n * Creates a tabster instance with the provided configuration\n *\n * @internal\n * @param targetDocument\n */\nexport function createTabsterWithConfig(targetDocument: Document | undefined): TabsterTypes.TabsterCore | undefined {\n const defaultView = targetDocument?.defaultView || undefined;\n const shadowDOMAPI = (defaultView as WindowWithTabsterShadowDOMAPI | undefined)?.__tabsterShadowDOMAPI;\n\n if (defaultView) {\n return createTabster(defaultView, {\n autoRoot: {},\n controlTab: false,\n getParent,\n checkUncontrolledTrappingFocus: element =>\n !!element.firstElementChild?.hasAttribute('data-is-focus-trap-zone-bumper'),\n DOMAPI: shadowDOMAPI,\n });\n }\n}\n\n/**\n * Tries to get a tabster instance on the current window or creates a new one\n * Since Tabster is single instance only, feel free to call this hook to ensure Tabster exists if necessary\n *\n * @internal\n * @returns Tabster a ref to core instance or a factory result\n */\nexport function useTabster(): React.RefObject<TabsterTypes.TabsterCore | null>;\nexport function useTabster<FactoryResult>(\n factory: UseTabsterFactory<FactoryResult>,\n): React.RefObject<FactoryResult | null>;\n\nexport function useTabster<FactoryResult>(factory = DEFAULT_FACTORY) {\n const { targetDocument } = useFluent();\n const factoryResultRef = React.useRef<FactoryResult | null>(null);\n\n
|
1
|
+
{"version":3,"sources":["../src/hooks/useTabster.ts"],"sourcesContent":["import * as React from 'react';\nimport { createTabster, disposeTabster, Types as TabsterTypes } from 'tabster';\nimport { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts';\nimport { getParent, useIsomorphicLayoutEffect, usePrevious } from '@fluentui/react-utilities';\n\ninterface WindowWithTabsterShadowDOMAPI extends Window {\n __tabsterShadowDOMAPI?: TabsterTypes.DOMAPI;\n}\n\ntype UseTabsterFactory<FactoryResult> = (tabster: TabsterTypes.TabsterCore) => FactoryResult;\n\nconst DEFAULT_FACTORY: UseTabsterFactory<TabsterTypes.TabsterCore> = tabster => {\n return tabster;\n};\n\n/**\n * Creates a tabster instance with the provided configuration\n *\n * @internal\n * @param targetDocument\n */\nexport function createTabsterWithConfig(targetDocument: Document | undefined): TabsterTypes.TabsterCore | undefined {\n const defaultView = targetDocument?.defaultView || undefined;\n const shadowDOMAPI = (defaultView as WindowWithTabsterShadowDOMAPI | undefined)?.__tabsterShadowDOMAPI;\n\n if (defaultView) {\n return createTabster(defaultView, {\n autoRoot: {},\n controlTab: false,\n getParent,\n checkUncontrolledTrappingFocus: element =>\n !!element.firstElementChild?.hasAttribute('data-is-focus-trap-zone-bumper'),\n DOMAPI: shadowDOMAPI,\n });\n }\n}\n\n/**\n * Tries to get a tabster instance on the current window or creates a new one\n * Since Tabster is single instance only, feel free to call this hook to ensure Tabster exists if necessary\n *\n * @internal\n * @returns Tabster a ref to core instance or a factory result\n */\nexport function useTabster(): React.RefObject<TabsterTypes.TabsterCore | null>;\nexport function useTabster<FactoryResult>(\n factory: UseTabsterFactory<FactoryResult>,\n): React.RefObject<FactoryResult | null>;\n\nexport function useTabster<FactoryResult>(factory = DEFAULT_FACTORY) {\n const { targetDocument } = useFluent();\n const factoryResultRef = React.useRef<FactoryResult | null>(null);\n\n useIsomorphicLayoutEffect(() => {\n const tabster = createTabsterWithConfig(targetDocument);\n\n if (tabster) {\n factoryResultRef.current = factory(tabster) as FactoryResult;\n\n return () => {\n disposeTabster(tabster);\n factoryResultRef.current = null;\n };\n }\n }, [targetDocument, factory]);\n\n if (process.env.NODE_ENV !== 'production') {\n // eslint-disable-next-line\n const previousFactory = usePrevious(factory);\n\n if (previousFactory !== null && previousFactory !== factory) {\n throw new Error(\n [\n '@fluentui/react-tabster: ',\n 'The factory function passed to useTabster has changed. This should not ever happen.',\n ].join('\\n'),\n );\n }\n }\n\n return factoryResultRef;\n}\n"],"names":["createTabsterWithConfig","useTabster","DEFAULT_FACTORY","tabster","targetDocument","defaultView","undefined","shadowDOMAPI","__tabsterShadowDOMAPI","createTabster","autoRoot","controlTab","getParent","checkUncontrolledTrappingFocus","element","firstElementChild","hasAttribute","DOMAPI","factory","useFluent","factoryResultRef","React","useRef","useIsomorphicLayoutEffect","current","disposeTabster","process","env","NODE_ENV","previousFactory","usePrevious","Error","join"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":";;;;;;;;;;;IAqBgBA,uBAAuB;eAAvBA;;IA4BAC,UAAU;eAAVA;;;;iEAjDO;yBAC8C;qCACrB;gCACkB;AAQlE,MAAMC,kBAA+DC,CAAAA;IACnE,OAAOA;AACT;AAQO,SAASH,wBAAwBI,cAAoC;IAC1E,MAAMC,cAAcD,CAAAA,2BAAAA,qCAAAA,eAAgBC,WAAW,KAAIC;IACnD,MAAMC,eAAgBF,wBAAAA,kCAAD,AAACA,YAA2DG,qBAAqB;IAEtG,IAAIH,aAAa;QACf,OAAOI,IAAAA,sBAAa,EAACJ,aAAa;YAChCK,UAAU,CAAC;YACXC,YAAY;YACZC,WAAAA,yBAAS;YACTC,gCAAgCC,CAAAA;oBAC5BA;uBAAF,CAAC,GAACA,6BAAAA,QAAQC,iBAAiB,cAAzBD,iDAAAA,2BAA2BE,YAAY,CAAC;;YAC5CC,QAAQV;QACV;IACF;AACF;AAcO,SAASN,WAA0BiB,UAAUhB,eAAe;IACjE,MAAM,EAAEE,cAAc,EAAE,GAAGe,IAAAA,uCAAS;IACpC,MAAMC,mBAAmBC,OAAMC,MAAM,CAAuB;IAE5DC,IAAAA,yCAAyB,EAAC;QACxB,MAAMpB,UAAUH,wBAAwBI;QAExC,IAAID,SAAS;YACXiB,iBAAiBI,OAAO,GAAGN,QAAQf;YAEnC,OAAO;gBACLsB,IAAAA,uBAAc,EAACtB;gBACfiB,iBAAiBI,OAAO,GAAG;YAC7B;QACF;IACF,GAAG;QAACpB;QAAgBc;KAAQ;IAE5B,IAAIQ,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzC,2BAA2B;QAC3B,MAAMC,kBAAkBC,IAAAA,2BAAW,EAACZ;QAEpC,IAAIW,oBAAoB,QAAQA,oBAAoBX,SAAS;YAC3D,MAAM,IAAIa,MACR;gBACE;gBACA;aACD,CAACC,IAAI,CAAC;QAEX;IACF;IAEA,OAAOZ;AACT"}
|