@graphiql/react
Version:
[Changelog](https://github.com/graphql/graphiql/blob/main/packages/graphiql-react/CHANGELOG.md) | [API Docs](https://graphiql-test.netlify.app/typedoc/modules/graphiql_react.html) | [NPM](https://www.npmjs.com/package/@graphiql/react)
139 lines (121 loc) • 3.76 kB
text/typescript
import { useEffect, useRef } from 'react';
import { useExecutionContext } from '../execution';
import {
commonKeys,
DEFAULT_EDITOR_THEME,
DEFAULT_KEY_MAP,
importCodeMirror,
} from './common';
import { useEditorContext } from './context';
import {
useChangeHandler,
useKeyMap,
useMergeQuery,
usePrettifyEditors,
useSynchronizeOption,
} from './hooks';
import { WriteableEditorProps } from './types';
export type UseHeaderEditorArgs = WriteableEditorProps & {
/**
* Invoked when the contents of the headers editor change.
* @param value The new contents of the editor.
*/
onEdit?(value: string): void;
};
// To make react-compiler happy, otherwise complains about using dynamic imports in Component
function importCodeMirrorImports() {
return importCodeMirror([
// @ts-expect-error
import('codemirror/mode/javascript/javascript.js'),
]);
}
const _useHeaderEditor = useHeaderEditor;
export function useHeaderEditor(
{
editorTheme = DEFAULT_EDITOR_THEME,
keyMap = DEFAULT_KEY_MAP,
onEdit,
readOnly = false,
}: UseHeaderEditorArgs = {},
caller?: Function,
) {
const {
initialHeaders,
headerEditor,
setHeaderEditor,
shouldPersistHeaders,
} = useEditorContext({
nonNull: true,
caller: caller || _useHeaderEditor,
});
const executionContext = useExecutionContext();
const merge = useMergeQuery({ caller: caller || _useHeaderEditor });
const prettify = usePrettifyEditors({ caller: caller || _useHeaderEditor });
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let isActive = true;
void importCodeMirrorImports().then(CodeMirror => {
// Don't continue if the effect has already been cleaned up
if (!isActive) {
return;
}
const container = ref.current;
if (!container) {
return;
}
const newEditor = CodeMirror(container, {
value: initialHeaders,
lineNumbers: true,
tabSize: 2,
mode: { name: 'javascript', json: true },
theme: editorTheme,
autoCloseBrackets: true,
matchBrackets: true,
showCursorWhenSelecting: true,
readOnly: readOnly ? 'nocursor' : false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
extraKeys: commonKeys,
});
newEditor.addKeyMap({
'Cmd-Space'() {
newEditor.showHint({ completeSingle: false, container });
},
'Ctrl-Space'() {
newEditor.showHint({ completeSingle: false, container });
},
'Alt-Space'() {
newEditor.showHint({ completeSingle: false, container });
},
'Shift-Space'() {
newEditor.showHint({ completeSingle: false, container });
},
});
newEditor.on('keyup', (editorInstance, event) => {
const { code, key, shiftKey } = event;
const isLetter = code.startsWith('Key');
const isNumber = !shiftKey && code.startsWith('Digit');
if (isLetter || isNumber || key === '_' || key === '"') {
editorInstance.execCommand('autocomplete');
}
});
setHeaderEditor(newEditor);
});
return () => {
isActive = false;
};
}, [editorTheme, initialHeaders, readOnly, setHeaderEditor]);
useSynchronizeOption(headerEditor, 'keyMap', keyMap);
useChangeHandler(
headerEditor,
onEdit,
shouldPersistHeaders ? STORAGE_KEY : null,
'headers',
_useHeaderEditor,
);
useKeyMap(headerEditor, ['Cmd-Enter', 'Ctrl-Enter'], executionContext?.run);
useKeyMap(headerEditor, ['Shift-Ctrl-P'], prettify);
useKeyMap(headerEditor, ['Shift-Ctrl-M'], merge);
return ref;
}
export const STORAGE_KEY = 'headers';