@fluentui/react-context-selector 9.2.15 → 9.2.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 CHANGED
@@ -1,12 +1,22 @@
1
1
  # Change Log - @fluentui/react-context-selector
2
2
 
3
- This log was last generated on Wed, 25 Feb 2026 13:28:23 GMT and should not be manually modified.
3
+ This log was last generated on Thu, 23 Apr 2026 11:59:42 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## [9.2.16](https://github.com/microsoft/fluentui/tree/@fluentui/react-context-selector_v9.2.16)
8
+
9
+ Thu, 23 Apr 2026 11:59:42 GMT
10
+ [Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-context-selector_v9.2.15..@fluentui/react-context-selector_v9.2.16)
11
+
12
+ ### Patches
13
+
14
+ - fix: rewrite useContextSelector to avoid React's eager-bailout pitfall on memoized consumers ([PR #36002](https://github.com/microsoft/fluentui/pull/36002) by olfedias@microsoft.com)
15
+ - Bump @fluentui/react-utilities to v9.26.3 ([PR #35996](https://github.com/microsoft/fluentui/pull/35996) by beachball)
16
+
7
17
  ## [9.2.15](https://github.com/microsoft/fluentui/tree/@fluentui/react-context-selector_v9.2.15)
8
18
 
9
- Wed, 25 Feb 2026 13:28:23 GMT
19
+ Wed, 25 Feb 2026 13:32:28 GMT
10
20
  [Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-context-selector_v9.2.14..@fluentui/react-context-selector_v9.2.15)
11
21
 
12
22
  ### Patches
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as React_2 from 'react';
1
+ import type * as React_2 from 'react';
2
2
 
3
3
  /**
4
4
  * @internal
@@ -15,44 +15,34 @@ export declare type ContextSelector<Value, SelectedValue> = (value: Value) => Se
15
15
  */
16
16
  export declare type ContextValue<Value> = {
17
17
  /** Holds a set of subscribers from components. */
18
- listeners: ((payload: readonly [ContextVersion, Value]) => void)[];
18
+ listeners: ((payload: Value) => void)[];
19
19
  /** Holds an actual value of React's context that will be propagated down for computations. */
20
- value: React_2.MutableRefObject<Value>;
21
- /** A version field is used to sync a context value and consumers. */
22
- version: React_2.MutableRefObject<ContextVersion>;
20
+ value: {
21
+ current: Value;
22
+ };
23
+ /** Indicates if a context holds default value. */
24
+ isDefault?: boolean;
23
25
  };
24
26
 
25
- /**
26
- * @internal
27
- */
28
- export declare type ContextValues<Value> = ContextValue<Value> & {
29
- /** List of listners to publish changes */
30
- listeners: ((payload: readonly [ContextVersion, Record<string, Value>]) => void)[];
31
- };
32
-
33
- /**
34
- * @internal
35
- */
36
- export declare type ContextVersion = number;
37
-
38
27
  /**
39
28
  * @internal
40
29
  */
41
30
  export declare const createContext: <Value>(defaultValue: Value) => Context<Value>;
42
31
 
43
32
  /**
44
- * @internal
45
33
  * This hook returns context selected value by selector.
46
34
  * It will only accept context created by `createContext`.
47
35
  * It will trigger re-render if only the selected value is referentially changed.
36
+ *
37
+ * @internal
48
38
  */
49
- export declare const useContextSelector: <Value, SelectedValue>(context: Context<Value>, selector: ContextSelector<Value, SelectedValue>) => SelectedValue;
39
+ export declare const useContextSelector: <Value, SelectedValue>(context: Context<Value>, selectorFn: ContextSelector<Value, SelectedValue>) => SelectedValue;
50
40
 
51
41
  /**
52
- * @internal
53
42
  * Utility hook for contexts created by react-context-selector to determine if a parent context exists
54
43
  * WARNING: This hook will not work for native React contexts
55
44
  *
45
+ * @internal
56
46
  * @param context - context created by react-context-selector
57
47
  * @returns whether the hook is wrapped by a parent context
58
48
  */
@@ -6,26 +6,19 @@ const createProvider = (Original)=>{
6
6
  const Provider = (props)=>{
7
7
  // Holds an actual "props.value"
8
8
  const valueRef = React.useRef(props.value);
9
- // Used to sync context updates and avoid stale values, can be considered as render/effect counter of Provider.
10
- const versionRef = React.useRef(0);
11
9
  // A stable object, is used to avoid context updates via mutation of its values.
12
10
  const contextValue = React.useRef(null);
13
11
  if (!contextValue.current) {
14
12
  contextValue.current = {
15
13
  value: valueRef,
16
- version: versionRef,
17
14
  listeners: []
18
15
  };
19
16
  }
20
17
  useIsomorphicLayoutEffect(()=>{
21
18
  valueRef.current = props.value;
22
- versionRef.current += 1;
23
19
  runWithPriority(NormalPriority, ()=>{
24
20
  contextValue.current.listeners.forEach((listener)=>{
25
- listener([
26
- versionRef.current,
27
- props.value
28
- ]);
21
+ listener(props.value);
29
22
  });
30
23
  });
31
24
  }, [
@@ -48,10 +41,8 @@ const createProvider = (Original)=>{
48
41
  value: {
49
42
  current: defaultValue
50
43
  },
51
- version: {
52
- current: -1
53
- },
54
- listeners: []
44
+ listeners: [],
45
+ isDefault: true
55
46
  });
56
47
  context.Provider = createProvider(context.Provider);
57
48
  // We don't support Consumer API
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/createContext.ts"],"sourcesContent":["'use client';\n\nimport { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { unstable_NormalPriority as NormalPriority, unstable_runWithPriority as runWithPriority } from 'scheduler';\n\nimport { Context, ContextValue } from './types';\n\nconst createProvider = <Value>(Original: React.Provider<ContextValue<Value>>) => {\n const Provider: React.FC<React.ProviderProps<Value>> = props => {\n // Holds an actual \"props.value\"\n const valueRef = React.useRef(props.value);\n // Used to sync context updates and avoid stale values, can be considered as render/effect counter of Provider.\n const versionRef = React.useRef(0);\n\n // A stable object, is used to avoid context updates via mutation of its values.\n const contextValue = React.useRef<ContextValue<Value>>(null);\n\n if (!contextValue.current) {\n contextValue.current = {\n value: valueRef,\n version: versionRef,\n listeners: [],\n };\n }\n\n useIsomorphicLayoutEffect(() => {\n valueRef.current = props.value;\n versionRef.current += 1;\n\n runWithPriority(NormalPriority, () => {\n (contextValue.current as ContextValue<Value>).listeners.forEach(listener => {\n listener([versionRef.current, props.value]);\n });\n });\n }, [props.value]);\n\n return React.createElement(Original, { value: contextValue.current }, props.children);\n };\n\n /* istanbul ignore else */\n if (process.env.NODE_ENV !== 'production') {\n Provider.displayName = 'ContextSelector.Provider';\n }\n\n return Provider as unknown as React.Provider<ContextValue<Value>>;\n};\n\n/**\n * @internal\n */\nexport const createContext = <Value>(defaultValue: Value): Context<Value> => {\n // eslint-disable-next-line @fluentui/no-context-default-value\n const context = React.createContext<ContextValue<Value>>({\n value: { current: defaultValue },\n version: { current: -1 },\n listeners: [],\n });\n\n context.Provider = createProvider<Value>(context.Provider);\n\n // We don't support Consumer API\n delete (context as unknown as Context<Value>).Consumer;\n\n return context as unknown as Context<Value>;\n};\n"],"names":["useIsomorphicLayoutEffect","React","unstable_NormalPriority","NormalPriority","unstable_runWithPriority","runWithPriority","createProvider","Original","Provider","props","valueRef","useRef","value","versionRef","contextValue","current","version","listeners","forEach","listener","createElement","children","process","env","NODE_ENV","displayName","createContext","defaultValue","context","Consumer"],"mappings":"AAAA;AAEA,SAASA,yBAAyB,QAAQ,4BAA4B;AACtE,YAAYC,WAAW,QAAQ;AAC/B,SAASC,2BAA2BC,cAAc,EAAEC,4BAA4BC,eAAe,QAAQ,YAAY;AAInH,MAAMC,iBAAiB,CAAQC;IAC7B,MAAMC,WAAiDC,CAAAA;QACrD,gCAAgC;QAChC,MAAMC,WAAWT,MAAMU,MAAM,CAACF,MAAMG,KAAK;QACzC,+GAA+G;QAC/G,MAAMC,aAAaZ,MAAMU,MAAM,CAAC;QAEhC,gFAAgF;QAChF,MAAMG,eAAeb,MAAMU,MAAM,CAAsB;QAEvD,IAAI,CAACG,aAAaC,OAAO,EAAE;YACzBD,aAAaC,OAAO,GAAG;gBACrBH,OAAOF;gBACPM,SAASH;gBACTI,WAAW,EAAE;YACf;QACF;QAEAjB,0BAA0B;YACxBU,SAASK,OAAO,GAAGN,MAAMG,KAAK;YAC9BC,WAAWE,OAAO,IAAI;YAEtBV,gBAAgBF,gBAAgB;gBAC7BW,aAAaC,OAAO,CAAyBE,SAAS,CAACC,OAAO,CAACC,CAAAA;oBAC9DA,SAAS;wBAACN,WAAWE,OAAO;wBAAEN,MAAMG,KAAK;qBAAC;gBAC5C;YACF;QACF,GAAG;YAACH,MAAMG,KAAK;SAAC;QAEhB,OAAOX,MAAMmB,aAAa,CAACb,UAAU;YAAEK,OAAOE,aAAaC,OAAO;QAAC,GAAGN,MAAMY,QAAQ;IACtF;IAEA,wBAAwB,GACxB,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzChB,SAASiB,WAAW,GAAG;IACzB;IAEA,OAAOjB;AACT;AAEA;;CAEC,GACD,OAAO,MAAMkB,gBAAgB,CAAQC;IACnC,8DAA8D;IAC9D,MAAMC,UAAU3B,MAAMyB,aAAa,CAAsB;QACvDd,OAAO;YAAEG,SAASY;QAAa;QAC/BX,SAAS;YAAED,SAAS,CAAC;QAAE;QACvBE,WAAW,EAAE;IACf;IAEAW,QAAQpB,QAAQ,GAAGF,eAAsBsB,QAAQpB,QAAQ;IAEzD,gCAAgC;IAChC,OAAO,AAACoB,QAAsCC,QAAQ;IAEtD,OAAOD;AACT,EAAE"}
1
+ {"version":3,"sources":["../src/createContext.ts"],"sourcesContent":["'use client';\n\nimport { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { unstable_NormalPriority as NormalPriority, unstable_runWithPriority as runWithPriority } from 'scheduler';\n\nimport type { Context, ContextValue } from './types';\n\nconst createProvider = <Value>(Original: React.Provider<ContextValue<Value>>) => {\n const Provider: React.FC<React.ProviderProps<Value>> = props => {\n // Holds an actual \"props.value\"\n const valueRef = React.useRef(props.value);\n\n // A stable object, is used to avoid context updates via mutation of its values.\n const contextValue = React.useRef<ContextValue<Value>>(null);\n\n if (!contextValue.current) {\n contextValue.current = {\n value: valueRef,\n listeners: [],\n };\n }\n\n useIsomorphicLayoutEffect(() => {\n valueRef.current = props.value;\n\n runWithPriority(NormalPriority, () => {\n (contextValue.current as ContextValue<Value>).listeners.forEach(listener => {\n listener(props.value);\n });\n });\n }, [props.value]);\n\n return React.createElement(Original, { value: contextValue.current }, props.children);\n };\n\n /* istanbul ignore else */\n if (process.env.NODE_ENV !== 'production') {\n Provider.displayName = 'ContextSelector.Provider';\n }\n\n return Provider as unknown as React.Provider<ContextValue<Value>>;\n};\n\n/**\n * @internal\n */\nexport const createContext = <Value>(defaultValue: Value): Context<Value> => {\n // eslint-disable-next-line @fluentui/no-context-default-value\n const context = React.createContext<ContextValue<Value>>({\n value: { current: defaultValue },\n listeners: [],\n isDefault: true,\n });\n\n context.Provider = createProvider<Value>(context.Provider);\n\n // We don't support Consumer API\n delete (context as unknown as Context<Value>).Consumer;\n\n return context as unknown as Context<Value>;\n};\n"],"names":["useIsomorphicLayoutEffect","React","unstable_NormalPriority","NormalPriority","unstable_runWithPriority","runWithPriority","createProvider","Original","Provider","props","valueRef","useRef","value","contextValue","current","listeners","forEach","listener","createElement","children","process","env","NODE_ENV","displayName","createContext","defaultValue","context","isDefault","Consumer"],"mappings":"AAAA;AAEA,SAASA,yBAAyB,QAAQ,4BAA4B;AACtE,YAAYC,WAAW,QAAQ;AAC/B,SAASC,2BAA2BC,cAAc,EAAEC,4BAA4BC,eAAe,QAAQ,YAAY;AAInH,MAAMC,iBAAiB,CAAQC;IAC7B,MAAMC,WAAiDC,CAAAA;QACrD,gCAAgC;QAChC,MAAMC,WAAWT,MAAMU,MAAM,CAACF,MAAMG,KAAK;QAEzC,gFAAgF;QAChF,MAAMC,eAAeZ,MAAMU,MAAM,CAAsB;QAEvD,IAAI,CAACE,aAAaC,OAAO,EAAE;YACzBD,aAAaC,OAAO,GAAG;gBACrBF,OAAOF;gBACPK,WAAW,EAAE;YACf;QACF;QAEAf,0BAA0B;YACxBU,SAASI,OAAO,GAAGL,MAAMG,KAAK;YAE9BP,gBAAgBF,gBAAgB;gBAC7BU,aAAaC,OAAO,CAAyBC,SAAS,CAACC,OAAO,CAACC,CAAAA;oBAC9DA,SAASR,MAAMG,KAAK;gBACtB;YACF;QACF,GAAG;YAACH,MAAMG,KAAK;SAAC;QAEhB,OAAOX,MAAMiB,aAAa,CAACX,UAAU;YAAEK,OAAOC,aAAaC,OAAO;QAAC,GAAGL,MAAMU,QAAQ;IACtF;IAEA,wBAAwB,GACxB,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzCd,SAASe,WAAW,GAAG;IACzB;IAEA,OAAOf;AACT;AAEA;;CAEC,GACD,OAAO,MAAMgB,gBAAgB,CAAQC;IACnC,8DAA8D;IAC9D,MAAMC,UAAUzB,MAAMuB,aAAa,CAAsB;QACvDZ,OAAO;YAAEE,SAASW;QAAa;QAC/BV,WAAW,EAAE;QACbY,WAAW;IACb;IAEAD,QAAQlB,QAAQ,GAAGF,eAAsBoB,QAAQlB,QAAQ;IAEzD,gCAAgC;IAChC,OAAO,AAACkB,QAAsCE,QAAQ;IAEtD,OAAOF;AACT,EAAE"}
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { createContext } from './createContext';\nexport { useContextSelector } from './useContextSelector';\nexport { useHasParentContext } from './useHasParentContext';\n// eslint-disable-next-line @fluentui/ban-context-export\nexport type { Context, ContextSelector, ContextValue, ContextValues, ContextVersion } from './types';\n"],"names":["createContext","useContextSelector","useHasParentContext"],"mappings":"AAAA,SAASA,aAAa,QAAQ,kBAAkB;AAChD,SAASC,kBAAkB,QAAQ,uBAAuB;AAC1D,SAASC,mBAAmB,QAAQ,wBAAwB"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { createContext } from './createContext';\nexport { useContextSelector } from './useContextSelector';\nexport { useHasParentContext } from './useHasParentContext';\n// eslint-disable-next-line @fluentui/ban-context-export\nexport type { Context, ContextSelector, ContextValue } from './types';\n"],"names":["createContext","useContextSelector","useHasParentContext"],"mappings":"AAAA,SAASA,aAAa,QAAQ,kBAAkB;AAChD,SAASC,kBAAkB,QAAQ,uBAAuB;AAC1D,SAASC,mBAAmB,QAAQ,wBAAwB"}
package/lib/types.js CHANGED
@@ -1 +1,3 @@
1
- import * as React from 'react';
1
+ /**
2
+ * @internal
3
+ */ export { };
package/lib/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import * as React from 'react';\n\n/**\n * @internal\n */\nexport type Context<Value> = React.Context<Value> & {\n Provider: React.FC<React.ProviderProps<Value>>;\n Consumer: never;\n};\n\nexport type ContextSelector<Value, SelectedValue> = (value: Value) => SelectedValue;\n\n/**\n * @internal\n */\nexport type ContextVersion = number;\n\n/**\n * @internal\n */\nexport type ContextValue<Value> = {\n /** Holds a set of subscribers from components. */\n listeners: ((payload: readonly [ContextVersion, Value]) => void)[];\n\n /** Holds an actual value of React's context that will be propagated down for computations. */\n value: // eslint-disable-next-line @typescript-eslint/no-deprecated\n React.MutableRefObject<Value>;\n\n /** A version field is used to sync a context value and consumers. */\n version: // eslint-disable-next-line @typescript-eslint/no-deprecated\n React.MutableRefObject<ContextVersion>;\n};\n\n/**\n * @internal\n */\nexport type ContextValues<Value> = ContextValue<Value> & {\n /** List of listners to publish changes */\n listeners: ((payload: readonly [ContextVersion, Record<string, Value>]) => void)[];\n};\n"],"names":["React"],"mappings":"AAAA,YAAYA,WAAW,QAAQ"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type * as React from 'react';\n\n/**\n * @internal\n */\nexport type Context<Value> = React.Context<Value> & {\n Provider: React.FC<React.ProviderProps<Value>>;\n Consumer: never;\n};\n\nexport type ContextSelector<Value, SelectedValue> = (value: Value) => SelectedValue;\n\n/**\n * @internal\n */\nexport type ContextValue<Value> = {\n /** Holds a set of subscribers from components. */\n listeners: ((payload: Value) => void)[];\n\n /** Holds an actual value of React's context that will be propagated down for computations. */\n value: { current: Value };\n\n /** Indicates if a context holds default value. */\n isDefault?: boolean;\n};\n"],"names":[],"mappings":"AAYA;;CAEC,GACD,WASE"}
@@ -1,74 +1,54 @@
1
1
  'use client';
2
- import { useEventCallback, useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
2
+ import { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';
3
3
  import * as React from 'react';
4
4
  /**
5
- * @internal
6
5
  * This hook returns context selected value by selector.
7
6
  * It will only accept context created by `createContext`.
8
7
  * It will trigger re-render if only the selected value is referentially changed.
9
- */ export const useContextSelector = (context, selector)=>{
8
+ *
9
+ * @internal
10
+ */ export const useContextSelector = (context, selectorFn)=>{
10
11
  const contextValue = React.useContext(context);
11
- const { value: { current: value }, version: { current: version }, listeners } = contextValue;
12
- const selected = selector(value);
13
- const [state, setState] = React.useState([
14
- value,
15
- selected
16
- ]);
17
- const dispatch = (payload)=>{
18
- setState((prevState)=>{
19
- if (!payload) {
20
- // early bail out when is dispatched during render
21
- return [
22
- value,
23
- selected
24
- ];
25
- }
26
- if (payload[0] <= version) {
27
- if (Object.is(prevState[1], selected)) {
28
- return prevState; // bail out
29
- }
30
- return [
31
- value,
32
- selected
33
- ];
34
- }
12
+ const { value: valueRef, listeners } = contextValue;
13
+ // Read valueRef during render and return selector(value) directly. This is analogous to `useSyncExternalStore`'s
14
+ // `getSnapshot` and is the only way to select a slice from a shared ref-based store without re-rendering every
15
+ // consumer on every provider update.
16
+ const valueAtRender = selectorFn(valueRef.current);
17
+ const [, forceUpdate] = React.useReducer((x)=>x + 1, 0);
18
+ // Refs holding the current selector and the most-recently-returned slice.
19
+ // Updated in a layout effect (ordering: children first, then provider) so
20
+ // they are current by the time the provider's listener loop fires.
21
+ const selectorFnRef = React.useRef(selectorFn);
22
+ const lastValueAtRender = React.useRef(valueAtRender);
23
+ useIsomorphicLayoutEffect(()=>{
24
+ selectorFnRef.current = selectorFn;
25
+ lastValueAtRender.current = valueAtRender;
26
+ });
27
+ useIsomorphicLayoutEffect(()=>{
28
+ const listener = (payload)=>{
29
+ // Selectors can throw on transiently-inconsistent inputs (stale props vs. newer context value). Swallow so a
30
+ // single consumer's throw doesn't abort the provider's `listeners.forEach`.
35
31
  try {
36
- if (Object.is(prevState[0], payload[1])) {
37
- return prevState; // do not update
38
- }
39
- const nextSelected = selector(payload[1]);
40
- if (Object.is(prevState[1], nextSelected)) {
41
- return prevState; // do not update
32
+ const nextSelectedValue = selectorFnRef.current(payload);
33
+ if (!Object.is(lastValueAtRender.current, nextSelectedValue)) {
34
+ forceUpdate();
42
35
  }
43
- return [
44
- payload[1],
45
- nextSelected
46
- ];
47
- } catch (e) {
48
- // ignored (stale props or some other reason)
36
+ } catch {
37
+ // ignored (stale props or similar — heals on the next parent-driven render)
49
38
  }
50
- // explicitly spread to enforce typing
51
- return [
52
- prevState[0],
53
- prevState[1]
54
- ]; // schedule update
55
- });
56
- };
57
- if (!Object.is(state[1], selected)) {
58
- // schedule re-render
59
- // this is safe because it's self contained
60
- dispatch(undefined);
61
- }
62
- const stableDispatch = useEventCallback(dispatch);
63
- useIsomorphicLayoutEffect(()=>{
64
- listeners.push(stableDispatch);
39
+ };
40
+ listeners.push(listener);
41
+ // Effect-fixup: catch updates that occurred between render and effect run (Relay's useFragmentInternal pattern).
42
+ listener(valueRef.current);
65
43
  return ()=>{
66
- const index = listeners.indexOf(stableDispatch);
67
- listeners.splice(index, 1);
44
+ const index = listeners.indexOf(listener);
45
+ if (index !== -1) {
46
+ listeners.splice(index, 1);
47
+ }
68
48
  };
69
49
  }, [
70
- stableDispatch,
71
- listeners
50
+ listeners,
51
+ valueRef
72
52
  ]);
73
- return state[1];
53
+ return valueAtRender;
74
54
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useContextSelector.ts"],"sourcesContent":["'use client';\n\nimport { useEventCallback, useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\n\nimport { Context, ContextSelector, ContextValue, ContextVersion } from './types';\n\n/**\n * @internal\n * This hook returns context selected value by selector.\n * It will only accept context created by `createContext`.\n * It will trigger re-render if only the selected value is referentially changed.\n */\nexport const useContextSelector = <Value, SelectedValue>(\n context: Context<Value>,\n selector: ContextSelector<Value, SelectedValue>,\n): SelectedValue => {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n\n const {\n value: { current: value },\n version: { current: version },\n listeners,\n } = contextValue;\n const selected = selector(value);\n\n const [state, setState] = React.useState<readonly [Value, SelectedValue]>([value, selected]);\n const dispatch = (\n payload:\n | undefined // undefined from render below\n | readonly [ContextVersion, Value], // from provider effect\n ) => {\n setState(prevState => {\n if (!payload) {\n // early bail out when is dispatched during render\n return [value, selected] as const;\n }\n\n if (payload[0] <= version) {\n if (Object.is(prevState[1], selected)) {\n return prevState; // bail out\n }\n\n return [value, selected] as const;\n }\n\n try {\n if (Object.is(prevState[0], payload[1])) {\n return prevState; // do not update\n }\n\n const nextSelected = selector(payload[1]);\n\n if (Object.is(prevState[1], nextSelected)) {\n return prevState; // do not update\n }\n\n return [payload[1], nextSelected] as const;\n } catch (e) {\n // ignored (stale props or some other reason)\n }\n\n // explicitly spread to enforce typing\n return [prevState[0], prevState[1]] as const; // schedule update\n });\n };\n\n if (!Object.is(state[1], selected)) {\n // schedule re-render\n // this is safe because it's self contained\n dispatch(undefined);\n }\n\n const stableDispatch = useEventCallback(dispatch);\n\n useIsomorphicLayoutEffect(() => {\n listeners.push(stableDispatch);\n\n return () => {\n const index = listeners.indexOf(stableDispatch);\n listeners.splice(index, 1);\n };\n }, [stableDispatch, listeners]);\n\n return state[1] as SelectedValue;\n};\n"],"names":["useEventCallback","useIsomorphicLayoutEffect","React","useContextSelector","context","selector","contextValue","useContext","value","current","version","listeners","selected","state","setState","useState","dispatch","payload","prevState","Object","is","nextSelected","e","undefined","stableDispatch","push","index","indexOf","splice"],"mappings":"AAAA;AAEA,SAASA,gBAAgB,EAAEC,yBAAyB,QAAQ,4BAA4B;AACxF,YAAYC,WAAW,QAAQ;AAI/B;;;;;CAKC,GACD,OAAO,MAAMC,qBAAqB,CAChCC,SACAC;IAEA,MAAMC,eAAeJ,MAAMK,UAAU,CAACH;IAEtC,MAAM,EACJI,OAAO,EAAEC,SAASD,KAAK,EAAE,EACzBE,SAAS,EAAED,SAASC,OAAO,EAAE,EAC7BC,SAAS,EACV,GAAGL;IACJ,MAAMM,WAAWP,SAASG;IAE1B,MAAM,CAACK,OAAOC,SAAS,GAAGZ,MAAMa,QAAQ,CAAkC;QAACP;QAAOI;KAAS;IAC3F,MAAMI,WAAW,CACfC;QAIAH,SAASI,CAAAA;YACP,IAAI,CAACD,SAAS;gBACZ,kDAAkD;gBAClD,OAAO;oBAACT;oBAAOI;iBAAS;YAC1B;YAEA,IAAIK,OAAO,CAAC,EAAE,IAAIP,SAAS;gBACzB,IAAIS,OAAOC,EAAE,CAACF,SAAS,CAAC,EAAE,EAAEN,WAAW;oBACrC,OAAOM,WAAW,WAAW;gBAC/B;gBAEA,OAAO;oBAACV;oBAAOI;iBAAS;YAC1B;YAEA,IAAI;gBACF,IAAIO,OAAOC,EAAE,CAACF,SAAS,CAAC,EAAE,EAAED,OAAO,CAAC,EAAE,GAAG;oBACvC,OAAOC,WAAW,gBAAgB;gBACpC;gBAEA,MAAMG,eAAehB,SAASY,OAAO,CAAC,EAAE;gBAExC,IAAIE,OAAOC,EAAE,CAACF,SAAS,CAAC,EAAE,EAAEG,eAAe;oBACzC,OAAOH,WAAW,gBAAgB;gBACpC;gBAEA,OAAO;oBAACD,OAAO,CAAC,EAAE;oBAAEI;iBAAa;YACnC,EAAE,OAAOC,GAAG;YACV,6CAA6C;YAC/C;YAEA,sCAAsC;YACtC,OAAO;gBAACJ,SAAS,CAAC,EAAE;gBAAEA,SAAS,CAAC,EAAE;aAAC,EAAW,kBAAkB;QAClE;IACF;IAEA,IAAI,CAACC,OAAOC,EAAE,CAACP,KAAK,CAAC,EAAE,EAAED,WAAW;QAClC,qBAAqB;QACrB,2CAA2C;QAC3CI,SAASO;IACX;IAEA,MAAMC,iBAAiBxB,iBAAiBgB;IAExCf,0BAA0B;QACxBU,UAAUc,IAAI,CAACD;QAEf,OAAO;YACL,MAAME,QAAQf,UAAUgB,OAAO,CAACH;YAChCb,UAAUiB,MAAM,CAACF,OAAO;QAC1B;IACF,GAAG;QAACF;QAAgBb;KAAU;IAE9B,OAAOE,KAAK,CAAC,EAAE;AACjB,EAAE"}
1
+ {"version":3,"sources":["../src/useContextSelector.ts"],"sourcesContent":["'use client';\n\nimport { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\n\nimport type { Context, ContextSelector, ContextValue } from './types';\n\n/**\n * This hook returns context selected value by selector.\n * It will only accept context created by `createContext`.\n * It will trigger re-render if only the selected value is referentially changed.\n *\n * @internal\n */\nexport const useContextSelector = <Value, SelectedValue>(\n context: Context<Value>,\n selectorFn: ContextSelector<Value, SelectedValue>,\n): SelectedValue => {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n const { value: valueRef, listeners } = contextValue;\n\n // Read valueRef during render and return selector(value) directly. This is analogous to `useSyncExternalStore`'s\n // `getSnapshot` and is the only way to select a slice from a shared ref-based store without re-rendering every\n // consumer on every provider update.\n const valueAtRender = selectorFn(valueRef.current);\n const [, forceUpdate] = React.useReducer((x: number) => x + 1, 0);\n\n // Refs holding the current selector and the most-recently-returned slice.\n // Updated in a layout effect (ordering: children first, then provider) so\n // they are current by the time the provider's listener loop fires.\n const selectorFnRef = React.useRef<ContextSelector<Value, SelectedValue>>(selectorFn);\n const lastValueAtRender = React.useRef<SelectedValue>(valueAtRender);\n\n useIsomorphicLayoutEffect(() => {\n selectorFnRef.current = selectorFn;\n lastValueAtRender.current = valueAtRender;\n });\n\n useIsomorphicLayoutEffect(() => {\n const listener = (payload: Value) => {\n // Selectors can throw on transiently-inconsistent inputs (stale props vs. newer context value). Swallow so a\n // single consumer's throw doesn't abort the provider's `listeners.forEach`.\n try {\n const nextSelectedValue = selectorFnRef.current(payload);\n\n if (!Object.is(lastValueAtRender.current, nextSelectedValue)) {\n forceUpdate();\n }\n } catch {\n // ignored (stale props or similar heals on the next parent-driven render)\n }\n };\n\n listeners.push(listener);\n\n // Effect-fixup: catch updates that occurred between render and effect run (Relay's useFragmentInternal pattern).\n listener(valueRef.current);\n\n return () => {\n const index = listeners.indexOf(listener);\n\n if (index !== -1) {\n listeners.splice(index, 1);\n }\n };\n }, [listeners, valueRef]);\n\n return valueAtRender;\n};\n"],"names":["useIsomorphicLayoutEffect","React","useContextSelector","context","selectorFn","contextValue","useContext","value","valueRef","listeners","valueAtRender","current","forceUpdate","useReducer","x","selectorFnRef","useRef","lastValueAtRender","listener","payload","nextSelectedValue","Object","is","push","index","indexOf","splice"],"mappings":"AAAA;AAEA,SAASA,yBAAyB,QAAQ,4BAA4B;AACtE,YAAYC,WAAW,QAAQ;AAI/B;;;;;;CAMC,GACD,OAAO,MAAMC,qBAAqB,CAChCC,SACAC;IAEA,MAAMC,eAAeJ,MAAMK,UAAU,CAACH;IACtC,MAAM,EAAEI,OAAOC,QAAQ,EAAEC,SAAS,EAAE,GAAGJ;IAEvC,iHAAiH;IACjH,+GAA+G;IAC/G,qCAAqC;IACrC,MAAMK,gBAAgBN,WAAWI,SAASG,OAAO;IACjD,MAAM,GAAGC,YAAY,GAAGX,MAAMY,UAAU,CAAC,CAACC,IAAcA,IAAI,GAAG;IAE/D,0EAA0E;IAC1E,0EAA0E;IAC1E,mEAAmE;IACnE,MAAMC,gBAAgBd,MAAMe,MAAM,CAAwCZ;IAC1E,MAAMa,oBAAoBhB,MAAMe,MAAM,CAAgBN;IAEtDV,0BAA0B;QACxBe,cAAcJ,OAAO,GAAGP;QACxBa,kBAAkBN,OAAO,GAAGD;IAC9B;IAEAV,0BAA0B;QACxB,MAAMkB,WAAW,CAACC;YAChB,6GAA6G;YAC7G,4EAA4E;YAC5E,IAAI;gBACF,MAAMC,oBAAoBL,cAAcJ,OAAO,CAACQ;gBAEhD,IAAI,CAACE,OAAOC,EAAE,CAACL,kBAAkBN,OAAO,EAAES,oBAAoB;oBAC5DR;gBACF;YACF,EAAE,OAAM;YACN,4EAA4E;YAC9E;QACF;QAEAH,UAAUc,IAAI,CAACL;QAEf,iHAAiH;QACjHA,SAASV,SAASG,OAAO;QAEzB,OAAO;YACL,MAAMa,QAAQf,UAAUgB,OAAO,CAACP;YAEhC,IAAIM,UAAU,CAAC,GAAG;gBAChBf,UAAUiB,MAAM,CAACF,OAAO;YAC1B;QACF;IACF,GAAG;QAACf;QAAWD;KAAS;IAExB,OAAOE;AACT,EAAE"}
@@ -1,16 +1,13 @@
1
1
  'use client';
2
2
  import * as React from 'react';
3
3
  /**
4
- * @internal
5
4
  * Utility hook for contexts created by react-context-selector to determine if a parent context exists
6
5
  * WARNING: This hook will not work for native React contexts
7
6
  *
7
+ * @internal
8
8
  * @param context - context created by react-context-selector
9
9
  * @returns whether the hook is wrapped by a parent context
10
10
  */ export function useHasParentContext(context) {
11
11
  const contextValue = React.useContext(context);
12
- if (contextValue.version) {
13
- return contextValue.version.current !== -1;
14
- }
15
- return false;
12
+ return !contextValue.isDefault;
16
13
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useHasParentContext.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Context, ContextValue } from './types';\n\n/**\n * @internal\n * Utility hook for contexts created by react-context-selector to determine if a parent context exists\n * WARNING: This hook will not work for native React contexts\n *\n * @param context - context created by react-context-selector\n * @returns whether the hook is wrapped by a parent context\n */\nexport function useHasParentContext<Value>(context: Context<Value>): boolean {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n\n if (contextValue.version) {\n return contextValue.version.current !== -1;\n }\n\n return false;\n}\n"],"names":["React","useHasParentContext","context","contextValue","useContext","version","current"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAG/B;;;;;;;CAOC,GACD,OAAO,SAASC,oBAA2BC,OAAuB;IAChE,MAAMC,eAAeH,MAAMI,UAAU,CAACF;IAEtC,IAAIC,aAAaE,OAAO,EAAE;QACxB,OAAOF,aAAaE,OAAO,CAACC,OAAO,KAAK,CAAC;IAC3C;IAEA,OAAO;AACT"}
1
+ {"version":3,"sources":["../src/useHasParentContext.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Context, ContextValue } from './types';\n\n/**\n * Utility hook for contexts created by react-context-selector to determine if a parent context exists\n * WARNING: This hook will not work for native React contexts\n *\n * @internal\n * @param context - context created by react-context-selector\n * @returns whether the hook is wrapped by a parent context\n */\nexport function useHasParentContext<Value>(context: Context<Value>): boolean {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n\n return !contextValue.isDefault;\n}\n"],"names":["React","useHasParentContext","context","contextValue","useContext","isDefault"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAG/B;;;;;;;CAOC,GACD,OAAO,SAASC,oBAA2BC,OAAuB;IAChE,MAAMC,eAAeH,MAAMI,UAAU,CAACF;IAEtC,OAAO,CAACC,aAAaE,SAAS;AAChC"}
@@ -17,26 +17,19 @@ const createProvider = (Original)=>{
17
17
  const Provider = (props)=>{
18
18
  // Holds an actual "props.value"
19
19
  const valueRef = _react.useRef(props.value);
20
- // Used to sync context updates and avoid stale values, can be considered as render/effect counter of Provider.
21
- const versionRef = _react.useRef(0);
22
20
  // A stable object, is used to avoid context updates via mutation of its values.
23
21
  const contextValue = _react.useRef(null);
24
22
  if (!contextValue.current) {
25
23
  contextValue.current = {
26
24
  value: valueRef,
27
- version: versionRef,
28
25
  listeners: []
29
26
  };
30
27
  }
31
28
  (0, _reactutilities.useIsomorphicLayoutEffect)(()=>{
32
29
  valueRef.current = props.value;
33
- versionRef.current += 1;
34
30
  (0, _scheduler.unstable_runWithPriority)(_scheduler.unstable_NormalPriority, ()=>{
35
31
  contextValue.current.listeners.forEach((listener)=>{
36
- listener([
37
- versionRef.current,
38
- props.value
39
- ]);
32
+ listener(props.value);
40
33
  });
41
34
  });
42
35
  }, [
@@ -57,10 +50,8 @@ const createContext = (defaultValue)=>{
57
50
  value: {
58
51
  current: defaultValue
59
52
  },
60
- version: {
61
- current: -1
62
- },
63
- listeners: []
53
+ listeners: [],
54
+ isDefault: true
64
55
  });
65
56
  context.Provider = createProvider(context.Provider);
66
57
  // We don't support Consumer API
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/createContext.ts"],"sourcesContent":["'use client';\n\nimport { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { unstable_NormalPriority as NormalPriority, unstable_runWithPriority as runWithPriority } from 'scheduler';\n\nimport { Context, ContextValue } from './types';\n\nconst createProvider = <Value>(Original: React.Provider<ContextValue<Value>>) => {\n const Provider: React.FC<React.ProviderProps<Value>> = props => {\n // Holds an actual \"props.value\"\n const valueRef = React.useRef(props.value);\n // Used to sync context updates and avoid stale values, can be considered as render/effect counter of Provider.\n const versionRef = React.useRef(0);\n\n // A stable object, is used to avoid context updates via mutation of its values.\n const contextValue = React.useRef<ContextValue<Value>>(null);\n\n if (!contextValue.current) {\n contextValue.current = {\n value: valueRef,\n version: versionRef,\n listeners: [],\n };\n }\n\n useIsomorphicLayoutEffect(() => {\n valueRef.current = props.value;\n versionRef.current += 1;\n\n runWithPriority(NormalPriority, () => {\n (contextValue.current as ContextValue<Value>).listeners.forEach(listener => {\n listener([versionRef.current, props.value]);\n });\n });\n }, [props.value]);\n\n return React.createElement(Original, { value: contextValue.current }, props.children);\n };\n\n /* istanbul ignore else */\n if (process.env.NODE_ENV !== 'production') {\n Provider.displayName = 'ContextSelector.Provider';\n }\n\n return Provider as unknown as React.Provider<ContextValue<Value>>;\n};\n\n/**\n * @internal\n */\nexport const createContext = <Value>(defaultValue: Value): Context<Value> => {\n // eslint-disable-next-line @fluentui/no-context-default-value\n const context = React.createContext<ContextValue<Value>>({\n value: { current: defaultValue },\n version: { current: -1 },\n listeners: [],\n });\n\n context.Provider = createProvider<Value>(context.Provider);\n\n // We don't support Consumer API\n delete (context as unknown as Context<Value>).Consumer;\n\n return context as unknown as Context<Value>;\n};\n"],"names":["createContext","createProvider","Original","Provider","props","valueRef","React","useRef","value","versionRef","contextValue","current","version","listeners","useIsomorphicLayoutEffect","runWithPriority","NormalPriority","forEach","listener","createElement","children","process","env","NODE_ENV","displayName","defaultValue","context","Consumer"],"mappings":"AAAA;;;;;+BAmDaA;;;eAAAA;;;;gCAjD6B;iEACnB;2BACgF;AAIvG,MAAMC,iBAAiB,CAAQC;IAC7B,MAAMC,WAAiDC,CAAAA;QACrD,gCAAgC;QAChC,MAAMC,WAAWC,OAAMC,MAAM,CAACH,MAAMI,KAAK;QACzC,+GAA+G;QAC/G,MAAMC,aAAaH,OAAMC,MAAM,CAAC;QAEhC,gFAAgF;QAChF,MAAMG,eAAeJ,OAAMC,MAAM,CAAsB;QAEvD,IAAI,CAACG,aAAaC,OAAO,EAAE;YACzBD,aAAaC,OAAO,GAAG;gBACrBH,OAAOH;gBACPO,SAASH;gBACTI,WAAW,EAAE;YACf;QACF;QAEAC,IAAAA,yCAAyB,EAAC;YACxBT,SAASM,OAAO,GAAGP,MAAMI,KAAK;YAC9BC,WAAWE,OAAO,IAAI;YAEtBI,IAAAA,mCAAe,EAACC,kCAAc,EAAE;gBAC7BN,aAAaC,OAAO,CAAyBE,SAAS,CAACI,OAAO,CAACC,CAAAA;oBAC9DA,SAAS;wBAACT,WAAWE,OAAO;wBAAEP,MAAMI,KAAK;qBAAC;gBAC5C;YACF;QACF,GAAG;YAACJ,MAAMI,KAAK;SAAC;QAEhB,OAAOF,OAAMa,aAAa,CAACjB,UAAU;YAAEM,OAAOE,aAAaC,OAAO;QAAC,GAAGP,MAAMgB,QAAQ;IACtF;IAEA,wBAAwB,GACxB,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzCpB,SAASqB,WAAW,GAAG;IACzB;IAEA,OAAOrB;AACT;AAKO,MAAMH,gBAAgB,CAAQyB;IACnC,8DAA8D;IAC9D,MAAMC,UAAUpB,OAAMN,aAAa,CAAsB;QACvDQ,OAAO;YAAEG,SAASc;QAAa;QAC/Bb,SAAS;YAAED,SAAS,CAAC;QAAE;QACvBE,WAAW,EAAE;IACf;IAEAa,QAAQvB,QAAQ,GAAGF,eAAsByB,QAAQvB,QAAQ;IAEzD,gCAAgC;IAChC,OAAO,AAACuB,QAAsCC,QAAQ;IAEtD,OAAOD;AACT"}
1
+ {"version":3,"sources":["../src/createContext.ts"],"sourcesContent":["'use client';\n\nimport { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\nimport { unstable_NormalPriority as NormalPriority, unstable_runWithPriority as runWithPriority } from 'scheduler';\n\nimport type { Context, ContextValue } from './types';\n\nconst createProvider = <Value>(Original: React.Provider<ContextValue<Value>>) => {\n const Provider: React.FC<React.ProviderProps<Value>> = props => {\n // Holds an actual \"props.value\"\n const valueRef = React.useRef(props.value);\n\n // A stable object, is used to avoid context updates via mutation of its values.\n const contextValue = React.useRef<ContextValue<Value>>(null);\n\n if (!contextValue.current) {\n contextValue.current = {\n value: valueRef,\n listeners: [],\n };\n }\n\n useIsomorphicLayoutEffect(() => {\n valueRef.current = props.value;\n\n runWithPriority(NormalPriority, () => {\n (contextValue.current as ContextValue<Value>).listeners.forEach(listener => {\n listener(props.value);\n });\n });\n }, [props.value]);\n\n return React.createElement(Original, { value: contextValue.current }, props.children);\n };\n\n /* istanbul ignore else */\n if (process.env.NODE_ENV !== 'production') {\n Provider.displayName = 'ContextSelector.Provider';\n }\n\n return Provider as unknown as React.Provider<ContextValue<Value>>;\n};\n\n/**\n * @internal\n */\nexport const createContext = <Value>(defaultValue: Value): Context<Value> => {\n // eslint-disable-next-line @fluentui/no-context-default-value\n const context = React.createContext<ContextValue<Value>>({\n value: { current: defaultValue },\n listeners: [],\n isDefault: true,\n });\n\n context.Provider = createProvider<Value>(context.Provider);\n\n // We don't support Consumer API\n delete (context as unknown as Context<Value>).Consumer;\n\n return context as unknown as Context<Value>;\n};\n"],"names":["createContext","createProvider","Original","Provider","props","valueRef","React","useRef","value","contextValue","current","listeners","useIsomorphicLayoutEffect","runWithPriority","NormalPriority","forEach","listener","createElement","children","process","env","NODE_ENV","displayName","defaultValue","context","isDefault","Consumer"],"mappings":"AAAA;;;;;+BA+CaA;;;eAAAA;;;;gCA7C6B;iEACnB;2BACgF;AAIvG,MAAMC,iBAAiB,CAAQC;IAC7B,MAAMC,WAAiDC,CAAAA;QACrD,gCAAgC;QAChC,MAAMC,WAAWC,OAAMC,MAAM,CAACH,MAAMI,KAAK;QAEzC,gFAAgF;QAChF,MAAMC,eAAeH,OAAMC,MAAM,CAAsB;QAEvD,IAAI,CAACE,aAAaC,OAAO,EAAE;YACzBD,aAAaC,OAAO,GAAG;gBACrBF,OAAOH;gBACPM,WAAW,EAAE;YACf;QACF;QAEAC,IAAAA,yCAAyB,EAAC;YACxBP,SAASK,OAAO,GAAGN,MAAMI,KAAK;YAE9BK,IAAAA,mCAAe,EAACC,kCAAc,EAAE;gBAC7BL,aAAaC,OAAO,CAAyBC,SAAS,CAACI,OAAO,CAACC,CAAAA;oBAC9DA,SAASZ,MAAMI,KAAK;gBACtB;YACF;QACF,GAAG;YAACJ,MAAMI,KAAK;SAAC;QAEhB,OAAOF,OAAMW,aAAa,CAACf,UAAU;YAAEM,OAAOC,aAAaC,OAAO;QAAC,GAAGN,MAAMc,QAAQ;IACtF;IAEA,wBAAwB,GACxB,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzClB,SAASmB,WAAW,GAAG;IACzB;IAEA,OAAOnB;AACT;AAKO,MAAMH,gBAAgB,CAAQuB;IACnC,8DAA8D;IAC9D,MAAMC,UAAUlB,OAAMN,aAAa,CAAsB;QACvDQ,OAAO;YAAEE,SAASa;QAAa;QAC/BZ,WAAW,EAAE;QACbc,WAAW;IACb;IAEAD,QAAQrB,QAAQ,GAAGF,eAAsBuB,QAAQrB,QAAQ;IAEzD,gCAAgC;IAChC,OAAO,AAACqB,QAAsCE,QAAQ;IAEtD,OAAOF;AACT"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { createContext } from './createContext';\nexport { useContextSelector } from './useContextSelector';\nexport { useHasParentContext } from './useHasParentContext';\n// eslint-disable-next-line @fluentui/ban-context-export\nexport type { Context, ContextSelector, ContextValue, ContextValues, ContextVersion } from './types';\n"],"names":["createContext","useContextSelector","useHasParentContext"],"mappings":";;;;;;;;;;;IAASA,aAAa;eAAbA,4BAAa;;IACbC,kBAAkB;eAAlBA,sCAAkB;;IAClBC,mBAAmB;eAAnBA,wCAAmB;;;+BAFE;oCACK;qCACC"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { createContext } from './createContext';\nexport { useContextSelector } from './useContextSelector';\nexport { useHasParentContext } from './useHasParentContext';\n// eslint-disable-next-line @fluentui/ban-context-export\nexport type { Context, ContextSelector, ContextValue } from './types';\n"],"names":["createContext","useContextSelector","useHasParentContext"],"mappings":";;;;;;;;;;;IAASA,aAAa;eAAbA,4BAAa;;IACbC,kBAAkB;eAAlBA,sCAAkB;;IAClBC,mBAAmB;eAAnBA,wCAAmB;;;+BAFE;oCACK;qCACC"}
@@ -2,5 +2,3 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
6
- const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import * as React from 'react';\n\n/**\n * @internal\n */\nexport type Context<Value> = React.Context<Value> & {\n Provider: React.FC<React.ProviderProps<Value>>;\n Consumer: never;\n};\n\nexport type ContextSelector<Value, SelectedValue> = (value: Value) => SelectedValue;\n\n/**\n * @internal\n */\nexport type ContextVersion = number;\n\n/**\n * @internal\n */\nexport type ContextValue<Value> = {\n /** Holds a set of subscribers from components. */\n listeners: ((payload: readonly [ContextVersion, Value]) => void)[];\n\n /** Holds an actual value of React's context that will be propagated down for computations. */\n value: // eslint-disable-next-line @typescript-eslint/no-deprecated\n React.MutableRefObject<Value>;\n\n /** A version field is used to sync a context value and consumers. */\n version: // eslint-disable-next-line @typescript-eslint/no-deprecated\n React.MutableRefObject<ContextVersion>;\n};\n\n/**\n * @internal\n */\nexport type ContextValues<Value> = ContextValue<Value> & {\n /** List of listners to publish changes */\n listeners: ((payload: readonly [ContextVersion, Record<string, Value>]) => void)[];\n};\n"],"names":[],"mappings":";;;;;iEAAuB"}
1
+ {"version":3,"sources":[],"names":[],"mappings":""}
@@ -12,69 +12,48 @@ Object.defineProperty(exports, "useContextSelector", {
12
12
  const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
13
13
  const _reactutilities = require("@fluentui/react-utilities");
14
14
  const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
15
- const useContextSelector = (context, selector)=>{
15
+ const useContextSelector = (context, selectorFn)=>{
16
16
  const contextValue = _react.useContext(context);
17
- const { value: { current: value }, version: { current: version }, listeners } = contextValue;
18
- const selected = selector(value);
19
- const [state, setState] = _react.useState([
20
- value,
21
- selected
22
- ]);
23
- const dispatch = (payload)=>{
24
- setState((prevState)=>{
25
- if (!payload) {
26
- // early bail out when is dispatched during render
27
- return [
28
- value,
29
- selected
30
- ];
31
- }
32
- if (payload[0] <= version) {
33
- if (Object.is(prevState[1], selected)) {
34
- return prevState; // bail out
35
- }
36
- return [
37
- value,
38
- selected
39
- ];
40
- }
17
+ const { value: valueRef, listeners } = contextValue;
18
+ // Read valueRef during render and return selector(value) directly. This is analogous to `useSyncExternalStore`'s
19
+ // `getSnapshot` and is the only way to select a slice from a shared ref-based store without re-rendering every
20
+ // consumer on every provider update.
21
+ const valueAtRender = selectorFn(valueRef.current);
22
+ const [, forceUpdate] = _react.useReducer((x)=>x + 1, 0);
23
+ // Refs holding the current selector and the most-recently-returned slice.
24
+ // Updated in a layout effect (ordering: children first, then provider) so
25
+ // they are current by the time the provider's listener loop fires.
26
+ const selectorFnRef = _react.useRef(selectorFn);
27
+ const lastValueAtRender = _react.useRef(valueAtRender);
28
+ (0, _reactutilities.useIsomorphicLayoutEffect)(()=>{
29
+ selectorFnRef.current = selectorFn;
30
+ lastValueAtRender.current = valueAtRender;
31
+ });
32
+ (0, _reactutilities.useIsomorphicLayoutEffect)(()=>{
33
+ const listener = (payload)=>{
34
+ // Selectors can throw on transiently-inconsistent inputs (stale props vs. newer context value). Swallow so a
35
+ // single consumer's throw doesn't abort the provider's `listeners.forEach`.
41
36
  try {
42
- if (Object.is(prevState[0], payload[1])) {
43
- return prevState; // do not update
37
+ const nextSelectedValue = selectorFnRef.current(payload);
38
+ if (!Object.is(lastValueAtRender.current, nextSelectedValue)) {
39
+ forceUpdate();
44
40
  }
45
- const nextSelected = selector(payload[1]);
46
- if (Object.is(prevState[1], nextSelected)) {
47
- return prevState; // do not update
48
- }
49
- return [
50
- payload[1],
51
- nextSelected
52
- ];
53
- } catch (e) {
54
- // ignored (stale props or some other reason)
41
+ } catch {
42
+ // ignored (stale props or similar — heals on the next parent-driven render)
55
43
  }
56
- // explicitly spread to enforce typing
57
- return [
58
- prevState[0],
59
- prevState[1]
60
- ]; // schedule update
61
- });
62
- };
63
- if (!Object.is(state[1], selected)) {
64
- // schedule re-render
65
- // this is safe because it's self contained
66
- dispatch(undefined);
67
- }
68
- const stableDispatch = (0, _reactutilities.useEventCallback)(dispatch);
69
- (0, _reactutilities.useIsomorphicLayoutEffect)(()=>{
70
- listeners.push(stableDispatch);
44
+ };
45
+ listeners.push(listener);
46
+ // Effect-fixup: catch updates that occurred between render and effect run (Relay's useFragmentInternal pattern).
47
+ listener(valueRef.current);
71
48
  return ()=>{
72
- const index = listeners.indexOf(stableDispatch);
73
- listeners.splice(index, 1);
49
+ const index = listeners.indexOf(listener);
50
+ if (index !== -1) {
51
+ listeners.splice(index, 1);
52
+ }
74
53
  };
75
54
  }, [
76
- stableDispatch,
77
- listeners
55
+ listeners,
56
+ valueRef
78
57
  ]);
79
- return state[1];
58
+ return valueAtRender;
80
59
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useContextSelector.ts"],"sourcesContent":["'use client';\n\nimport { useEventCallback, useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\n\nimport { Context, ContextSelector, ContextValue, ContextVersion } from './types';\n\n/**\n * @internal\n * This hook returns context selected value by selector.\n * It will only accept context created by `createContext`.\n * It will trigger re-render if only the selected value is referentially changed.\n */\nexport const useContextSelector = <Value, SelectedValue>(\n context: Context<Value>,\n selector: ContextSelector<Value, SelectedValue>,\n): SelectedValue => {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n\n const {\n value: { current: value },\n version: { current: version },\n listeners,\n } = contextValue;\n const selected = selector(value);\n\n const [state, setState] = React.useState<readonly [Value, SelectedValue]>([value, selected]);\n const dispatch = (\n payload:\n | undefined // undefined from render below\n | readonly [ContextVersion, Value], // from provider effect\n ) => {\n setState(prevState => {\n if (!payload) {\n // early bail out when is dispatched during render\n return [value, selected] as const;\n }\n\n if (payload[0] <= version) {\n if (Object.is(prevState[1], selected)) {\n return prevState; // bail out\n }\n\n return [value, selected] as const;\n }\n\n try {\n if (Object.is(prevState[0], payload[1])) {\n return prevState; // do not update\n }\n\n const nextSelected = selector(payload[1]);\n\n if (Object.is(prevState[1], nextSelected)) {\n return prevState; // do not update\n }\n\n return [payload[1], nextSelected] as const;\n } catch (e) {\n // ignored (stale props or some other reason)\n }\n\n // explicitly spread to enforce typing\n return [prevState[0], prevState[1]] as const; // schedule update\n });\n };\n\n if (!Object.is(state[1], selected)) {\n // schedule re-render\n // this is safe because it's self contained\n dispatch(undefined);\n }\n\n const stableDispatch = useEventCallback(dispatch);\n\n useIsomorphicLayoutEffect(() => {\n listeners.push(stableDispatch);\n\n return () => {\n const index = listeners.indexOf(stableDispatch);\n listeners.splice(index, 1);\n };\n }, [stableDispatch, listeners]);\n\n return state[1] as SelectedValue;\n};\n"],"names":["useContextSelector","context","selector","contextValue","React","useContext","value","current","version","listeners","selected","state","setState","useState","dispatch","payload","prevState","Object","is","nextSelected","e","undefined","stableDispatch","useEventCallback","useIsomorphicLayoutEffect","push","index","indexOf","splice"],"mappings":"AAAA;;;;;+BAaaA;;;eAAAA;;;;gCAX+C;iEACrC;AAUhB,MAAMA,qBAAqB,CAChCC,SACAC;IAEA,MAAMC,eAAeC,OAAMC,UAAU,CAACJ;IAEtC,MAAM,EACJK,OAAO,EAAEC,SAASD,KAAK,EAAE,EACzBE,SAAS,EAAED,SAASC,OAAO,EAAE,EAC7BC,SAAS,EACV,GAAGN;IACJ,MAAMO,WAAWR,SAASI;IAE1B,MAAM,CAACK,OAAOC,SAAS,GAAGR,OAAMS,QAAQ,CAAkC;QAACP;QAAOI;KAAS;IAC3F,MAAMI,WAAW,CACfC;QAIAH,SAASI,CAAAA;YACP,IAAI,CAACD,SAAS;gBACZ,kDAAkD;gBAClD,OAAO;oBAACT;oBAAOI;iBAAS;YAC1B;YAEA,IAAIK,OAAO,CAAC,EAAE,IAAIP,SAAS;gBACzB,IAAIS,OAAOC,EAAE,CAACF,SAAS,CAAC,EAAE,EAAEN,WAAW;oBACrC,OAAOM,WAAW,WAAW;gBAC/B;gBAEA,OAAO;oBAACV;oBAAOI;iBAAS;YAC1B;YAEA,IAAI;gBACF,IAAIO,OAAOC,EAAE,CAACF,SAAS,CAAC,EAAE,EAAED,OAAO,CAAC,EAAE,GAAG;oBACvC,OAAOC,WAAW,gBAAgB;gBACpC;gBAEA,MAAMG,eAAejB,SAASa,OAAO,CAAC,EAAE;gBAExC,IAAIE,OAAOC,EAAE,CAACF,SAAS,CAAC,EAAE,EAAEG,eAAe;oBACzC,OAAOH,WAAW,gBAAgB;gBACpC;gBAEA,OAAO;oBAACD,OAAO,CAAC,EAAE;oBAAEI;iBAAa;YACnC,EAAE,OAAOC,GAAG;YACV,6CAA6C;YAC/C;YAEA,sCAAsC;YACtC,OAAO;gBAACJ,SAAS,CAAC,EAAE;gBAAEA,SAAS,CAAC,EAAE;aAAC,EAAW,kBAAkB;QAClE;IACF;IAEA,IAAI,CAACC,OAAOC,EAAE,CAACP,KAAK,CAAC,EAAE,EAAED,WAAW;QAClC,qBAAqB;QACrB,2CAA2C;QAC3CI,SAASO;IACX;IAEA,MAAMC,iBAAiBC,IAAAA,gCAAgB,EAACT;IAExCU,IAAAA,yCAAyB,EAAC;QACxBf,UAAUgB,IAAI,CAACH;QAEf,OAAO;YACL,MAAMI,QAAQjB,UAAUkB,OAAO,CAACL;YAChCb,UAAUmB,MAAM,CAACF,OAAO;QAC1B;IACF,GAAG;QAACJ;QAAgBb;KAAU;IAE9B,OAAOE,KAAK,CAAC,EAAE;AACjB"}
1
+ {"version":3,"sources":["../src/useContextSelector.ts"],"sourcesContent":["'use client';\n\nimport { useIsomorphicLayoutEffect } from '@fluentui/react-utilities';\nimport * as React from 'react';\n\nimport type { Context, ContextSelector, ContextValue } from './types';\n\n/**\n * This hook returns context selected value by selector.\n * It will only accept context created by `createContext`.\n * It will trigger re-render if only the selected value is referentially changed.\n *\n * @internal\n */\nexport const useContextSelector = <Value, SelectedValue>(\n context: Context<Value>,\n selectorFn: ContextSelector<Value, SelectedValue>,\n): SelectedValue => {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n const { value: valueRef, listeners } = contextValue;\n\n // Read valueRef during render and return selector(value) directly. This is analogous to `useSyncExternalStore`'s\n // `getSnapshot` and is the only way to select a slice from a shared ref-based store without re-rendering every\n // consumer on every provider update.\n const valueAtRender = selectorFn(valueRef.current);\n const [, forceUpdate] = React.useReducer((x: number) => x + 1, 0);\n\n // Refs holding the current selector and the most-recently-returned slice.\n // Updated in a layout effect (ordering: children first, then provider) so\n // they are current by the time the provider's listener loop fires.\n const selectorFnRef = React.useRef<ContextSelector<Value, SelectedValue>>(selectorFn);\n const lastValueAtRender = React.useRef<SelectedValue>(valueAtRender);\n\n useIsomorphicLayoutEffect(() => {\n selectorFnRef.current = selectorFn;\n lastValueAtRender.current = valueAtRender;\n });\n\n useIsomorphicLayoutEffect(() => {\n const listener = (payload: Value) => {\n // Selectors can throw on transiently-inconsistent inputs (stale props vs. newer context value). Swallow so a\n // single consumer's throw doesn't abort the provider's `listeners.forEach`.\n try {\n const nextSelectedValue = selectorFnRef.current(payload);\n\n if (!Object.is(lastValueAtRender.current, nextSelectedValue)) {\n forceUpdate();\n }\n } catch {\n // ignored (stale props or similar heals on the next parent-driven render)\n }\n };\n\n listeners.push(listener);\n\n // Effect-fixup: catch updates that occurred between render and effect run (Relay's useFragmentInternal pattern).\n listener(valueRef.current);\n\n return () => {\n const index = listeners.indexOf(listener);\n\n if (index !== -1) {\n listeners.splice(index, 1);\n }\n };\n }, [listeners, valueRef]);\n\n return valueAtRender;\n};\n"],"names":["useContextSelector","context","selectorFn","contextValue","React","useContext","value","valueRef","listeners","valueAtRender","current","forceUpdate","useReducer","x","selectorFnRef","useRef","lastValueAtRender","useIsomorphicLayoutEffect","listener","payload","nextSelectedValue","Object","is","push","index","indexOf","splice"],"mappings":"AAAA;;;;;+BAcaA;;;eAAAA;;;;gCAZ6B;iEACnB;AAWhB,MAAMA,qBAAqB,CAChCC,SACAC;IAEA,MAAMC,eAAeC,OAAMC,UAAU,CAACJ;IACtC,MAAM,EAAEK,OAAOC,QAAQ,EAAEC,SAAS,EAAE,GAAGL;IAEvC,iHAAiH;IACjH,+GAA+G;IAC/G,qCAAqC;IACrC,MAAMM,gBAAgBP,WAAWK,SAASG,OAAO;IACjD,MAAM,GAAGC,YAAY,GAAGP,OAAMQ,UAAU,CAAC,CAACC,IAAcA,IAAI,GAAG;IAE/D,0EAA0E;IAC1E,0EAA0E;IAC1E,mEAAmE;IACnE,MAAMC,gBAAgBV,OAAMW,MAAM,CAAwCb;IAC1E,MAAMc,oBAAoBZ,OAAMW,MAAM,CAAgBN;IAEtDQ,IAAAA,yCAAyB,EAAC;QACxBH,cAAcJ,OAAO,GAAGR;QACxBc,kBAAkBN,OAAO,GAAGD;IAC9B;IAEAQ,IAAAA,yCAAyB,EAAC;QACxB,MAAMC,WAAW,CAACC;YAChB,6GAA6G;YAC7G,4EAA4E;YAC5E,IAAI;gBACF,MAAMC,oBAAoBN,cAAcJ,OAAO,CAACS;gBAEhD,IAAI,CAACE,OAAOC,EAAE,CAACN,kBAAkBN,OAAO,EAAEU,oBAAoB;oBAC5DT;gBACF;YACF,EAAE,OAAM;YACN,4EAA4E;YAC9E;QACF;QAEAH,UAAUe,IAAI,CAACL;QAEf,iHAAiH;QACjHA,SAASX,SAASG,OAAO;QAEzB,OAAO;YACL,MAAMc,QAAQhB,UAAUiB,OAAO,CAACP;YAEhC,IAAIM,UAAU,CAAC,GAAG;gBAChBhB,UAAUkB,MAAM,CAACF,OAAO;YAC1B;QACF;IACF,GAAG;QAAChB;QAAWD;KAAS;IAExB,OAAOE;AACT"}
@@ -13,8 +13,5 @@ const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildc
13
13
  const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
14
14
  function useHasParentContext(context) {
15
15
  const contextValue = _react.useContext(context);
16
- if (contextValue.version) {
17
- return contextValue.version.current !== -1;
18
- }
19
- return false;
16
+ return !contextValue.isDefault;
20
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useHasParentContext.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Context, ContextValue } from './types';\n\n/**\n * @internal\n * Utility hook for contexts created by react-context-selector to determine if a parent context exists\n * WARNING: This hook will not work for native React contexts\n *\n * @param context - context created by react-context-selector\n * @returns whether the hook is wrapped by a parent context\n */\nexport function useHasParentContext<Value>(context: Context<Value>): boolean {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n\n if (contextValue.version) {\n return contextValue.version.current !== -1;\n }\n\n return false;\n}\n"],"names":["useHasParentContext","context","contextValue","React","useContext","version","current"],"mappings":"AAAA;;;;;+BAagBA;;;eAAAA;;;;iEAXO;AAWhB,SAASA,oBAA2BC,OAAuB;IAChE,MAAMC,eAAeC,OAAMC,UAAU,CAACH;IAEtC,IAAIC,aAAaG,OAAO,EAAE;QACxB,OAAOH,aAAaG,OAAO,CAACC,OAAO,KAAK,CAAC;IAC3C;IAEA,OAAO;AACT"}
1
+ {"version":3,"sources":["../src/useHasParentContext.ts"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport type { Context, ContextValue } from './types';\n\n/**\n * Utility hook for contexts created by react-context-selector to determine if a parent context exists\n * WARNING: This hook will not work for native React contexts\n *\n * @internal\n * @param context - context created by react-context-selector\n * @returns whether the hook is wrapped by a parent context\n */\nexport function useHasParentContext<Value>(context: Context<Value>): boolean {\n const contextValue = React.useContext(context as unknown as Context<ContextValue<Value>>);\n\n return !contextValue.isDefault;\n}\n"],"names":["useHasParentContext","context","contextValue","React","useContext","isDefault"],"mappings":"AAAA;;;;;+BAagBA;;;eAAAA;;;;iEAXO;AAWhB,SAASA,oBAA2BC,OAAuB;IAChE,MAAMC,eAAeC,OAAMC,UAAU,CAACH;IAEtC,OAAO,CAACC,aAAaG,SAAS;AAChC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluentui/react-context-selector",
3
- "version": "9.2.15",
3
+ "version": "9.2.16",
4
4
  "description": "React useContextSelector hook in userland",
5
5
  "main": "lib-commonjs/index.js",
6
6
  "module": "lib/index.js",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "license": "MIT",
14
14
  "dependencies": {
15
- "@fluentui/react-utilities": "^9.26.2",
15
+ "@fluentui/react-utilities": "^9.26.3",
16
16
  "@swc/helpers": "^0.5.1"
17
17
  },
18
18
  "peerDependencies": {