Permissions popup context iso

This commit is contained in:
Josh Perez 2021-09-17 18:24:21 -04:00 committed by GitHub
parent f3715411c6
commit 7b5faa1cc1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 562 additions and 506 deletions

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { useEffect } from 'react';
import { usePrevious } from '../util/hooks';
import { usePrevious } from './usePrevious';
type RemoteParticipant = {
hasRemoteVideo: boolean;

View file

@ -0,0 +1,16 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ActionCreatorsMapObject, bindActionCreators } from 'redux';
import { useDispatch } from 'react-redux';
import { useMemo } from 'react';
export const useBoundActions = <T extends ActionCreatorsMapObject>(
actions: T
): T => {
const dispatch = useDispatch();
return useMemo(() => {
return bindActionCreators(actions, dispatch);
}, [actions, dispatch]);
};

View file

@ -0,0 +1,26 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { useEffect } from 'react';
export function useEscapeHandling(handleEscape?: () => unknown): void {
useEffect(() => {
if (!handleEscape) {
return;
}
const handler = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
handleEscape();
event.preventDefault();
event.stopPropagation();
}
};
document.addEventListener('keydown', handler);
return () => {
document.removeEventListener('keydown', handler);
};
}, [handleEscape]);
}

56
ts/hooks/useHasWrapped.ts Normal file
View file

@ -0,0 +1,56 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { Ref, useEffect, useState } from 'react';
import { first, last, noop } from 'lodash';
function getTop(element: Readonly<Element>): number {
return element.getBoundingClientRect().top;
}
function isWrapped(element: Readonly<null | HTMLElement>): boolean {
if (!element) {
return false;
}
const { children } = element;
const firstChild = first(children);
const lastChild = last(children);
return Boolean(
firstChild &&
lastChild &&
firstChild !== lastChild &&
getTop(firstChild) !== getTop(lastChild)
);
}
/**
* A hook that returns a ref (to put on your element) and a boolean. The boolean will be
* `true` if the element's children have different `top`s, and `false` otherwise.
*/
export function useHasWrapped<T extends HTMLElement>(): [Ref<T>, boolean] {
const [element, setElement] = useState<null | T>(null);
const [hasWrapped, setHasWrapped] = useState(isWrapped(element));
useEffect(() => {
if (!element) {
return noop;
}
// We can remove this `any` when we upgrade to TypeScript 4.2+, which adds
// `ResizeObserver` type definitions.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const observer = new (window as any).ResizeObserver(() => {
setHasWrapped(isWrapped(element));
});
observer.observe(element);
return () => {
observer.disconnect();
};
}, [element]);
return [setElement, hasWrapped];
}

View file

@ -0,0 +1,64 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { useCallback, useRef, useState } from 'react';
import * as log from '../logging/log';
/**
* A light hook wrapper around `IntersectionObserver`.
*
* Example usage:
*
* function MyComponent() {
* const [intersectionRef, intersectionEntry] = useIntersectionObserver();
* const isVisible = intersectionEntry
* ? intersectionEntry.isIntersecting
* : true;
*
* return (
* <div ref={intersectionRef}>
* I am {isVisible ? 'on the screen' : 'invisible'}
* </div>
* );
* }
*/
export function useIntersectionObserver(): [
(el?: Element | null) => void,
IntersectionObserverEntry | null
] {
const [
intersectionObserverEntry,
setIntersectionObserverEntry,
] = useState<IntersectionObserverEntry | null>(null);
const unobserveRef = useRef<(() => unknown) | null>(null);
const setRef = useCallback((el?: Element | null) => {
if (unobserveRef.current) {
unobserveRef.current();
unobserveRef.current = null;
}
if (!el) {
return;
}
const observer = new IntersectionObserver(entries => {
if (entries.length !== 1) {
log.error(
'IntersectionObserverWrapper was observing the wrong number of elements'
);
return;
}
entries.forEach(entry => {
setIntersectionObserverEntry(entry);
});
});
unobserveRef.current = observer.unobserve.bind(observer, el);
observer.observe(el);
}, []);
return [setRef, intersectionObserverEntry];
}

View file

@ -0,0 +1,26 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { useEffect, useState } from 'react';
export function usePageVisibility(): boolean {
const [result, setResult] = useState(!document.hidden);
useEffect(() => {
const onVisibilityChange = () => {
setResult(!document.hidden);
};
document.addEventListener('visibilitychange', onVisibilityChange, false);
return () => {
document.removeEventListener(
'visibilitychange',
onVisibilityChange,
false
);
};
}, []);
return result;
}

11
ts/hooks/usePrevious.ts Normal file
View file

@ -0,0 +1,11 @@
// Copyright 2020-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { useRef } from 'react';
export function usePrevious<T>(initialValue: T, currentValue: T): T {
const previousValueRef = useRef<T>(initialValue);
const result = previousValueRef.current;
previousValueRef.current = currentValue;
return result;
}

View file

@ -0,0 +1,47 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as React from 'react';
type CallbackType = (toFocus: HTMLElement | null | undefined) => void;
// Restore focus on teardown
export const useRestoreFocus = (): Array<CallbackType> => {
const toFocusRef = React.useRef<HTMLElement | null>(null);
const lastFocusedRef = React.useRef<HTMLElement | null>(null);
// We need to use a callback here because refs aren't necessarily populated on first
// render. For example, ModalHost makes a top-level parent div first, and then renders
// into it. And the children you pass it don't have access to that root div.
const setFocusRef = React.useCallback(
(toFocus: HTMLElement | null | undefined) => {
if (!toFocus) {
return;
}
// We only want to do this once.
if (toFocusRef.current) {
return;
}
toFocusRef.current = toFocus;
// Remember last-focused element, focus this new target element.
lastFocusedRef.current = document.activeElement as HTMLElement;
toFocus.focus();
},
[]
);
React.useEffect(() => {
return () => {
// On unmount, returned focus to element focused before we set the focus
setTimeout(() => {
if (lastFocusedRef.current && lastFocusedRef.current.focus) {
lastFocusedRef.current.focus();
}
});
};
}, []);
return [setFocusRef];
};