Skip to content

Commit

Permalink
Move focus to active document (#1070)
Browse files Browse the repository at this point in the history
  • Loading branch information
gzdunek authored Aug 3, 2022
1 parent 7125001 commit 6d26558
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 16 deletions.
2 changes: 2 additions & 0 deletions web/packages/shared/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import useAttempt from './useAttempt';
import useFavicon from './useFavicon';
import useDocTitle from './useDocTitle';
import useAttemptNext from './useAttemptNext';
import { useRefAutoFocus } from './useRefAutoFocus';

export {
useRef,
Expand All @@ -29,4 +30,5 @@ export {
useEffect,
useFavicon,
useDocTitle,
useRefAutoFocus,
};
1 change: 1 addition & 0 deletions web/packages/shared/hooks/useRefAutoFocus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useRefAutoFocus';
76 changes: 76 additions & 0 deletions web/packages/shared/hooks/useRefAutoFocus/useRefAutoFocus.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { DependencyList } from 'react';

import { render } from 'design/utils/testing';

import { useRefAutoFocus } from './useRefAutoFocus';

test('focus automatically when allowed', () => {
const element = {
focus: jest.fn(),
};
render(<Focusable element={element} shouldFocus={true} />);
expect(element.focus).toHaveBeenCalledTimes(1);
});

test('do nothing when focus in not allowed', () => {
const element = {
focus: jest.fn(),
};
render(<Focusable element={element} shouldFocus={false} />);
expect(element.focus).not.toHaveBeenCalled();
});

test('refocus when deps list changes', () => {
const element = {
focus: jest.fn(),
};
const { rerender } = render(
<Focusable
element={element}
shouldFocus={true}
reFocusDeps={['old prop']}
/>
);
rerender(
<Focusable
element={element}
shouldFocus={true}
reFocusDeps={['new prop']}
/>
);
expect(element.focus).toHaveBeenCalledTimes(2);
});

test('do not refocus when deps list does not change', () => {
const element = {
focus: jest.fn(),
};
const { rerender } = render(
<Focusable
element={element}
shouldFocus={true}
reFocusDeps={['old prop']}
/>
);
rerender(
<Focusable
element={element}
shouldFocus={true}
reFocusDeps={['old prop']}
/>
);
expect(element.focus).toHaveBeenCalledTimes(1);
});

const Focusable = (props: {
element: { focus(): void };
shouldFocus: boolean;
reFocusDeps?: DependencyList;
}) => {
const ref = useRefAutoFocus({
shouldFocus: props.shouldFocus,
refocusDeps: props.reFocusDeps,
});
ref.current = props.element;
return null;
};
20 changes: 20 additions & 0 deletions web/packages/shared/hooks/useRefAutoFocus/useRefAutoFocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DependencyList, MutableRefObject, useEffect, useRef } from 'react';

/**
* Returns `ref` object that is automatically focused when `shouldFocus` is `true`.
* Focus can be also re triggered by changing any of the `refocusDeps`.
*/
export function useRefAutoFocus<T extends { focus(): void }>(options: {
shouldFocus: boolean;
refocusDeps?: DependencyList;
}): MutableRefObject<T> {
const ref = useRef<T>();

useEffect(() => {
if (options.shouldFocus) {
ref.current?.focus();
}
}, [options.shouldFocus, ref, ...(options.refocusDeps || [])]);

return ref;
}
41 changes: 26 additions & 15 deletions web/packages/teleterm/src/ui/Document/Document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,36 @@ limitations under the License.

import React from 'react';
import { Flex } from 'design';
import { useRefAutoFocus } from 'shared/hooks';

const Document: React.FC<{
visible: boolean;
onContextMenu?(): void;
autoFocusDisabled?: boolean;
[x: string]: any;
}> = ({ visible, children, onContextMenu, ...styles }) => (
<Flex
flex="1"
bg="primary.darker"
onContextMenu={onContextMenu}
style={{
overflow: 'auto',
display: visible ? 'flex' : 'none',
position: 'relative',
}}
{...styles}
>
{children}
</Flex>
);
}> = ({ visible, children, onContextMenu, autoFocusDisabled, ...styles }) => {
const ref = useRefAutoFocus<HTMLDivElement>({
shouldFocus: visible && !autoFocusDisabled,
});

return (
<Flex
tabIndex={visible ? 0 : -1}
flex="1"
ref={ref}
bg="primary.darker"
onContextMenu={onContextMenu}
style={{
overflow: 'auto',
display: visible ? 'flex' : 'none',
position: 'relative',
outline: 'none',
}}
{...styles}
>
{children}
</Flex>
);
};

export default Document;
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function DocumentTerminal(props: Props & { visible: boolean }) {
flexDirection="column"
pl={2}
onContextMenu={state.data?.openContextMenu}
autoFocusDisabled={true}
>
{ptyProcess && (
<Terminal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function DocumentsRenderer() {

function renderDocuments(documentsService: DocumentsService) {
return documentsService.getDocuments().map(doc => {
const isActiveDoc = doc === documentsService.getActive();
const isActiveDoc = workspacesService.isDocumentActive(doc.uri);
return <MemoizedDocument doc={doc} visible={isActiveDoc} key={doc.uri} />;
});
}
Expand Down
3 changes: 3 additions & 0 deletions web/packages/teleterm/src/ui/TabHost/TabHost.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ function getTestSetup({ documents }: { documents: Document[] }) {
{ clusterUri: 'test_uri', workspaceDocumentsService: docsService },
];
},
isDocumentActive(documentUri: string) {
return documentUri === documents[0].uri;
},
getRootClusterUri() {
return 'test_uri';
},
Expand Down

0 comments on commit 6d26558

Please sign in to comment.