Skip to content

Commit 70c19a1

Browse files
committed
Add stable state hook
1 parent 41b9c0c commit 70c19a1

File tree

5 files changed

+110
-1
lines changed

5 files changed

+110
-1
lines changed

packages/component/src/Attachment/Text/private/ActivityCopyButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { instance, nullable, object, optional, pipe, readonly, string, type Infe
88
import useStyleSet from '../../../hooks/useStyleSet';
99
import ClipboardWritePermissionComposer, {
1010
useClipboardWritePermissionHooks
11-
} from '../../../providers/ClipboardWritePermission/ClipboardWritePermissionComposer';
11+
} from '../../../providers/ClipboardWritePermissionWithStable/ClipboardWritePermissionComposer';
1212
import { useQueueStaticElement } from '../../../providers/LiveRegionTwin';
1313
import refObject from '../../../types/internal/refObject';
1414
import ActivityButton from './ActivityButton';
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// import { reactNode, validateProps } from 'botframework-webchat-react-valibot';
2+
import { validateProps } from 'botframework-webchat-api/internal';
3+
import React, { createContext, memo, useContext, useEffect, useMemo, useState } from 'react';
4+
import { object, optional, pipe, readonly, type InferInput } from 'valibot';
5+
6+
import reactNode from '../../types/internal/reactNode';
7+
import useStableStateHook from './private/useStableStateHook';
8+
9+
const clipboardWritePermissionComposerPropsSchema = pipe(
10+
object({
11+
children: optional(reactNode())
12+
}),
13+
readonly()
14+
);
15+
16+
type ClipboardWritePermissionComposerProps = InferInput<typeof clipboardWritePermissionComposerPropsSchema>;
17+
18+
type ClipboardWritePermissionContextType = Readonly<{
19+
usePermissionGranted: () => readonly [boolean];
20+
}>;
21+
22+
const ClipboardWritePermissionContext = createContext<ClipboardWritePermissionContextType>({} as any);
23+
24+
function ClipboardWritePermissionComposer(props: ClipboardWritePermissionComposerProps) {
25+
const { children } = validateProps(clipboardWritePermissionComposerPropsSchema, props);
26+
27+
const [permissionGranted, setPermissionGranted] = useState(false);
28+
29+
const usePermissionGranted = useStableStateHook(permissionGranted);
30+
31+
const context = useMemo<ClipboardWritePermissionContextType>(
32+
() =>
33+
Object.freeze({
34+
usePermissionGranted
35+
}),
36+
[usePermissionGranted]
37+
);
38+
39+
useEffect(() => {
40+
let unmounted = false;
41+
42+
(async function () {
43+
if ((await navigator.permissions.query({ name: 'clipboard-write' as any })).state === 'granted') {
44+
unmounted || setPermissionGranted(true);
45+
}
46+
})();
47+
48+
return () => {
49+
unmounted = true;
50+
};
51+
}, [setPermissionGranted]);
52+
53+
return (
54+
<ClipboardWritePermissionContext.Provider value={context}>{children}</ClipboardWritePermissionContext.Provider>
55+
);
56+
}
57+
58+
function useClipboardWritePermissionHooks(): Readonly<{
59+
usePermissionGranted(): readonly [boolean];
60+
}> {
61+
return useContext(ClipboardWritePermissionContext);
62+
}
63+
64+
export default memo(ClipboardWritePermissionComposer);
65+
export { useClipboardWritePermissionHooks };
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useCallback, useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from 'react';
2+
import { createPropagation } from 'use-propagate';
3+
import { useRefFrom } from 'use-ref-from';
4+
5+
export default function useStableStateHook<T>(value: T): () => readonly [T];
6+
7+
export default function useStableStateHook<T>(
8+
value: T,
9+
setValue: Dispatch<SetStateAction<T>>
10+
): () => readonly [T, Dispatch<SetStateAction<T>>];
11+
12+
export default function useStableStateHook<T>(
13+
value: T,
14+
setValue?: Dispatch<SetStateAction<T>> | undefined
15+
): () => readonly [T, Dispatch<SetStateAction<T>>] | readonly [T] {
16+
const propagationRef = useRef<ReturnType<typeof createPropagation<T>>>();
17+
const valueRef = useRefFrom(value);
18+
19+
if (!propagationRef.current) {
20+
propagationRef.current = createPropagation<T>();
21+
}
22+
23+
const {
24+
current: { usePropagate, useListen }
25+
} = propagationRef;
26+
27+
const propagate = usePropagate();
28+
29+
useEffect(() => propagate(value), [propagate, value]);
30+
31+
const useHook = () => {
32+
const [propagatedValue, setPropagatedValue] = useState<T>(valueRef.current);
33+
34+
useListen(setPropagatedValue);
35+
36+
return useMemo(
37+
() => Object.freeze(setValue ? ([propagatedValue, setValue] as const) : ([propagatedValue] as const)),
38+
// eslint-disable-next-line react-hooks/exhaustive-deps
39+
[propagatedValue, setValue]
40+
);
41+
};
42+
43+
return useCallback(useHook, [useListen, setValue, valueRef]);
44+
}

packages/component/src/providers/ClipboardWritePermission/ClipboardWritePermissionComposer.tsx renamed to packages/component/src/providers/ClipboardWritePermissionWithStateContext/ClipboardWritePermissionComposer.tsx

File renamed without changes.

packages/component/src/providers/ClipboardWritePermission/private/createStateContextWithHook.tsx renamed to packages/component/src/providers/ClipboardWritePermissionWithStateContext/private/createStateContextWithHook.tsx

File renamed without changes.

0 commit comments

Comments
 (0)