@fluentui/react-tabster 9.26.14 → 9.26.16
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 +24 -3
- package/dist/index.d.ts +3 -1
- package/lib/hooks/useFocusObserved.js +2 -1
- package/lib/hooks/useFocusObserved.js.map +1 -1
- package/lib/hooks/useMergeTabsterAttributes.js +37 -50
- package/lib/hooks/useMergeTabsterAttributes.js.map +1 -1
- package/lib-commonjs/hooks/useFocusObserved.js +2 -1
- package/lib-commonjs/hooks/useFocusObserved.js.map +1 -1
- package/lib-commonjs/hooks/useMergeTabsterAttributes.js +34 -49
- package/lib-commonjs/hooks/useMergeTabsterAttributes.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
# Change Log - @fluentui/react-tabster
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Wed, 24 Jun 2026 11:03:47 GMT and should not be manually modified.
|
|
4
4
|
|
|
5
5
|
<!-- Start content -->
|
|
6
6
|
|
|
7
|
+
## [9.26.16](https://github.com/microsoft/fluentui/tree/@fluentui/react-tabster_v9.26.16)
|
|
8
|
+
|
|
9
|
+
Wed, 24 Jun 2026 11:03:47 GMT
|
|
10
|
+
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tabster_v9.26.15..@fluentui/react-tabster_v9.26.16)
|
|
11
|
+
|
|
12
|
+
### Patches
|
|
13
|
+
|
|
14
|
+
- useMergedTabsterAttributes_unstable now supports attributes that change at runtime, including a changing number of attributes ([PR #36266](https://github.com/microsoft/fluentui/pull/36266) by bsunderhus@microsoft.com)
|
|
15
|
+
- fix: remove redundant use no memo directives, and add justification to valid ones ([PR #36224](https://github.com/microsoft/fluentui/pull/36224) by martinhochel@microsoft.com)
|
|
16
|
+
- Bump @fluentui/react-utilities to v9.26.5 ([commit](https://github.com/microsoft/fluentui/commit/a4b871ca80c1f16f35ab4229def4fe02be7f30ea) by beachball)
|
|
17
|
+
|
|
18
|
+
## [9.26.15](https://github.com/microsoft/fluentui/tree/@fluentui/react-tabster_v9.26.15)
|
|
19
|
+
|
|
20
|
+
Tue, 26 May 2026 09:39:07 GMT
|
|
21
|
+
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tabster_v9.26.14..@fluentui/react-tabster_v9.26.15)
|
|
22
|
+
|
|
23
|
+
### Patches
|
|
24
|
+
|
|
25
|
+
- chore: bump tabster to ^8.8.0 and keyborg to ^2.14.1 ([PR #36082](https://github.com/microsoft/fluentui/pull/36082) by 198982749+Copilot@users.noreply.github.com)
|
|
26
|
+
- Bump @fluentui/react-utilities to v9.26.4 ([PR #36246](https://github.com/microsoft/fluentui/pull/36246) by beachball)
|
|
27
|
+
|
|
7
28
|
## [9.26.14](https://github.com/microsoft/fluentui/tree/@fluentui/react-tabster_v9.26.14)
|
|
8
29
|
|
|
9
|
-
Thu, 23 Apr 2026
|
|
30
|
+
Thu, 23 Apr 2026 14:21:07 GMT
|
|
10
31
|
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-tabster_v9.26.13..@fluentui/react-tabster_v9.26.14)
|
|
11
32
|
|
|
12
33
|
### Patches
|
|
13
34
|
|
|
14
35
|
- fix: mitigate hidden sideEffects by explicitly using type imports and decoupling useDangerousNeverHidden from tabster import path ([PR #35933](https://github.com/microsoft/fluentui/pull/35933) by martinhochel@microsoft.com)
|
|
15
|
-
- Bump @fluentui/react-utilities to v9.26.3 ([PR #
|
|
36
|
+
- Bump @fluentui/react-utilities to v9.26.3 ([PR #36035](https://github.com/microsoft/fluentui/pull/36035) by beachball)
|
|
16
37
|
|
|
17
38
|
## [9.26.13](https://github.com/microsoft/fluentui/tree/@fluentui/react-tabster_v9.26.13)
|
|
18
39
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1481,7 +1481,9 @@ export declare function useKeyboardNavAttribute<E extends HTMLElement>(): React_
|
|
|
1481
1481
|
/**
|
|
1482
1482
|
* Merges a collection of tabster attributes.
|
|
1483
1483
|
*
|
|
1484
|
-
*
|
|
1484
|
+
* The result is memoized and only recomputed when the contributing attributes
|
|
1485
|
+
* actually change, so both the values and the number of attributes may change
|
|
1486
|
+
* at runtime.
|
|
1485
1487
|
* @internal
|
|
1486
1488
|
* @param attributes - collection of tabster attributes from other react-tabster hooks
|
|
1487
1489
|
* @returns single merged tabster attribute
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useFocusObserved.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types as TabsterTypes } from 'tabster';\nimport { getObservedElement } from 'tabster';\nimport { useTabster } from './useTabster';\n\ninterface UseFocusObservedOptions {\n /**\n * After timeout the focus attempt fails\n */\n timeout?: number;\n}\n\n/**\n * @param name - The observed element to focus\n * @param options - Options for the focus observed\n *\n * @returns Function that will focus an element\n */\nexport function useFocusObserved(\n name: string,\n options: UseFocusObservedOptions = {},\n): () => TabsterTypes.ObservedElementAsyncRequest<boolean> {\n const { timeout = 1000 } = options;\n const observedAPIRef = useTabster(getObservedElement);\n\n return React.useCallback(() => {\n const observerAPI = observedAPIRef.current;\n\n if (observerAPI) {\n return observerAPI.requestFocus(name, timeout);\n }\n\n return {\n result: Promise.resolve(false),\n cancel: () => null,\n };\n }, [observedAPIRef, name, timeout]);\n}\n"],"names":["React","getObservedElement","useTabster","useFocusObserved","name","options","timeout","observedAPIRef","useCallback","observerAPI","current","requestFocus","result","Promise","resolve","cancel"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAE/B,SAASC,kBAAkB,QAAQ,UAAU;AAC7C,SAASC,UAAU,QAAQ,eAAe;AAS1C;;;;;CAKC,GACD,OAAO,SAASC,iBACdC,IAAY,EACZC,UAAmC,CAAC,CAAC;IAErC,MAAM,EAAEC,UAAU,IAAI,EAAE,GAAGD;IAC3B,MAAME,iBAAiBL,WAAWD;IAElC,OAAOD,MAAMQ,WAAW,CAAC;QACvB,MAAMC,cAAcF,eAAeG,OAAO;QAE1C,IAAID,aAAa;YACf,OAAOA,YAAYE,YAAY,CAACP,MAAME;QACxC;QAEA,OAAO;YACLM,QAAQC,QAAQC,OAAO,CAAC;YACxBC,QAAQ,IAAM;QAChB;IACF,GAAG;
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useFocusObserved.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types as TabsterTypes } from 'tabster';\nimport { getObservedElement } from 'tabster';\nimport { useTabster } from './useTabster';\n\ninterface UseFocusObservedOptions {\n /**\n * After timeout the focus attempt fails\n */\n timeout?: number;\n}\n\n/**\n * @param name - The observed element to focus\n * @param options - Options for the focus observed\n *\n * @returns Function that will focus an element\n */\nexport function useFocusObserved(\n name: string,\n options: UseFocusObservedOptions = {},\n): () => TabsterTypes.ObservedElementAsyncRequest<boolean> {\n const { timeout = 1000 } = options;\n const observedAPIRef = useTabster(getObservedElement);\n\n return React.useCallback(() => {\n const observerAPI = observedAPIRef.current;\n\n if (observerAPI) {\n return observerAPI.requestFocus(name, timeout);\n }\n\n return {\n result: Promise.resolve(false),\n cancel: () => null,\n diagnostics: {},\n };\n }, [observedAPIRef, name, timeout]);\n}\n"],"names":["React","getObservedElement","useTabster","useFocusObserved","name","options","timeout","observedAPIRef","useCallback","observerAPI","current","requestFocus","result","Promise","resolve","cancel","diagnostics"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAE/B,SAASC,kBAAkB,QAAQ,UAAU;AAC7C,SAASC,UAAU,QAAQ,eAAe;AAS1C;;;;;CAKC,GACD,OAAO,SAASC,iBACdC,IAAY,EACZC,UAAmC,CAAC,CAAC;IAErC,MAAM,EAAEC,UAAU,IAAI,EAAE,GAAGD;IAC3B,MAAME,iBAAiBL,WAAWD;IAElC,OAAOD,MAAMQ,WAAW,CAAC;QACvB,MAAMC,cAAcF,eAAeG,OAAO;QAE1C,IAAID,aAAa;YACf,OAAOA,YAAYE,YAAY,CAACP,MAAME;QACxC;QAEA,OAAO;YACLM,QAAQC,QAAQC,OAAO,CAAC;YACxBC,QAAQ,IAAM;YACdC,aAAa,CAAC;QAChB;IACF,GAAG;QAACT;QAAgBH;QAAME;KAAQ;AACpC"}
|
|
@@ -4,34 +4,53 @@ import { TABSTER_ATTRIBUTE_NAME } from 'tabster';
|
|
|
4
4
|
/**
|
|
5
5
|
* Merges a collection of tabster attributes.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* The result is memoized and only recomputed when the contributing attributes
|
|
8
|
+
* actually change, so both the values and the number of attributes may change
|
|
9
|
+
* at runtime.
|
|
8
10
|
* @internal
|
|
9
11
|
* @param attributes - collection of tabster attributes from other react-tabster hooks
|
|
10
12
|
* @returns single merged tabster attribute
|
|
11
13
|
*/ export const useMergedTabsterAttributes_unstable = (...attributes)=>{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// The collected attributes are reduced to a single primitive `key` so the
|
|
15
|
+
// `React.useMemo` dependency list keeps a constant size, even when the number
|
|
16
|
+
// of contributing attributes changes between renders. The array and the key
|
|
17
|
+
// are built in a single pass.
|
|
18
|
+
const stringAttributes = [];
|
|
19
|
+
let key = '';
|
|
20
|
+
for (const attribute of attributes){
|
|
21
|
+
const value = attribute === null || attribute === void 0 ? void 0 : attribute[TABSTER_ATTRIBUTE_NAME];
|
|
22
|
+
if (value) {
|
|
23
|
+
stringAttributes.push(value);
|
|
24
|
+
key += value + MERGE_KEY_SEPARATOR;
|
|
16
25
|
}
|
|
17
|
-
return acc;
|
|
18
|
-
}, []);
|
|
19
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
20
|
-
// ignoring rules of hooks because this is a condition based on the environment
|
|
21
|
-
// it's safe to ignore the rule
|
|
22
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
23
|
-
useWarnIfUnstableAttributes(stringAttributes);
|
|
24
26
|
}
|
|
25
27
|
return React.useMemo(()=>({
|
|
26
|
-
[TABSTER_ATTRIBUTE_NAME]: stringAttributes
|
|
27
|
-
}), //
|
|
28
|
-
//
|
|
28
|
+
[TABSTER_ATTRIBUTE_NAME]: mergeAttributes(stringAttributes)
|
|
29
|
+
}), // `key` fully captures the contents of `stringAttributes`, which is rebuilt
|
|
30
|
+
// on every render and therefore cannot be a dependency itself.
|
|
29
31
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
-
|
|
32
|
+
[
|
|
33
|
+
key
|
|
34
|
+
]);
|
|
31
35
|
};
|
|
32
36
|
/**
|
|
33
|
-
*
|
|
34
|
-
|
|
37
|
+
* `NUL` separator used to build the memoization key. It is a safe separator
|
|
38
|
+
* because the attribute values originate from `JSON.stringify`, which escapes
|
|
39
|
+
* control characters, so a literal `NUL` can never appear inside one of them.
|
|
40
|
+
*/ const MERGE_KEY_SEPARATOR = '\u0000';
|
|
41
|
+
/**
|
|
42
|
+
* Merges a collection of tabster attribute JSON strings into a single one.
|
|
43
|
+
* Later attributes take priority over earlier ones.
|
|
44
|
+
*/ const mergeAttributes = (stringAttributes)=>{
|
|
45
|
+
if (stringAttributes.length === 0) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
// common case: a single hook contributed an attribute, no parsing required
|
|
49
|
+
if (stringAttributes.length === 1) {
|
|
50
|
+
return stringAttributes[0];
|
|
51
|
+
}
|
|
52
|
+
return JSON.stringify(Object.assign({}, ...stringAttributes.map(safelyParseJSON)));
|
|
53
|
+
};
|
|
35
54
|
/**
|
|
36
55
|
* Tries to parse a JSON string and returns an object.
|
|
37
56
|
* If the JSON string is invalid, an empty object is returned.
|
|
@@ -42,35 +61,3 @@ import { TABSTER_ATTRIBUTE_NAME } from 'tabster';
|
|
|
42
61
|
return {};
|
|
43
62
|
}
|
|
44
63
|
};
|
|
45
|
-
/**
|
|
46
|
-
* Helper hook that ensures that the attributes passed to the hook are stable.
|
|
47
|
-
* This is necessary because the attributes are expected to not change at runtime.
|
|
48
|
-
*
|
|
49
|
-
* This hook will console.warn if the attributes change at runtime.
|
|
50
|
-
*/ const useWarnIfUnstableAttributes = (attributes)=>{
|
|
51
|
-
'use no memo';
|
|
52
|
-
const initialAttributesRef = React.useRef(attributes);
|
|
53
|
-
let isStable = initialAttributesRef.current.length === attributes.length;
|
|
54
|
-
if (initialAttributesRef.current !== attributes && isStable) {
|
|
55
|
-
for(let i = 0; i < attributes.length; i++){
|
|
56
|
-
if (initialAttributesRef.current[i] !== attributes[i]) {
|
|
57
|
-
isStable = false;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
React.useEffect(()=>{
|
|
63
|
-
if (!isStable) {
|
|
64
|
-
const error = new Error();
|
|
65
|
-
// eslint-disable-next-line no-console
|
|
66
|
-
console.warn(/** #__DE-INDENT__ */ `
|
|
67
|
-
@fluentui/react-tabster [useMergedTabsterAttributes]:
|
|
68
|
-
The attributes passed to the hook changed at runtime.
|
|
69
|
-
This might lead to unexpected behavior, please ensure that the attributes are stable.
|
|
70
|
-
${error.stack}
|
|
71
|
-
`);
|
|
72
|
-
}
|
|
73
|
-
}, [
|
|
74
|
-
isStable
|
|
75
|
-
]);
|
|
76
|
-
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useMergeTabsterAttributes.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types } from 'tabster';\nimport { TABSTER_ATTRIBUTE_NAME } from 'tabster';\n\n/**\n * Merges a collection of tabster attributes.\n *\n *
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useMergeTabsterAttributes.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types } from 'tabster';\nimport { TABSTER_ATTRIBUTE_NAME } from 'tabster';\n\n/**\n * Merges a collection of tabster attributes.\n *\n * The result is memoized and only recomputed when the contributing attributes\n * actually change, so both the values and the number of attributes may change\n * at runtime.\n * @internal\n * @param attributes - collection of tabster attributes from other react-tabster hooks\n * @returns single merged tabster attribute\n */\nexport const useMergedTabsterAttributes_unstable = (\n ...attributes: (Partial<Types.TabsterDOMAttribute> | null | undefined)[]\n): Types.TabsterDOMAttribute => {\n // The collected attributes are reduced to a single primitive `key` so the\n // `React.useMemo` dependency list keeps a constant size, even when the number\n // of contributing attributes changes between renders. The array and the key\n // are built in a single pass.\n const stringAttributes: string[] = [];\n let key = '';\n for (const attribute of attributes) {\n const value = attribute?.[TABSTER_ATTRIBUTE_NAME];\n if (value) {\n stringAttributes.push(value);\n key += value + MERGE_KEY_SEPARATOR;\n }\n }\n\n return React.useMemo(\n () => ({ [TABSTER_ATTRIBUTE_NAME]: mergeAttributes(stringAttributes) }),\n // `key` fully captures the contents of `stringAttributes`, which is rebuilt\n // on every render and therefore cannot be a dependency itself.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [key],\n );\n};\n\n/**\n * `NUL` separator used to build the memoization key. It is a safe separator\n * because the attribute values originate from `JSON.stringify`, which escapes\n * control characters, so a literal `NUL` can never appear inside one of them.\n */\nconst MERGE_KEY_SEPARATOR = '\\u0000';\n\n/**\n * Merges a collection of tabster attribute JSON strings into a single one.\n * Later attributes take priority over earlier ones.\n */\nconst mergeAttributes = (stringAttributes: string[]): string | undefined => {\n if (stringAttributes.length === 0) {\n return undefined;\n }\n // common case: a single hook contributed an attribute, no parsing required\n if (stringAttributes.length === 1) {\n return stringAttributes[0];\n }\n return JSON.stringify(Object.assign({}, ...stringAttributes.map(safelyParseJSON)));\n};\n\n/**\n * Tries to parse a JSON string and returns an object.\n * If the JSON string is invalid, an empty object is returned.\n */\nconst safelyParseJSON = (json: string): object => {\n try {\n return JSON.parse(json);\n } catch {\n return {};\n }\n};\n"],"names":["React","TABSTER_ATTRIBUTE_NAME","useMergedTabsterAttributes_unstable","attributes","stringAttributes","key","attribute","value","push","MERGE_KEY_SEPARATOR","useMemo","mergeAttributes","length","undefined","JSON","stringify","Object","assign","map","safelyParseJSON","json","parse"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAE/B,SAASC,sBAAsB,QAAQ,UAAU;AAEjD;;;;;;;;;CASC,GACD,OAAO,MAAMC,sCAAsC,CACjD,GAAGC;IAEH,0EAA0E;IAC1E,8EAA8E;IAC9E,4EAA4E;IAC5E,8BAA8B;IAC9B,MAAMC,mBAA6B,EAAE;IACrC,IAAIC,MAAM;IACV,KAAK,MAAMC,aAAaH,WAAY;QAClC,MAAMI,QAAQD,sBAAAA,gCAAAA,SAAW,CAACL,uBAAuB;QACjD,IAAIM,OAAO;YACTH,iBAAiBI,IAAI,CAACD;YACtBF,OAAOE,QAAQE;QACjB;IACF;IAEA,OAAOT,MAAMU,OAAO,CAClB,IAAO,CAAA;YAAE,CAACT,uBAAuB,EAAEU,gBAAgBP;QAAkB,CAAA,GACrE,4EAA4E;IAC5E,+DAA+D;IAC/D,uDAAuD;IACvD;QAACC;KAAI;AAET,EAAE;AAEF;;;;CAIC,GACD,MAAMI,sBAAsB;AAE5B;;;CAGC,GACD,MAAME,kBAAkB,CAACP;IACvB,IAAIA,iBAAiBQ,MAAM,KAAK,GAAG;QACjC,OAAOC;IACT;IACA,2EAA2E;IAC3E,IAAIT,iBAAiBQ,MAAM,KAAK,GAAG;QACjC,OAAOR,gBAAgB,CAAC,EAAE;IAC5B;IACA,OAAOU,KAAKC,SAAS,CAACC,OAAOC,MAAM,CAAC,CAAC,MAAMb,iBAAiBc,GAAG,CAACC;AAClE;AAEA;;;CAGC,GACD,MAAMA,kBAAkB,CAACC;IACvB,IAAI;QACF,OAAON,KAAKO,KAAK,CAACD;IACpB,EAAE,OAAM;QACN,OAAO,CAAC;IACV;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useFocusObserved.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types as TabsterTypes } from 'tabster';\nimport { getObservedElement } from 'tabster';\nimport { useTabster } from './useTabster';\n\ninterface UseFocusObservedOptions {\n /**\n * After timeout the focus attempt fails\n */\n timeout?: number;\n}\n\n/**\n * @param name - The observed element to focus\n * @param options - Options for the focus observed\n *\n * @returns Function that will focus an element\n */\nexport function useFocusObserved(\n name: string,\n options: UseFocusObservedOptions = {},\n): () => TabsterTypes.ObservedElementAsyncRequest<boolean> {\n const { timeout = 1000 } = options;\n const observedAPIRef = useTabster(getObservedElement);\n\n return React.useCallback(() => {\n const observerAPI = observedAPIRef.current;\n\n if (observerAPI) {\n return observerAPI.requestFocus(name, timeout);\n }\n\n return {\n result: Promise.resolve(false),\n cancel: () => null,\n };\n }, [observedAPIRef, name, timeout]);\n}\n"],"names":["useFocusObserved","name","options","timeout","observedAPIRef","useTabster","getObservedElement","React","useCallback","observerAPI","current","requestFocus","result","Promise","resolve","cancel"],"mappings":"AAAA;;;;;+BAoBgBA;;;eAAAA;;;;iEAlBO;yBAEY;4BACR;AAepB,SAASA,iBACdC,IAAY,EACZC,UAAmC,CAAC,CAAC;IAErC,MAAM,EAAEC,UAAU,IAAI,EAAE,GAAGD;IAC3B,MAAME,iBAAiBC,IAAAA,sBAAU,EAACC,2BAAkB;IAEpD,OAAOC,OAAMC,WAAW,CAAC;QACvB,MAAMC,cAAcL,eAAeM,OAAO;QAE1C,IAAID,aAAa;YACf,OAAOA,YAAYE,YAAY,CAACV,MAAME;QACxC;QAEA,OAAO;YACLS,QAAQC,QAAQC,OAAO,CAAC;YACxBC,QAAQ,IAAM;QAChB;IACF,GAAG;
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useFocusObserved.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types as TabsterTypes } from 'tabster';\nimport { getObservedElement } from 'tabster';\nimport { useTabster } from './useTabster';\n\ninterface UseFocusObservedOptions {\n /**\n * After timeout the focus attempt fails\n */\n timeout?: number;\n}\n\n/**\n * @param name - The observed element to focus\n * @param options - Options for the focus observed\n *\n * @returns Function that will focus an element\n */\nexport function useFocusObserved(\n name: string,\n options: UseFocusObservedOptions = {},\n): () => TabsterTypes.ObservedElementAsyncRequest<boolean> {\n const { timeout = 1000 } = options;\n const observedAPIRef = useTabster(getObservedElement);\n\n return React.useCallback(() => {\n const observerAPI = observedAPIRef.current;\n\n if (observerAPI) {\n return observerAPI.requestFocus(name, timeout);\n }\n\n return {\n result: Promise.resolve(false),\n cancel: () => null,\n diagnostics: {},\n };\n }, [observedAPIRef, name, timeout]);\n}\n"],"names":["useFocusObserved","name","options","timeout","observedAPIRef","useTabster","getObservedElement","React","useCallback","observerAPI","current","requestFocus","result","Promise","resolve","cancel","diagnostics"],"mappings":"AAAA;;;;;+BAoBgBA;;;eAAAA;;;;iEAlBO;yBAEY;4BACR;AAepB,SAASA,iBACdC,IAAY,EACZC,UAAmC,CAAC,CAAC;IAErC,MAAM,EAAEC,UAAU,IAAI,EAAE,GAAGD;IAC3B,MAAME,iBAAiBC,IAAAA,sBAAU,EAACC,2BAAkB;IAEpD,OAAOC,OAAMC,WAAW,CAAC;QACvB,MAAMC,cAAcL,eAAeM,OAAO;QAE1C,IAAID,aAAa;YACf,OAAOA,YAAYE,YAAY,CAACV,MAAME;QACxC;QAEA,OAAO;YACLS,QAAQC,QAAQC,OAAO,CAAC;YACxBC,QAAQ,IAAM;YACdC,aAAa,CAAC;QAChB;IACF,GAAG;QAACZ;QAAgBH;QAAME;KAAQ;AACpC"}
|
|
@@ -13,29 +13,46 @@ const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildc
|
|
|
13
13
|
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
|
|
14
14
|
const _tabster = require("tabster");
|
|
15
15
|
const useMergedTabsterAttributes_unstable = (...attributes)=>{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
// The collected attributes are reduced to a single primitive `key` so the
|
|
17
|
+
// `React.useMemo` dependency list keeps a constant size, even when the number
|
|
18
|
+
// of contributing attributes changes between renders. The array and the key
|
|
19
|
+
// are built in a single pass.
|
|
20
|
+
const stringAttributes = [];
|
|
21
|
+
let key = '';
|
|
22
|
+
for (const attribute of attributes){
|
|
23
|
+
const value = attribute === null || attribute === void 0 ? void 0 : attribute[_tabster.TABSTER_ATTRIBUTE_NAME];
|
|
24
|
+
if (value) {
|
|
25
|
+
stringAttributes.push(value);
|
|
26
|
+
key += value + MERGE_KEY_SEPARATOR;
|
|
20
27
|
}
|
|
21
|
-
return acc;
|
|
22
|
-
}, []);
|
|
23
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
24
|
-
// ignoring rules of hooks because this is a condition based on the environment
|
|
25
|
-
// it's safe to ignore the rule
|
|
26
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
27
|
-
useWarnIfUnstableAttributes(stringAttributes);
|
|
28
28
|
}
|
|
29
29
|
return _react.useMemo(()=>({
|
|
30
|
-
[_tabster.TABSTER_ATTRIBUTE_NAME]: stringAttributes
|
|
31
|
-
}), //
|
|
32
|
-
//
|
|
30
|
+
[_tabster.TABSTER_ATTRIBUTE_NAME]: mergeAttributes(stringAttributes)
|
|
31
|
+
}), // `key` fully captures the contents of `stringAttributes`, which is rebuilt
|
|
32
|
+
// on every render and therefore cannot be a dependency itself.
|
|
33
33
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
-
|
|
34
|
+
[
|
|
35
|
+
key
|
|
36
|
+
]);
|
|
35
37
|
};
|
|
36
38
|
/**
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
+
* `NUL` separator used to build the memoization key. It is a safe separator
|
|
40
|
+
* because the attribute values originate from `JSON.stringify`, which escapes
|
|
41
|
+
* control characters, so a literal `NUL` can never appear inside one of them.
|
|
42
|
+
*/ const MERGE_KEY_SEPARATOR = '\u0000';
|
|
43
|
+
/**
|
|
44
|
+
* Merges a collection of tabster attribute JSON strings into a single one.
|
|
45
|
+
* Later attributes take priority over earlier ones.
|
|
46
|
+
*/ const mergeAttributes = (stringAttributes)=>{
|
|
47
|
+
if (stringAttributes.length === 0) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
// common case: a single hook contributed an attribute, no parsing required
|
|
51
|
+
if (stringAttributes.length === 1) {
|
|
52
|
+
return stringAttributes[0];
|
|
53
|
+
}
|
|
54
|
+
return JSON.stringify(Object.assign({}, ...stringAttributes.map(safelyParseJSON)));
|
|
55
|
+
};
|
|
39
56
|
/**
|
|
40
57
|
* Tries to parse a JSON string and returns an object.
|
|
41
58
|
* If the JSON string is invalid, an empty object is returned.
|
|
@@ -46,35 +63,3 @@ const useMergedTabsterAttributes_unstable = (...attributes)=>{
|
|
|
46
63
|
return {};
|
|
47
64
|
}
|
|
48
65
|
};
|
|
49
|
-
/**
|
|
50
|
-
* Helper hook that ensures that the attributes passed to the hook are stable.
|
|
51
|
-
* This is necessary because the attributes are expected to not change at runtime.
|
|
52
|
-
*
|
|
53
|
-
* This hook will console.warn if the attributes change at runtime.
|
|
54
|
-
*/ const useWarnIfUnstableAttributes = (attributes)=>{
|
|
55
|
-
'use no memo';
|
|
56
|
-
const initialAttributesRef = _react.useRef(attributes);
|
|
57
|
-
let isStable = initialAttributesRef.current.length === attributes.length;
|
|
58
|
-
if (initialAttributesRef.current !== attributes && isStable) {
|
|
59
|
-
for(let i = 0; i < attributes.length; i++){
|
|
60
|
-
if (initialAttributesRef.current[i] !== attributes[i]) {
|
|
61
|
-
isStable = false;
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
_react.useEffect(()=>{
|
|
67
|
-
if (!isStable) {
|
|
68
|
-
const error = new Error();
|
|
69
|
-
// eslint-disable-next-line no-console
|
|
70
|
-
console.warn(/** #__DE-INDENT__ */ `
|
|
71
|
-
@fluentui/react-tabster [useMergedTabsterAttributes]:
|
|
72
|
-
The attributes passed to the hook changed at runtime.
|
|
73
|
-
This might lead to unexpected behavior, please ensure that the attributes are stable.
|
|
74
|
-
${error.stack}
|
|
75
|
-
`);
|
|
76
|
-
}
|
|
77
|
-
}, [
|
|
78
|
-
isStable
|
|
79
|
-
]);
|
|
80
|
-
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useMergeTabsterAttributes.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types } from 'tabster';\nimport { TABSTER_ATTRIBUTE_NAME } from 'tabster';\n\n/**\n * Merges a collection of tabster attributes.\n *\n *
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useMergeTabsterAttributes.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Types } from 'tabster';\nimport { TABSTER_ATTRIBUTE_NAME } from 'tabster';\n\n/**\n * Merges a collection of tabster attributes.\n *\n * The result is memoized and only recomputed when the contributing attributes\n * actually change, so both the values and the number of attributes may change\n * at runtime.\n * @internal\n * @param attributes - collection of tabster attributes from other react-tabster hooks\n * @returns single merged tabster attribute\n */\nexport const useMergedTabsterAttributes_unstable = (\n ...attributes: (Partial<Types.TabsterDOMAttribute> | null | undefined)[]\n): Types.TabsterDOMAttribute => {\n // The collected attributes are reduced to a single primitive `key` so the\n // `React.useMemo` dependency list keeps a constant size, even when the number\n // of contributing attributes changes between renders. The array and the key\n // are built in a single pass.\n const stringAttributes: string[] = [];\n let key = '';\n for (const attribute of attributes) {\n const value = attribute?.[TABSTER_ATTRIBUTE_NAME];\n if (value) {\n stringAttributes.push(value);\n key += value + MERGE_KEY_SEPARATOR;\n }\n }\n\n return React.useMemo(\n () => ({ [TABSTER_ATTRIBUTE_NAME]: mergeAttributes(stringAttributes) }),\n // `key` fully captures the contents of `stringAttributes`, which is rebuilt\n // on every render and therefore cannot be a dependency itself.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [key],\n );\n};\n\n/**\n * `NUL` separator used to build the memoization key. It is a safe separator\n * because the attribute values originate from `JSON.stringify`, which escapes\n * control characters, so a literal `NUL` can never appear inside one of them.\n */\nconst MERGE_KEY_SEPARATOR = '\\u0000';\n\n/**\n * Merges a collection of tabster attribute JSON strings into a single one.\n * Later attributes take priority over earlier ones.\n */\nconst mergeAttributes = (stringAttributes: string[]): string | undefined => {\n if (stringAttributes.length === 0) {\n return undefined;\n }\n // common case: a single hook contributed an attribute, no parsing required\n if (stringAttributes.length === 1) {\n return stringAttributes[0];\n }\n return JSON.stringify(Object.assign({}, ...stringAttributes.map(safelyParseJSON)));\n};\n\n/**\n * Tries to parse a JSON string and returns an object.\n * If the JSON string is invalid, an empty object is returned.\n */\nconst safelyParseJSON = (json: string): object => {\n try {\n return JSON.parse(json);\n } catch {\n return {};\n }\n};\n"],"names":["useMergedTabsterAttributes_unstable","attributes","stringAttributes","key","attribute","value","TABSTER_ATTRIBUTE_NAME","push","MERGE_KEY_SEPARATOR","React","useMemo","mergeAttributes","length","undefined","JSON","stringify","Object","assign","map","safelyParseJSON","json","parse"],"mappings":"AAAA;;;;;+BAgBaA;;;eAAAA;;;;iEAdU;yBAEgB;AAYhC,MAAMA,sCAAsC,CACjD,GAAGC;IAEH,0EAA0E;IAC1E,8EAA8E;IAC9E,4EAA4E;IAC5E,8BAA8B;IAC9B,MAAMC,mBAA6B,EAAE;IACrC,IAAIC,MAAM;IACV,KAAK,MAAMC,aAAaH,WAAY;QAClC,MAAMI,QAAQD,sBAAAA,gCAAAA,SAAW,CAACE,+BAAsB,CAAC;QACjD,IAAID,OAAO;YACTH,iBAAiBK,IAAI,CAACF;YACtBF,OAAOE,QAAQG;QACjB;IACF;IAEA,OAAOC,OAAMC,OAAO,CAClB,IAAO,CAAA;YAAE,CAACJ,+BAAsB,CAAC,EAAEK,gBAAgBT;QAAkB,CAAA,GACrE,4EAA4E;IAC5E,+DAA+D;IAC/D,uDAAuD;IACvD;QAACC;KAAI;AAET;AAEA;;;;CAIC,GACD,MAAMK,sBAAsB;AAE5B;;;CAGC,GACD,MAAMG,kBAAkB,CAACT;IACvB,IAAIA,iBAAiBU,MAAM,KAAK,GAAG;QACjC,OAAOC;IACT;IACA,2EAA2E;IAC3E,IAAIX,iBAAiBU,MAAM,KAAK,GAAG;QACjC,OAAOV,gBAAgB,CAAC,EAAE;IAC5B;IACA,OAAOY,KAAKC,SAAS,CAACC,OAAOC,MAAM,CAAC,CAAC,MAAMf,iBAAiBgB,GAAG,CAACC;AAClE;AAEA;;;CAGC,GACD,MAAMA,kBAAkB,CAACC;IACvB,IAAI;QACF,OAAON,KAAKO,KAAK,CAACD;IACpB,EAAE,OAAM;QACN,OAAO,CAAC;IACV;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluentui/react-tabster",
|
|
3
|
-
"version": "9.26.
|
|
3
|
+
"version": "9.26.16",
|
|
4
4
|
"description": "Utilities for focus management and facade for tabster",
|
|
5
5
|
"main": "lib-commonjs/index.js",
|
|
6
6
|
"module": "lib/index.js",
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@fluentui/react-shared-contexts": "^9.26.2",
|
|
16
16
|
"@fluentui/react-theme": "^9.2.1",
|
|
17
|
-
"@fluentui/react-utilities": "^9.26.
|
|
17
|
+
"@fluentui/react-utilities": "^9.26.5",
|
|
18
18
|
"@griffel/react": "^1.5.32",
|
|
19
19
|
"@swc/helpers": "^0.5.1",
|
|
20
|
-
"keyborg": "^2.
|
|
21
|
-
"tabster": "^8.
|
|
20
|
+
"keyborg": "^2.14.1",
|
|
21
|
+
"tabster": "^8.8.0"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"@types/react": ">=16.14.0 <20.0.0",
|