From 7c5f11f5304797d22aea8ebad79dafa388ec27c4 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 20:28:44 +0200 Subject: [PATCH 001/255] use execution store --- .changeset/five-cars-roll.md | 12 + .../src/__tests__/components.spec.tsx | 2 +- packages/graphiql-react/src/editor/index.ts | 6 +- packages/graphiql-react/src/execution.tsx | 220 +++++++++--------- packages/graphiql-react/src/index.ts | 6 +- 5 files changed, 126 insertions(+), 120 deletions(-) create mode 100644 .changeset/five-cars-roll.md diff --git a/.changeset/five-cars-roll.md b/.changeset/five-cars-roll.md new file mode 100644 index 00000000000..2cbde8418a9 --- /dev/null +++ b/.changeset/five-cars-roll.md @@ -0,0 +1,12 @@ +--- +'@graphiql/plugin-doc-explorer': patch +'@graphiql/plugin-explorer': patch +'@graphiql/plugin-history': patch +'@graphiql/react': minor +'graphiql': patch +--- + +feat(@graphiql/react): migrate React context to zustand: + - replace `useExecutionContext` with `useExecutionStore` hook + - replace `useEditorContext` with `useEditorStore` hook + - replace `useAutoCompleteLeafs` hook with `getAutoCompleteLeafs` function diff --git a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx index 749ccdcb3ae..22c9be5e6c8 100644 --- a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx +++ b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx @@ -24,7 +24,7 @@ vi.mock('@graphiql/react', async () => { tabs: [], }; }, - useExecutionContext() { + useExecutionStore() { return {}; }, }; diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index dfeb1a18ff2..bd17bb0cefa 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -5,11 +5,7 @@ export { ResponseEditor, VariableEditor, } from './components'; -export { - EditorContext, - EditorContextProvider, - useEditorContext, -} from './context'; +export { EditorContextProvider, useEditorStore } from './context'; export { useHeaderEditor } from './header-editor'; export { useAutoCompleteLeafs, diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index 605790b9266..315d7e21a42 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -1,7 +1,9 @@ +// eslint-disable-next-line react/jsx-filename-extension -- TODO import { Fetcher, formatError, formatResult, + GetDefaultFieldNamesFn, isAsyncIterable, isObservable, Unsubscribable, @@ -13,29 +15,33 @@ import { print, } from 'graphql'; import { getFragmentDependenciesForAST } from 'graphql-language-service'; -import { FC, ReactNode, useRef, useState } from 'react'; +import { FC, ReactNode, useEffect } from 'react'; import setValue from 'set-value'; import getValue from 'get-value'; -import { useAutoCompleteLeafs, useEditorContext } from './editor'; -import { UseAutoCompleteLeafsArgs } from './editor/hooks'; -import { createContextHook, createNullableContext } from './utility/context'; +import { getAutoCompleteLeafs } from './editor'; +import { createStore, useStore } from 'zustand'; +import { editorStore } from './editor/context'; +import { schemaStore } from './schema'; export type ExecutionContextType = { /** * If there is currently a GraphQL request in-flight. For multipart * requests like subscriptions, this will be `true` while fetching the * first partial response and `false` while fetching subsequent batches. + * @default false */ isFetching: boolean; /** * If there is currently a GraphQL request in-flight. For multipart * requests like subscriptions, this will be `true` until the last batch * has been fetched or the connection is closed from the client. + * @default false */ isSubscribed: boolean; /** * The operation name that will be sent with all GraphQL requests. + * @default null */ operationName: string | null; /** @@ -46,13 +52,21 @@ export type ExecutionContextType = { * Stop the GraphQL request that is currently in-flight. */ stop(): void; + subscription: Unsubscribable | null; + /** + * A function to determine which field leafs are automatically added when + * trying to execute a query with missing selection sets. It will be called + * with the `GraphQLType` for which fields need to be added. + */ + getDefaultFieldNames?: GetDefaultFieldNamesFn; + /** + * @default 0 + */ + queryId: number; }; -export const ExecutionContext = - createNullableContext('ExecutionContext'); - type ExecutionContextProviderProps = Pick< - UseAutoCompleteLeafsArgs, + ExecutionContextType, 'getDefaultFieldNames' > & { children: ReactNode; @@ -73,44 +87,34 @@ type ExecutionContextProviderProps = Pick< operationName?: string; }; -export const ExecutionContextProvider: FC = ({ - fetcher, - getDefaultFieldNames, - children, - operationName, -}) => { - if (typeof fetcher !== 'function') { - throw new TypeError( - 'The `ExecutionContextProvider` component requires a `fetcher` function to be passed as prop.', - ); - } - - const { - externalFragments, - headerEditor, - queryEditor, - responseEditor, - variableEditor, - updateActiveTabValues, - } = useEditorContext({ nonNull: true, caller: ExecutionContextProvider }); - const autoCompleteLeafs = useAutoCompleteLeafs({ - getDefaultFieldNames, - caller: ExecutionContextProvider, - }); - const [isFetching, setIsFetching] = useState(false); - const [subscription, setSubscription] = useState(null); - const queryIdRef = useRef(0); - - const stop = () => { +export const executionStore = createStore< + ExecutionContextType & + Pick +>((set, get) => ({ + isFetching: false, + isSubscribed: false, + subscription: null, + operationName: null, + getDefaultFieldNames: undefined, + queryId: 0, + stop() { + const { subscription } = get(); subscription?.unsubscribe(); - setIsFetching(false); - setSubscription(null); - }; - - const run: ExecutionContextType['run'] = async () => { + set({ isFetching: false, subscription: null }); + }, + async run() { + const { + externalFragments, + headerEditor, + queryEditor, + responseEditor, + variableEditor, + updateActiveTabValues, + } = editorStore.getState(); if (!queryEditor || !responseEditor) { return; } + const { subscription, operationName, queryId } = get(); // If there's an active subscription, unsubscribe it and return if (subscription) { @@ -123,13 +127,13 @@ export const ExecutionContextProvider: FC = ({ updateActiveTabValues({ response: value }); }; - queryIdRef.current += 1; - const queryId = queryIdRef.current; + const newQueryId = queryId + 1; + set({ queryId: newQueryId }); // Use the edited query after autoCompleteLeafs() runs or, // in case autoCompletion fails (the function returns undefined), // the current query from the editor. - let query = autoCompleteLeafs() || queryEditor.getValue(); + let query = getAutoCompleteLeafs() || queryEditor.getValue(); const variablesString = variableEditor?.getValue(); let variables: Record | undefined; @@ -174,17 +178,13 @@ export const ExecutionContextProvider: FC = ({ } setResponse(''); - setIsFetching(true); - // Can't be moved in try-catch since react-compiler throw `Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement` - const opName = operationName ?? queryEditor.operationName ?? undefined; - const _headers = headers ?? undefined; - const documentAST = queryEditor.documentAST ?? undefined; + set({ isFetching: true }); try { const fullResponse: ExecutionResult = {}; const handleResponse = (result: ExecutionResult) => { // A different query was dispatched in the meantime, so don't // show the results of this one. - if (queryId !== queryIdRef.current) { + if (newQueryId !== get().queryId) { return; } @@ -203,24 +203,24 @@ export const ExecutionContextProvider: FC = ({ mergeIncrementalResult(fullResponse, part); } - setIsFetching(false); + set({ isFetching: false }); setResponse(formatResult(fullResponse)); } else { - const response = formatResult(result); - setIsFetching(false); - setResponse(response); + set({ isFetching: false }); + setResponse(formatResult(result)); } }; - + const { fetcher } = schemaStore.getState(); const fetch = fetcher( { query, variables, - operationName: opName, + operationName: + operationName ?? queryEditor.operationName ?? undefined, }, { - headers: _headers, - documentAST, + headers: headers ?? undefined, + documentAST: queryEditor.documentAST ?? undefined, }, ); @@ -229,73 +229,75 @@ export const ExecutionContextProvider: FC = ({ // If the fetcher returned an Observable, then subscribe to it, calling // the callback on each next value and handling both errors and the // completion of the Observable. - setSubscription( - value.subscribe({ - next(result) { - handleResponse(result); - }, - error(error: Error) { - setIsFetching(false); - if (error) { - setResponse(formatError(error)); - } - setSubscription(null); - }, - complete() { - setIsFetching(false); - setSubscription(null); - }, - }), - ); + const newSubscription = value.subscribe({ + next(result) { + handleResponse(result); + }, + error(error: Error) { + set({ isFetching: false }); + if (error) { + setResponse(formatError(error)); + } + set({ subscription: null }); + }, + complete() { + set({ isFetching: false, subscription: null }); + }, + }); + set({ subscription: newSubscription }); } else if (isAsyncIterable(value)) { - setSubscription({ + const newSubscription = { unsubscribe: () => value[Symbol.asyncIterator]().return?.(), - }); - await handleAsyncResults(handleResponse, value); - setIsFetching(false); - setSubscription(null); + }; + set({ subscription: newSubscription }); + for await (const result of value) { + handleResponse(result); + } + set({ isFetching: false, subscription: null }); } else { handleResponse(value); } } catch (error) { - setIsFetching(false); + set({ isFetching: false }); setResponse(formatError(error)); - setSubscription(null); + set({ subscription: null }); } - }; - const value: ExecutionContextType = { - isFetching, - isSubscribed: Boolean(subscription), - operationName: operationName ?? null, - run, - stop, - }; - - return ( - - {children} - - ); -}; + }, +})); -// Extract function because react-compiler doesn't support `for await` yet -async function handleAsyncResults( - onResponse: (result: ExecutionResult) => void, - value: any, -): Promise { - for await (const result of value) { - onResponse(result); +// @ts-expect-error -- ignore `children` type warning +export const ExecutionContextProvider: FC = ({ + fetcher, + getDefaultFieldNames, + children, + operationName = null, +}) => { + if (!fetcher) { + throw new TypeError( + 'The `ExecutionContextProvider` component requires a `fetcher` function to be passed as prop.', + ); } -} + useEffect(() => { + executionStore.setState({ + // isSubscribed: Boolean(subscription), + operationName, + getDefaultFieldNames, + }); + }, [getDefaultFieldNames, operationName]); + + return children; +}; -export const useExecutionContext = createContextHook(ExecutionContext); +export function useExecutionStore() { + return useStore(executionStore); +} function tryParseJsonObject({ json, errorMessageParse, errorMessageType, }: { - json: string | undefined; + json?: string; errorMessageParse: string; errorMessageType: string; }) { diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index afc7edefce2..41a4a36919e 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -23,11 +23,7 @@ export { useHeadersEditorState, VariableEditor, } from './editor'; -export { - ExecutionContext, - ExecutionContextProvider, - useExecutionContext, -} from './execution'; +export { ExecutionContextProvider, useExecutionStore } from './execution'; export { PluginContextProvider, usePluginStore } from './plugin'; export { GraphiQLProvider } from './provider'; export { SchemaContextProvider, useSchemaStore } from './schema'; From 53cb535ee876af189a52ca5f7edcc76289448733 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 20:54:59 +0200 Subject: [PATCH 002/255] upd --- .../graphiql-react/src/editor/context.tsx | 121 +++++++++++------- packages/graphiql-react/src/editor/hooks.ts | 107 ++++++---------- packages/graphiql-react/src/editor/index.ts | 4 +- packages/graphiql-react/src/execution.tsx | 5 +- packages/graphiql-react/src/index.ts | 2 +- 5 files changed, 119 insertions(+), 120 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index c660e5fcb33..5b52b00a2a7 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -31,6 +31,7 @@ import { import { CodeMirrorEditor } from './types'; import { STORAGE_KEY as STORAGE_KEY_VARIABLES } from './variable-editor'; import { DEFAULT_QUERY } from '../constants'; +import { createStore } from 'zustand'; export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { documentAST: DocumentNode | null; @@ -39,28 +40,7 @@ export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { variableToType: VariableToType | null; }; -export type EditorContextType = TabsState & { - /** - * Add a new tab. - */ - addTab(): void; - /** - * Switch to a different tab. - * @param index The index of the tab that should be switched to. - */ - changeTab(index: number): void; - /** - * Move a tab to a new spot. - * @param newOrder The new order for the tabs. - */ - moveTab(newOrder: TabState[]): void; - /** - * Close a tab. If the currently active tab is closed, the tab before it will - * become active. If there is no tab before the closed one, the tab after it - * will become active. - * @param index The index of the tab that should be closed. - */ - closeTab(index: number): void; +interface EditorStore extends TabsState { /** * Update the state for the tab that is currently active. This will be * reflected in the `tabs` object and the state will be persisted in storage @@ -71,7 +51,6 @@ export type EditorContextType = TabsState & { updateActiveTabValues( partialTab: Partial>, ): void; - /** * The CodeMirror editor instance for the headers editor. */ @@ -106,6 +85,44 @@ export type EditorContextType = TabsState & { * Set the CodeMirror editor instance for the variables editor. */ setVariableEditor(newEditor: CodeMirrorEditor): void; + /** + * A map of fragment definitions using the fragment name as key which are + * made available to include in the query. + */ + externalFragments: Map; + /** + * Invoked when the state of the tabs changes. Possible triggers are: + * - Updating any editor contents inside the currently active tab + * - Adding a tab + * - Switching to a different tab + * - Closing a tab + * @param tabState The tab state after it has been updated. + */ + onTabChange?(tabState: TabsState): void; +} + +export type EditorContextType = { + /** + * Add a new tab. + */ + addTab(): void; + /** + * Switch to a different tab. + * @param index The index of the tab that should be switched to. + */ + changeTab(index: number): void; + /** + * Move a tab to a new spot. + * @param newOrder The new order for the tabs. + */ + moveTab(newOrder: TabState[]): void; + /** + * Close a tab. If the currently active tab is closed, the tab before it will + * become active. If there is no tab before the closed one, the tab after it + * will become active. + * @param index The index of the tab that should be closed. + */ + closeTab(index: number): void; /** * Changes the operation name and invokes the `onEditOperationName` callback. @@ -133,11 +150,6 @@ export type EditorContextType = TabsState & { */ initialVariables: string; - /** - * A map of fragment definitions using the fragment name as key which are - * made available to include in the query. - */ - externalFragments: Map; /** * A list of custom validation rules that are run in addition to the rules * provided by the GraphQL spec. @@ -157,7 +169,7 @@ export type EditorContextType = TabsState & { export const EditorContext = createNullableContext('EditorContext'); -type EditorContextProviderProps = { +type EditorContextProviderProps = Pick & { children: ReactNode; /** * The initial contents of the query editor when loading GraphiQL and there @@ -206,15 +218,6 @@ type EditorContextProviderProps = { * @param operationName The operation name after it has been changed. */ onEditOperationName?(operationName: string): void; - /** - * Invoked when the state of the tabs changes. Possible triggers are: - * - Updating any editor contents inside the currently active tab - * - Adding a tab - * - Switching to a different tab - * - Closing a tab - * @param tabState The tab state after it has been updated. - */ - onTabChange?(tabState: TabsState): void; /** * This prop can be used to set the contents of the query editor. Every time * this prop changes, the contents of the query editor are replaced. Note @@ -255,6 +258,38 @@ type EditorContextProviderProps = { defaultHeaders?: string; }; +export const editorStore = createStore((set, get) => ({ + tabs: [], + activeTabIndex: 0, + updateActiveTabValues(partialTab) { + set(current => { + const { onTabChange } = get(); + const updated = setPropertiesInActiveTab(current, partialTab); + storeTabs(updated); + onTabChange?.(updated); + return updated; + }); + }, + headerEditor: null!, + queryEditor: null!, + responseEditor: null!, + variableEditor: null!, + setHeaderEditor(headerEditor) { + set({ headerEditor }); + }, + setQueryEditor(queryEditor) { + set({ queryEditor }); + }, + setResponseEditor(responseEditor) { + set({ responseEditor }); + }, + setVariableEditor(variableEditor) { + set({ variableEditor }); + }, + externalFragments: null!, + onTabChange: undefined, +})); + export const EditorContextProvider: FC = props => { const storage = useStorage(); const [headerEditor, setHeaderEditor] = useState( @@ -420,16 +455,6 @@ export const EditorContextProvider: FC = props => { }); }; - const updateActiveTabValues: EditorContextType['updateActiveTabValues'] = - partialTab => { - setTabState(current => { - const updated = setPropertiesInActiveTab(current, partialTab); - storeTabs(updated); - onTabChange?.(updated); - return updated; - }); - }; - const { onEditOperationName } = props; const setOperationName: EditorContextType['setOperationName'] = operationName => { diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index 079bcd5c272..5fe5a529307 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -164,7 +164,6 @@ export type UseCopyQueryArgs = { const _useCopyQuery = useCopyQuery; const _useMergeQuery = useMergeQuery; const _usePrettifyEditors = usePrettifyEditors; -const _useAutoCompleteLeafs = useAutoCompleteLeafs; export function useCopyQuery({ caller, onCopyQuery }: UseCopyQueryArgs = {}) { const { queryEditor } = useEditorContext({ @@ -284,73 +283,49 @@ export function usePrettifyEditors({ }; } -export type UseAutoCompleteLeafsArgs = { - /** - * A function to determine which field leafs are automatically added when - * trying to execute a query with missing selection sets. It will be called - * with the `GraphQLType` for which fields need to be added. - */ - getDefaultFieldNames?: GetDefaultFieldNamesFn; - /** - * This is only meant to be used internally in `@graphiql/react`. - */ - caller?: Function; -}; - -export function useAutoCompleteLeafs({ - getDefaultFieldNames, - caller, -}: UseAutoCompleteLeafsArgs = {}) { - const { schema } = useSchemaStore(); - const { queryEditor } = useEditorContext({ - nonNull: true, - caller: caller || _useAutoCompleteLeafs, - }); - return () => { - if (!queryEditor) { - return; - } - - const query = queryEditor.getValue(); - const { insertions, result } = fillLeafs( - schema, - query, - getDefaultFieldNames, - ); - if (insertions && insertions.length > 0) { - queryEditor.operation(() => { - const cursor = queryEditor.getCursor(); - const cursorIndex = queryEditor.indexFromPos(cursor); - queryEditor.setValue(result || ''); - let added = 0; - const markers = insertions.map(({ index, string }) => - queryEditor.markText( - queryEditor.posFromIndex(index + added), - queryEditor.posFromIndex(index + (added += string.length)), - { - className: 'auto-inserted-leaf', - clearOnEnter: true, - title: 'Automatically added leaf fields', - }, - ), - ); - setTimeout(() => { - for (const marker of markers) { - marker.clear(); - } - }, 7000); - let newCursorIndex = cursorIndex; - for (const { index, string } of insertions) { - if (index < cursorIndex) { - newCursorIndex += string.length; - } +export function getAutoCompleteLeafs() { + const { queryEditor } = editorStore.getState(); + if (!queryEditor) { + return; + } + const { schema } = schemaStore.getState(); + const query = queryEditor.getValue(); + const { getDefaultFieldNames } = executionStore.getState(); + const { insertions, result } = fillLeafs(schema, query, getDefaultFieldNames); + + if (insertions && insertions.length > 0) { + queryEditor.operation(() => { + const cursor = queryEditor.getCursor(); + const cursorIndex = queryEditor.indexFromPos(cursor); + queryEditor.setValue(result || ''); + let added = 0; + const markers = insertions.map(({ index, string }) => + queryEditor.markText( + queryEditor.posFromIndex(index + added), + queryEditor.posFromIndex(index + (added += string.length)), + { + className: 'auto-inserted-leaf', + clearOnEnter: true, + title: 'Automatically added leaf fields', + }, + ), + ); + setTimeout(() => { + for (const marker of markers) { + marker.clear(); } - queryEditor.setCursor(queryEditor.posFromIndex(newCursorIndex)); - }); - } + }, 7000); + let newCursorIndex = cursorIndex; + for (const { index, string } of insertions) { + if (index < cursorIndex) { + newCursorIndex += string.length; + } + } + queryEditor.setCursor(queryEditor.posFromIndex(newCursorIndex)); + }); + } - return result; - }; + return result; } // https://react.dev/learn/you-might-not-need-an-effect diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index bd17bb0cefa..1551379d9d3 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -5,10 +5,10 @@ export { ResponseEditor, VariableEditor, } from './components'; -export { EditorContextProvider, useEditorStore } from './context'; +export { EditorContext, EditorContextProvider, useEditorContext } from './context'; export { useHeaderEditor } from './header-editor'; export { - useAutoCompleteLeafs, + getAutoCompleteLeafs, useCopyQuery, useMergeQuery, usePrettifyEditors, diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index 315d7e21a42..c59e5fb4e8c 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -15,7 +15,7 @@ import { print, } from 'graphql'; import { getFragmentDependenciesForAST } from 'graphql-language-service'; -import { FC, ReactNode, useEffect } from 'react'; +import { FC, ReactElement, ReactNode, useEffect } from 'react'; import setValue from 'set-value'; import getValue from 'get-value'; @@ -265,7 +265,6 @@ export const executionStore = createStore< }, })); -// @ts-expect-error -- ignore `children` type warning export const ExecutionContextProvider: FC = ({ fetcher, getDefaultFieldNames, @@ -285,7 +284,7 @@ export const ExecutionContextProvider: FC = ({ }); }, [getDefaultFieldNames, operationName]); - return children; + return children as ReactElement; }; export function useExecutionStore() { diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 41a4a36919e..1826705c5c7 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -7,7 +7,7 @@ export { ImagePreview, QueryEditor, ResponseEditor, - useAutoCompleteLeafs, + getAutoCompleteLeafs, useCopyQuery, useEditorContext, useHeaderEditor, From e52977eba9f03abfb8344087da23e777b0eb3ce0 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:04:03 +0200 Subject: [PATCH 003/255] upd --- .../graphiql-react/src/editor/context.tsx | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 5b52b00a2a7..8fa874f5507 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -7,10 +7,10 @@ import { visit, } from 'graphql'; import { VariableToType } from 'graphql-language-service'; -import { FC, ReactNode, useEffect, useRef, useState } from 'react'; +import { FC, ReactNode, useContext, useEffect, useRef, useState } from 'react'; import { useStorage } from '../storage'; -import { createContextHook, createNullableContext } from '../utility/context'; +import { createNullableContext } from '../utility/context'; import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor'; import { useSynchronizeValue } from './hooks'; import { STORAGE_KEY_QUERY } from './query-editor'; @@ -31,7 +31,7 @@ import { import { CodeMirrorEditor } from './types'; import { STORAGE_KEY as STORAGE_KEY_VARIABLES } from './variable-editor'; import { DEFAULT_QUERY } from '../constants'; -import { createStore } from 'zustand'; +import { createStore, useStore } from 'zustand'; export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { documentAST: DocumentNode | null; @@ -166,8 +166,7 @@ export type EditorContextType = { setShouldPersistHeaders(persist: boolean): void; }; -export const EditorContext = - createNullableContext('EditorContext'); +const EditorContext = createNullableContext('EditorContext'); type EditorContextProviderProps = Pick & { children: ReactNode; @@ -290,19 +289,14 @@ export const editorStore = createStore((set, get) => ({ onTabChange: undefined, })); -export const EditorContextProvider: FC = props => { +export const EditorContextProvider: FC = ({ + externalFragments, + ...props +}) => { const storage = useStorage(); - const [headerEditor, setHeaderEditor] = useState( - null, - ); - const [queryEditor, setQueryEditor] = - useState(null); - const [responseEditor, setResponseEditor] = useState( - null, - ); - const [variableEditor, setVariableEditor] = useState( - null, - ); + const { headerEditor, queryEditor, responseEditor, variableEditor } = + // TODO: refactor to use useEditorStore + useStore(editorStore); const [shouldPersistHeaders, setShouldPersistHeadersInternal] = useState( () => { @@ -463,23 +457,23 @@ export const EditorContextProvider: FC = props => { } updateQueryEditor(queryEditor, operationName); - updateActiveTabValues({ operationName }); + editorStore.getState().updateActiveTabValues({ operationName }); onEditOperationName?.(operationName); }; - const externalFragments = (() => { + const $externalFragments = (() => { const map = new Map(); - if (Array.isArray(props.externalFragments)) { - for (const fragment of props.externalFragments) { + if (Array.isArray(externalFragments)) { + for (const fragment of externalFragments) { map.set(fragment.name.value, fragment); } - } else if (typeof props.externalFragments === 'string') { - visit(parse(props.externalFragments, {}), { + } else if (typeof externalFragments === 'string') { + visit(parse(externalFragments, {}), { FragmentDefinition(fragment) { map.set(fragment.name.value, fragment); }, }); - } else if (props.externalFragments) { + } else if (externalFragments) { throw new Error( 'The `externalFragments` prop must either be a string that contains the fragment definitions in SDL or a list of FragmentDefinitionNode objects.', ); @@ -487,25 +481,21 @@ export const EditorContextProvider: FC = props => { return map; })(); + useEffect(() => { + editorStore.setState({ + ...tabState, + externalFragments: $externalFragments, + onTabChange, + }); + }, [$externalFragments, onTabChange, tabState]); + const validationRules = props.validationRules || []; const value: EditorContextType = { - ...tabState, addTab, changeTab, moveTab, closeTab, - updateActiveTabValues, - - headerEditor, - queryEditor, - responseEditor, - variableEditor, - setHeaderEditor, - setQueryEditor, - setResponseEditor, - setVariableEditor, - setOperationName, initialQuery: initialState.query, @@ -513,7 +503,6 @@ export const EditorContextProvider: FC = props => { initialHeaders: initialState.headers, initialResponse: initialState.response, - externalFragments, validationRules, shouldPersistHeaders, @@ -533,6 +522,12 @@ function updateQueryEditor( queryEditor.operationName = operationName; } -export const useEditorContext = createContextHook(EditorContext); +export function useEditorStore() { + return { + ...useStore(editorStore), + // TODO: this is temporary and will be removed over to zustand only usage + ...useContext(EditorContext), + }; +} const PERSIST_HEADERS_STORAGE_KEY = 'shouldPersistHeaders'; From fcfd51940d323efb23affd0fb2246273ef2ca142 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:06:52 +0200 Subject: [PATCH 004/255] upd --- .../graphiql-react/src/editor/context.tsx | 2 +- packages/graphiql-react/src/editor/tabs.ts | 67 +++++++------------ 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 8fa874f5507..8c4890008cd 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -212,7 +212,7 @@ type EditorContextProviderProps = Pick & { /** * Invoked when the operation name changes. Possible triggers are: * - Editing the contents of the query editor - * - Selecting a operation for execution in a document that contains multiple + * - Selecting an operation for execution in a document that contains multiple * operation definitions * @param operationName The operation name after it has been changed. */ diff --git a/packages/graphiql-react/src/editor/tabs.ts b/packages/graphiql-react/src/editor/tabs.ts index 14caa4b9cd8..0f13aa7b4af 100644 --- a/packages/graphiql-react/src/editor/tabs.ts +++ b/packages/graphiql-react/src/editor/tabs.ts @@ -190,34 +190,22 @@ function hasStringOrNullKey(obj: Record, key: string) { return key in obj && (typeof obj[key] === 'string' || obj[key] === null); } -export function useSynchronizeActiveTabValues({ - queryEditor, - variableEditor, - headerEditor, - responseEditor, -}: { - queryEditor: CodeMirrorEditorWithOperationFacts | null; - variableEditor: CodeMirrorEditor | null; - headerEditor: CodeMirrorEditor | null; - responseEditor: CodeMirrorEditor | null; -}) { - return useCallback<(state: TabsState) => TabsState>( - state => { - const query = queryEditor?.getValue() ?? null; - const variables = variableEditor?.getValue() ?? null; - const headers = headerEditor?.getValue() ?? null; - const operationName = queryEditor?.operationName ?? null; - const response = responseEditor?.getValue() ?? null; - return setPropertiesInActiveTab(state, { - query, - variables, - headers, - response, - operationName, - }); - }, - [queryEditor, variableEditor, headerEditor, responseEditor], - ); +export function synchronizeActiveTabValues(state: TabsState): TabsState { + const { queryEditor, variableEditor, headerEditor, responseEditor } = + editorStore.getState(); + + const query = queryEditor?.getValue() ?? null; + const variables = variableEditor?.getValue() ?? null; + const headers = headerEditor?.getValue() ?? null; + const operationName = queryEditor?.operationName ?? null; + const response = responseEditor?.getValue() ?? null; + return setPropertiesInActiveTab(state, { + query, + variables, + headers, + response, + operationName, + }); } export function serializeTabState( @@ -233,21 +221,14 @@ export function serializeTabState( ); } -export function useStoreTabs({ - shouldPersistHeaders, -}: { - shouldPersistHeaders?: boolean; -}) { - return useCallback( - (currentState: TabsState) => { - const { storage } = storageStore.getState(); - const store = debounce(500, (value: string) => { - storage.set(STORAGE_KEY, value); - }); - store(serializeTabState(currentState, shouldPersistHeaders)); - }, - [shouldPersistHeaders], - ); +export function storeTabs(currentState: TabsState) { + const { storage } = storageStore.getState(); + const { shouldPersistHeaders } = editorStore.getState(); + const store = debounce(500, (value: string) => { + storage.set(STORAGE_KEY, value); + }); + + store(serializeTabState(currentState, shouldPersistHeaders)); } export function useSetEditorValues({ From 8db47ced3a984a6a97e00d494e41a2c913b8670d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:18:22 +0200 Subject: [PATCH 005/255] upd --- .../src/editor/components/header-editor.tsx | 9 +++----- packages/graphiql-react/src/editor/index.ts | 2 +- .../src/editor/response-editor.tsx | 23 ++++++------------- .../graphiql-react/src/toolbar/execute.tsx | 14 ++++------- 4 files changed, 15 insertions(+), 33 deletions(-) diff --git a/packages/graphiql-react/src/editor/components/header-editor.tsx b/packages/graphiql-react/src/editor/components/header-editor.tsx index 31c9779d748..80d6c00e5d7 100644 --- a/packages/graphiql-react/src/editor/components/header-editor.tsx +++ b/packages/graphiql-react/src/editor/components/header-editor.tsx @@ -1,6 +1,6 @@ import { FC, useEffect } from 'react'; import { clsx } from 'clsx'; -import { useEditorContext } from '../context'; +import { useEditorStore } from '../context'; import { useHeaderEditor, UseHeaderEditorArgs } from '../header-editor'; import '../style/codemirror.css'; import '../style/fold.css'; @@ -18,11 +18,8 @@ export const HeaderEditor: FC = ({ isHidden, ...hookArgs }) => { - const { headerEditor } = useEditorContext({ - nonNull: true, - caller: HeaderEditor, - }); - const ref = useHeaderEditor(hookArgs, HeaderEditor); + const { headerEditor } = useEditorStore(); + const ref = useHeaderEditor(hookArgs); useEffect(() => { if (!isHidden) { diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index 1551379d9d3..8243aa21f19 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -5,7 +5,7 @@ export { ResponseEditor, VariableEditor, } from './components'; -export { EditorContext, EditorContextProvider, useEditorContext } from './context'; +export { EditorContextProvider, useEditorStore } from './context'; export { useHeaderEditor } from './header-editor'; export { getAutoCompleteLeafs, diff --git a/packages/graphiql-react/src/editor/response-editor.tsx b/packages/graphiql-react/src/editor/response-editor.tsx index 5c5e8b25512..c21bf96c643 100644 --- a/packages/graphiql-react/src/editor/response-editor.tsx +++ b/packages/graphiql-react/src/editor/response-editor.tsx @@ -11,7 +11,7 @@ import { importCodeMirror, } from './common'; import { ImagePreview } from './components'; -import { useEditorContext } from './context'; +import { useEditorStore } from './context'; import { useSynchronizeOption } from './hooks'; import { CodeMirrorEditor, CommonEditorProps } from './types'; @@ -52,23 +52,14 @@ function importCodeMirrorImports() { ); } -// To make react-compiler happy, otherwise complains about - Hooks may not be referenced as normal values -const _useResponseEditor = useResponseEditor; - -export function useResponseEditor( - { - responseTooltip, - editorTheme = DEFAULT_EDITOR_THEME, - keyMap = DEFAULT_KEY_MAP, - }: UseResponseEditorArgs = {}, - caller?: Function, -) { +export function useResponseEditor({ + responseTooltip, + editorTheme = DEFAULT_EDITOR_THEME, + keyMap = DEFAULT_KEY_MAP, +}: UseResponseEditorArgs = {}) { const { fetchError, validationErrors } = useSchemaStore(); const { initialResponse, responseEditor, setResponseEditor } = - useEditorContext({ - nonNull: true, - caller: caller || _useResponseEditor, - }); + useEditorStore(); const ref = useRef(null); const responseTooltipRef = useRef( diff --git a/packages/graphiql-react/src/toolbar/execute.tsx b/packages/graphiql-react/src/toolbar/execute.tsx index 6d292062019..9ce96a9be96 100644 --- a/packages/graphiql-react/src/toolbar/execute.tsx +++ b/packages/graphiql-react/src/toolbar/execute.tsx @@ -1,20 +1,14 @@ import { FC } from 'react'; -import { useEditorContext } from '../editor'; -import { useExecutionContext } from '../execution'; +import { useEditorStore } from '../editor'; +import { useExecutionStore } from '../execution'; import { PlayIcon, StopIcon } from '../icons'; import { DropdownMenu, Tooltip } from '../ui'; import './execute.css'; export const ExecuteButton: FC = () => { - const { queryEditor, setOperationName } = useEditorContext({ - nonNull: true, - caller: ExecuteButton, - }); + const { queryEditor, setOperationName } = useEditorStore(); const { isFetching, isSubscribed, operationName, run, stop } = - useExecutionContext({ - nonNull: true, - caller: ExecuteButton, - }); + useExecutionStore(); const operations = queryEditor?.operations || []; const hasOptions = operations.length > 1 && typeof operationName !== 'string'; From 75892537c45d280118b5f2df86675f00e458a2e1 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:23:01 +0200 Subject: [PATCH 006/255] upd --- .../graphiql-react/src/editor/query-editor.ts | 31 +++++------------ packages/graphiql-react/src/editor/tabs.ts | 18 ++++------ .../src/editor/variable-editor.ts | 34 ++++++++----------- 3 files changed, 28 insertions(+), 55 deletions(-) diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.ts index e828842d84d..e591e387f70 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.ts @@ -12,7 +12,7 @@ import { OperationFacts, } from 'graphql-language-service'; import { RefObject, useEffect, useRef } from 'react'; -import { useExecutionContext } from '../execution'; +import { executionStore } from '../execution'; import { markdown } from '../markdown'; import { usePluginStore } from '../plugin'; import { useSchemaStore } from '../schema'; @@ -24,10 +24,7 @@ import { DEFAULT_KEY_MAP, importCodeMirror, } from './common'; -import { - CodeMirrorEditorWithOperationFacts, - useEditorContext, -} from './context'; +import { CodeMirrorEditorWithOperationFacts, useEditorStore } from './context'; import { useCompletion, useCopyQuery, @@ -139,19 +136,12 @@ export function useQueryEditor( validationRules, variableEditor, updateActiveTabValues, - } = useEditorContext({ - nonNull: true, - caller: caller || _useQueryEditor, - }); - const executionContext = useExecutionContext(); + } = useEditorStore(); const storage = useStorage(); const plugin = usePluginStore(); const copy = useCopyQuery({ caller: caller || _useQueryEditor, onCopyQuery }); - const merge = useMergeQuery({ caller: caller || _useQueryEditor }); - const prettify = usePrettifyEditors({ - caller: caller || _useQueryEditor, - onPrettifyQuery, - }); + const merge = useMergeQuery(); + const prettify = usePrettifyEditors({ onPrettifyQuery }); const ref = useRef(null); const codeMirrorRef = useRef(undefined); @@ -398,15 +388,10 @@ export function useQueryEditor( useCompletion(queryEditor, onClickReference); - const run = executionContext?.run; const runAtCursor = () => { - if ( - !run || - !queryEditor || - !queryEditor.operations || - !queryEditor.hasFocus() - ) { - run?.(); + const { run } = executionStore.getState(); + + if (!queryEditor || !queryEditor.operations || !queryEditor.hasFocus()) { return; } diff --git a/packages/graphiql-react/src/editor/tabs.ts b/packages/graphiql-react/src/editor/tabs.ts index 0f13aa7b4af..57fe4f8caad 100644 --- a/packages/graphiql-react/src/editor/tabs.ts +++ b/packages/graphiql-react/src/editor/tabs.ts @@ -5,7 +5,7 @@ import { storageStore } from '../storage'; import { useCallback } from 'react'; import { debounce } from '../utility/debounce'; -import { CodeMirrorEditorWithOperationFacts } from './context'; +import { CodeMirrorEditorWithOperationFacts, editorStore } from './context'; import { CodeMirrorEditor } from './types'; export type TabDefinition = { @@ -193,18 +193,12 @@ function hasStringOrNullKey(obj: Record, key: string) { export function synchronizeActiveTabValues(state: TabsState): TabsState { const { queryEditor, variableEditor, headerEditor, responseEditor } = editorStore.getState(); - - const query = queryEditor?.getValue() ?? null; - const variables = variableEditor?.getValue() ?? null; - const headers = headerEditor?.getValue() ?? null; - const operationName = queryEditor?.operationName ?? null; - const response = responseEditor?.getValue() ?? null; return setPropertiesInActiveTab(state, { - query, - variables, - headers, - response, - operationName, + query: queryEditor?.getValue() ?? null, + variables: variableEditor?.getValue() ?? null, + headers: headerEditor?.getValue() ?? null, + response: responseEditor?.getValue() ?? null, + operationName: queryEditor?.operationName ?? null, }); } diff --git a/packages/graphiql-react/src/editor/variable-editor.ts b/packages/graphiql-react/src/editor/variable-editor.ts index 1ecdf0bf7e2..af800b9d021 100644 --- a/packages/graphiql-react/src/editor/variable-editor.ts +++ b/packages/graphiql-react/src/editor/variable-editor.ts @@ -1,14 +1,14 @@ import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; import { useEffect, useRef } from 'react'; -import { useExecutionContext } from '../execution'; +import { useExecutionStore } from '../execution'; import { commonKeys, DEFAULT_EDITOR_THEME, DEFAULT_KEY_MAP, importCodeMirror, } from './common'; -import { useEditorContext } from './context'; +import { useEditorStore } from './context'; import { useChangeHandler, useCompletion, @@ -45,24 +45,18 @@ function importCodeMirrorImports() { // To make react-compiler happy, otherwise complains about - Hooks may not be referenced as normal values const _useVariableEditor = useVariableEditor; -export function useVariableEditor( - { - editorTheme = DEFAULT_EDITOR_THEME, - keyMap = DEFAULT_KEY_MAP, - onClickReference, - onEdit, - readOnly = false, - }: UseVariableEditorArgs = {}, - caller?: Function, -) { +export function useVariableEditor({ + editorTheme = DEFAULT_EDITOR_THEME, + keyMap = DEFAULT_KEY_MAP, + onClickReference, + onEdit, + readOnly = false, +}: UseVariableEditorArgs = {}) { const { initialVariables, variableEditor, setVariableEditor } = - useEditorContext({ - nonNull: true, - caller: caller || _useVariableEditor, - }); - const executionContext = useExecutionContext(); - const merge = useMergeQuery({ caller: caller || _useVariableEditor }); - const prettify = usePrettifyEditors({ caller: caller || _useVariableEditor }); + useEditorStore(); + const { run } = useExecutionStore(); + const merge = useMergeQuery(); + const prettify = usePrettifyEditors(); const ref = useRef(null); useEffect(() => { let isActive = true; @@ -147,7 +141,7 @@ export function useVariableEditor( useCompletion(variableEditor, onClickReference); - useKeyMap(variableEditor, ['Cmd-Enter', 'Ctrl-Enter'], executionContext?.run); + useKeyMap(variableEditor, ['Cmd-Enter', 'Ctrl-Enter'], run); useKeyMap(variableEditor, ['Shift-Ctrl-P'], prettify); useKeyMap(variableEditor, ['Shift-Ctrl-M'], merge); From 9425ff8a7b55a68d229bbe6049cd1df690b58d63 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:23:26 +0200 Subject: [PATCH 007/255] upd --- .../src/editor/header-editor.ts | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/graphiql-react/src/editor/header-editor.ts b/packages/graphiql-react/src/editor/header-editor.ts index e9e19a74bee..5a84eabe9ed 100644 --- a/packages/graphiql-react/src/editor/header-editor.ts +++ b/packages/graphiql-react/src/editor/header-editor.ts @@ -1,13 +1,12 @@ 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 { useEditorStore } from './context'; import { useChangeHandler, useKeyMap, @@ -16,6 +15,7 @@ import { useSynchronizeOption, } from './hooks'; import { WriteableEditorProps } from './types'; +import { useExecutionStore } from '../execution'; export type UseHeaderEditorArgs = WriteableEditorProps & { /** @@ -32,29 +32,22 @@ function importCodeMirrorImports() { 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, -) { +export function useHeaderEditor({ + editorTheme = DEFAULT_EDITOR_THEME, + keyMap = DEFAULT_KEY_MAP, + onEdit, + readOnly = false, +}: UseHeaderEditorArgs = {}) { 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 }); + } = useEditorStore(); + const { run } = useExecutionStore(); + const merge = useMergeQuery(); + const prettify = usePrettifyEditors(); const ref = useRef(null); useEffect(() => { @@ -125,10 +118,9 @@ export function useHeaderEditor( onEdit, shouldPersistHeaders ? STORAGE_KEY : null, 'headers', - _useHeaderEditor, ); - useKeyMap(headerEditor, ['Cmd-Enter', 'Ctrl-Enter'], executionContext?.run); + useKeyMap(headerEditor, ['Cmd-Enter', 'Ctrl-Enter'], run); useKeyMap(headerEditor, ['Shift-Ctrl-P'], prettify); useKeyMap(headerEditor, ['Shift-Ctrl-M'], merge); From f0d728d6e65a949ba3862fa93fd0e0327f69f561 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:23:48 +0200 Subject: [PATCH 008/255] upd --- packages/graphiql-react/src/editor/context.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 8c4890008cd..805dc301363 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -22,8 +22,8 @@ import { TabsState, TabState, useSetEditorValues, - useStoreTabs, - useSynchronizeActiveTabValues, + storeTabs, + synchronizeActiveTabValues, clearHeadersFromTabs, serializeTabState, STORAGE_KEY as STORAGE_KEY_TABS, @@ -373,12 +373,6 @@ export const EditorContextProvider: FC = ({ } }, [props.shouldPersistHeaders, setShouldPersistHeaders]); - const synchronizeActiveTabValues = useSynchronizeActiveTabValues({ - queryEditor, - variableEditor, - headerEditor, - responseEditor, - }); const { onTabChange, defaultHeaders, defaultQuery, children } = props; const setEditorValues = useSetEditorValues({ queryEditor, From 73355225fa13e4f6ef5a359ba84189e9afedd376 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:26:22 +0200 Subject: [PATCH 009/255] upd --- packages/graphiql-react/src/editor/tabs.ts | 53 +++++++++------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/packages/graphiql-react/src/editor/tabs.ts b/packages/graphiql-react/src/editor/tabs.ts index 57fe4f8caad..e7100e729ef 100644 --- a/packages/graphiql-react/src/editor/tabs.ts +++ b/packages/graphiql-react/src/editor/tabs.ts @@ -198,7 +198,7 @@ export function synchronizeActiveTabValues(state: TabsState): TabsState { variables: variableEditor?.getValue() ?? null, headers: headerEditor?.getValue() ?? null, response: responseEditor?.getValue() ?? null, - operationName: queryEditor?.operationName ?? null, + operationName: queryEditor?.operationName ?? null, }); } @@ -225,38 +225,29 @@ export function storeTabs(currentState: TabsState) { store(serializeTabState(currentState, shouldPersistHeaders)); } -export function useSetEditorValues({ - queryEditor, - variableEditor, - headerEditor, - responseEditor, - defaultHeaders, +export function setEditorValues({ + query, + variables, + headers, + response, }: { - queryEditor: CodeMirrorEditorWithOperationFacts | null; - variableEditor: CodeMirrorEditor | null; - headerEditor: CodeMirrorEditor | null; - responseEditor: CodeMirrorEditor | null; - defaultHeaders?: string; + query: string | null; + variables?: string | null; + headers?: string | null; + response: string | null; }) { - return useCallback( - ({ - query, - variables, - headers, - response, - }: { - query: string | null; - variables?: string | null; - headers?: string | null; - response: string | null; - }) => { - queryEditor?.setValue(query ?? ''); - variableEditor?.setValue(variables ?? ''); - headerEditor?.setValue(headers ?? defaultHeaders ?? ''); - responseEditor?.setValue(response ?? ''); - }, - [headerEditor, queryEditor, responseEditor, variableEditor, defaultHeaders], - ); + const { + queryEditor, + variableEditor, + headerEditor, + responseEditor, + defaultHeaders, + } = editorStore.getState(); + + queryEditor?.setValue(query ?? ''); + variableEditor?.setValue(variables ?? ''); + headerEditor?.setValue(headers ?? defaultHeaders ?? ''); + responseEditor?.setValue(response ?? ''); } export function createTab({ From 6ac226e68103abb1adc022e5c35f1bced33a0deb Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:31:55 +0200 Subject: [PATCH 010/255] upd --- .../src/editor/components/response-editor.tsx | 2 +- .../src/editor/components/variable-editor.tsx | 9 +++------ packages/graphiql-react/src/editor/context.tsx | 13 +------------ .../graphiql-react/src/editor/variable-editor.ts | 11 +---------- packages/graphiql-react/src/index.ts | 3 +-- packages/graphiql-react/src/schema.ts | 16 +++++----------- 6 files changed, 12 insertions(+), 42 deletions(-) diff --git a/packages/graphiql-react/src/editor/components/response-editor.tsx b/packages/graphiql-react/src/editor/components/response-editor.tsx index 894dcc6f70a..fb7c98ecc53 100644 --- a/packages/graphiql-react/src/editor/components/response-editor.tsx +++ b/packages/graphiql-react/src/editor/components/response-editor.tsx @@ -6,7 +6,7 @@ import '../style/info.css'; import '../style/editor.css'; export const ResponseEditor: FC = props => { - const ref = useResponseEditor(props, ResponseEditor); + const ref = useResponseEditor(props); return (
= ({ isHidden, ...hookArgs }) => { - const { variableEditor } = useEditorContext({ - nonNull: true, - caller: VariableEditor, - }); - const ref = useVariableEditor(hookArgs, VariableEditor); + const { variableEditor } = useEditorStore(); + const ref = useVariableEditor(hookArgs); useEffect(() => { if (!isHidden) { diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 805dc301363..8d6b67bd366 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -21,7 +21,7 @@ import { TabDefinition, TabsState, TabState, - useSetEditorValues, + setEditorValues, storeTabs, synchronizeActiveTabValues, clearHeadersFromTabs, @@ -312,10 +312,6 @@ export const EditorContextProvider: FC = ({ useSynchronizeValue(responseEditor, props.response); useSynchronizeValue(variableEditor, props.variables); - const storeTabs = useStoreTabs({ - shouldPersistHeaders, - }); - // We store this in state but never update it. By passing a function we only // need to compute it lazily during the initial render. const [initialState] = useState(() => { @@ -374,13 +370,6 @@ export const EditorContextProvider: FC = ({ }, [props.shouldPersistHeaders, setShouldPersistHeaders]); const { onTabChange, defaultHeaders, defaultQuery, children } = props; - const setEditorValues = useSetEditorValues({ - queryEditor, - variableEditor, - headerEditor, - responseEditor, - defaultHeaders, - }); const addTab: EditorContextType['addTab'] = () => { setTabState(current => { diff --git a/packages/graphiql-react/src/editor/variable-editor.ts b/packages/graphiql-react/src/editor/variable-editor.ts index af800b9d021..b78f6b6ac89 100644 --- a/packages/graphiql-react/src/editor/variable-editor.ts +++ b/packages/graphiql-react/src/editor/variable-editor.ts @@ -42,9 +42,6 @@ function importCodeMirrorImports() { ]); } -// To make react-compiler happy, otherwise complains about - Hooks may not be referenced as normal values -const _useVariableEditor = useVariableEditor; - export function useVariableEditor({ editorTheme = DEFAULT_EDITOR_THEME, keyMap = DEFAULT_KEY_MAP, @@ -131,13 +128,7 @@ export function useVariableEditor({ useSynchronizeOption(variableEditor, 'keyMap', keyMap); - useChangeHandler( - variableEditor, - onEdit, - STORAGE_KEY, - 'variables', - _useVariableEditor, - ); + useChangeHandler(variableEditor, onEdit, STORAGE_KEY, 'variables'); useCompletion(variableEditor, onClickReference); diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 1826705c5c7..977958f3e1c 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -1,7 +1,6 @@ import './style/root.css'; export { - EditorContext, EditorContextProvider, HeaderEditor, ImagePreview, @@ -9,7 +8,7 @@ export { ResponseEditor, getAutoCompleteLeafs, useCopyQuery, - useEditorContext, + useEditorStore, useHeaderEditor, useMergeQuery, usePrettifyEditors, diff --git a/packages/graphiql-react/src/schema.ts b/packages/graphiql-react/src/schema.ts index 00890b82bfa..0e2c120c5ed 100644 --- a/packages/graphiql-react/src/schema.ts +++ b/packages/graphiql-react/src/schema.ts @@ -17,7 +17,7 @@ import { } from 'graphql'; import { Dispatch, FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore, useStore } from 'zustand'; -import { useEditorContext } from './editor'; +import { useEditorStore } from './editor'; import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; type MaybeGraphQLSchema = GraphQLSchema | null | undefined; @@ -306,12 +306,7 @@ export const SchemaContextProvider: FC = ({ 'The `SchemaContextProvider` component requires a `fetcher` function to be passed as prop.', ); } - const { headerEditor } = useEditorContext({ - nonNull: true, - caller: SchemaContextProvider, - }); - - const { introspect } = useSchemaStore(); + const { headerEditor } = useEditorStore(); /** * Synchronize prop changes with state @@ -341,9 +336,8 @@ export const SchemaContextProvider: FC = ({ })); // Trigger introspection - introspect(); + schemaStore.getState().introspect(); }, [ - introspect, schema, dangerouslyAssumeSchemaIsValid, onSchemaChange, @@ -360,7 +354,7 @@ export const SchemaContextProvider: FC = ({ useEffect(() => { function triggerIntrospection(event: KeyboardEvent) { if (event.ctrlKey && event.key === 'R') { - introspect(); + schemaStore.getState().introspect(); } } @@ -368,7 +362,7 @@ export const SchemaContextProvider: FC = ({ return () => { window.removeEventListener('keydown', triggerIntrospection); }; - }, [introspect]); + }, []); return children as ReactElement; }; From 7bf7e80be6fa0ac8d7550451c6f3bae0b0bb422c Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:32:56 +0200 Subject: [PATCH 011/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 51 ++++++--------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index 5fe5a529307..b3d46686336 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -11,12 +11,13 @@ import { parse, print } from 'graphql'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports -- TODO: check why query builder update only 1st field https://github.com/graphql/graphiql/issues/3836 import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { usePluginStore } from '../plugin'; -import { useSchemaStore } from '../schema'; +import { schemaStore, useSchemaStore } from '../schema'; import { storageStore } from '../storage'; import { debounce } from '../utility'; import { onHasCompletion } from './completion'; -import { useEditorContext } from './context'; +import { editorStore, useEditorStore } from './context'; import { CodeMirrorEditor } from './types'; +import { executionStore } from '../execution'; export function useSynchronizeValue( editor: CodeMirrorEditor | null, @@ -44,9 +45,7 @@ export function useChangeHandler( callback: ((value: string) => void) | undefined, storageKey: string | null, tabProperty: 'variables' | 'headers', - caller: Function, ) { - const { updateActiveTabValues } = useEditorContext({ nonNull: true, caller }); useEffect(() => { if (!editor) { return; @@ -60,6 +59,7 @@ export function useChangeHandler( storage.set(storageKey, value); }); + const { updateActiveTabValues } = editorStore.getState(); const updateTab = debounce(100, (value: string) => { updateActiveTabValues({ [tabProperty]: value }); }); @@ -81,7 +81,7 @@ export function useChangeHandler( }; editor.on('change', handleChange); return () => editor.off('change', handleChange); - }, [callback, editor, storageKey, tabProperty, updateActiveTabValues]); + }, [callback, editor, storageKey, tabProperty]); } export function useCompletion( @@ -160,17 +160,9 @@ export type UseCopyQueryArgs = { onCopyQuery?: (query: string) => void; }; -// To make react-compiler happy, otherwise complains about - Hooks may not be referenced as normal values -const _useCopyQuery = useCopyQuery; -const _useMergeQuery = useMergeQuery; -const _usePrettifyEditors = usePrettifyEditors; - -export function useCopyQuery({ caller, onCopyQuery }: UseCopyQueryArgs = {}) { - const { queryEditor } = useEditorContext({ - nonNull: true, - caller: caller || _useCopyQuery, - }); +export function useCopyQuery({ onCopyQuery }: UseCopyQueryArgs = {}) { return () => { + const { queryEditor } = editorStore.getState(); if (!queryEditor) { return; } @@ -182,35 +174,21 @@ export function useCopyQuery({ caller, onCopyQuery }: UseCopyQueryArgs = {}) { }; } -type UseMergeQueryArgs = { - /** - * This is only meant to be used internally in `@graphiql/react`. - */ - caller?: Function; -}; - -export function useMergeQuery({ caller }: UseMergeQueryArgs = {}) { - const { queryEditor } = useEditorContext({ - nonNull: true, - caller: caller || _useMergeQuery, - }); - const { schema } = useSchemaStore(); +export function useMergeQuery() { return () => { + const { queryEditor } = editorStore.getState(); const documentAST = queryEditor?.documentAST; const query = queryEditor?.getValue(); if (!documentAST || !query) { return; } + const { schema } = schemaStore.getState(); queryEditor.setValue(print(mergeAst(documentAST, schema))); }; } export type UsePrettifyEditorsArgs = { - /** - * This is only meant to be used internally in `@graphiql/react`. - */ - caller?: Function; /** * Invoked when the prettify callback is invoked. * @param query The current value of the query editor. @@ -228,14 +206,11 @@ function DEFAULT_PRETTIFY_QUERY(query: string): string { } export function usePrettifyEditors({ - caller, onPrettifyQuery = DEFAULT_PRETTIFY_QUERY, }: UsePrettifyEditorsArgs = {}) { - const { queryEditor, headerEditor, variableEditor } = useEditorContext({ - nonNull: true, - caller: caller || _usePrettifyEditors, - }); return async () => { + const { queryEditor, headerEditor, variableEditor } = + editorStore.getState(); if (variableEditor) { const variableEditorContent = variableEditor.getValue(); try { @@ -332,7 +307,7 @@ export function getAutoCompleteLeafs() { export const useEditorState = (editor: 'query' | 'variable' | 'header') => { // eslint-disable-next-line react-hooks/react-compiler -- TODO: check why query builder update only 1st field https://github.com/graphql/graphiql/issues/3836 'use no memo'; - const context = useEditorContext({ nonNull: true }); + const context = useEditorStore(); const editorInstance = context[`${editor}Editor` as const]; let valueString = ''; From b28cc14024b75a97d3ed4b654cc6af6f0b24c4f6 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:41:00 +0200 Subject: [PATCH 012/255] upd --- packages/graphiql-plugin-explorer/src/index.tsx | 8 ++++---- .../src/__tests__/components.spec.tsx | 14 ++++++-------- .../graphiql-plugin-history/src/components.tsx | 7 ++----- packages/graphiql-plugin-history/src/context.tsx | 10 +++------- packages/graphiql/src/GraphiQL.tsx | 8 ++++---- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/graphiql-plugin-explorer/src/index.tsx b/packages/graphiql-plugin-explorer/src/index.tsx index 2a1704be668..8e35d6cd870 100644 --- a/packages/graphiql-plugin-explorer/src/index.tsx +++ b/packages/graphiql-plugin-explorer/src/index.tsx @@ -1,8 +1,8 @@ import React, { CSSProperties, FC, useCallback } from 'react'; import { GraphiQLPlugin, - useEditorContext, - useExecutionContext, + useEditorStore, + useExecutionStore, useSchemaStore, useOperationsEditorState, useOptimisticState, @@ -62,9 +62,9 @@ export type GraphiQLExplorerPluginProps = Omit< >; const ExplorerPlugin: FC = props => { - const { setOperationName } = useEditorContext({ nonNull: true }); + const { setOperationName } = useEditorStore(); const { schema } = useSchemaStore(); - const { run } = useExecutionContext({ nonNull: true }); + const { run } = useExecutionStore(); // handle running the current operation from the plugin const handleRunOperation = useCallback( diff --git a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx index 22c9be5e6c8..d0d913bb2ed 100644 --- a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx +++ b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx @@ -4,7 +4,7 @@ import type { ComponentProps } from 'react'; import { formatQuery, HistoryItem } from '../components'; import { HistoryContextProvider } from '../context'; import { - useEditorContext, + useEditorStore, Tooltip, StorageContextProvider, } from '@graphiql/react'; @@ -16,7 +16,7 @@ vi.mock('@graphiql/react', async () => { const mockedSetHeaderEditor = vi.fn(); return { ...originalModule, - useEditorContext() { + useEditorStore() { return { queryEditor: { setValue: mockedSetQueryEditor }, variableEditor: { setValue: mockedSetVariableEditor }, @@ -78,12 +78,10 @@ function getMockProps( } describe('QueryHistoryItem', () => { - const mockedSetQueryEditor = useEditorContext()!.queryEditor! - .setValue as Mock; - const mockedSetVariableEditor = useEditorContext()!.variableEditor! - .setValue as Mock; - const mockedSetHeaderEditor = useEditorContext()!.headerEditor! - .setValue as Mock; + const store = useEditorStore(); + const mockedSetQueryEditor = store.queryEditor!.setValue as Mock; + const mockedSetVariableEditor = store.variableEditor!.setValue as Mock; + const mockedSetHeaderEditor = store.headerEditor!.setValue as Mock; beforeEach(() => { mockedSetQueryEditor.mockClear(); mockedSetVariableEditor.mockClear(); diff --git a/packages/graphiql-plugin-history/src/components.tsx b/packages/graphiql-plugin-history/src/components.tsx index e2e54d86e9a..bd379ca62ee 100644 --- a/packages/graphiql-plugin-history/src/components.tsx +++ b/packages/graphiql-plugin-history/src/components.tsx @@ -7,7 +7,7 @@ import { StarFilledIcon, StarIcon, TrashIcon, - useEditorContext, + useEditorStore, Button, Tooltip, UnStyledButton, @@ -112,10 +112,7 @@ type QueryHistoryItemProps = { export const HistoryItem: FC = props => { const { editLabel, toggleFavorite, deleteFromHistory, setActive } = useHistoryActions(); - const { headerEditor, queryEditor, variableEditor } = useEditorContext({ - nonNull: true, - caller: HistoryItem, - }); + const { headerEditor, queryEditor, variableEditor } = useEditorStore(); const inputRef = useRef(null); const buttonRef = useRef(null); const [isEditable, setIsEditable] = useState(false); diff --git a/packages/graphiql-plugin-history/src/context.tsx b/packages/graphiql-plugin-history/src/context.tsx index caefb1c8a92..00dee498f99 100644 --- a/packages/graphiql-plugin-history/src/context.tsx +++ b/packages/graphiql-plugin-history/src/context.tsx @@ -2,11 +2,7 @@ import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore, useStore } from 'zustand'; import { HistoryStore, QueryStoreItem } from '@graphiql/toolkit'; -import { - useExecutionContext, - useEditorContext, - useStorage, -} from '@graphiql/react'; +import { useExecutionStore, useEditorStore, useStorage } from '@graphiql/react'; const historyStore = createStore((set, get) => ({ historyStorage: null!, @@ -118,8 +114,8 @@ export const HistoryContextProvider: FC = ({ maxHistoryLength = 20, children, }) => { - const { isFetching } = useExecutionContext({ nonNull: true }); - const { tabs, activeTabIndex } = useEditorContext({ nonNull: true }); + const { isFetching } = useExecutionStore(); + const { tabs, activeTabIndex } = useEditorStore(); const activeTab = tabs[activeTabIndex]; const storage = useStorage(); diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 1bd1d32b3ad..b705ac33138 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -38,8 +38,8 @@ import { UnStyledButton, useCopyQuery, useDragResize, - useEditorContext, - useExecutionContext, + useEditorStore, + useExecutionStore, UseHeaderEditorArgs, useMergeQuery, usePluginStore, @@ -238,8 +238,8 @@ export const GraphiQLInterface: FC = props => { shouldPersistHeaders, tabs, activeTabIndex, - } = useEditorContext({ nonNull: true }); - const executionContext = useExecutionContext({ nonNull: true }); + } = useEditorStore(); + const executionContext = useExecutionStore(); const { isFetching: isSchemaFetching, introspect } = useSchemaStore(); const storageContext = useStorage(); const { visiblePlugin, setVisiblePlugin, plugins } = usePluginStore(); From 7a090537f7d6255deb80d9b5866928a9737c9377 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:50:03 +0200 Subject: [PATCH 013/255] upd --- .../graphiql-react/src/editor/context.tsx | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 8d6b67bd366..a66cc311ceb 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -51,6 +51,7 @@ interface EditorStore extends TabsState { updateActiveTabValues( partialTab: Partial>, ): void; + /** * The CodeMirror editor instance for the headers editor. */ @@ -69,27 +70,47 @@ interface EditorStore extends TabsState { * The CodeMirror editor instance for the variables editor. */ variableEditor: CodeMirrorEditor | null; + /** * Set the CodeMirror editor instance for the headers editor. */ setHeaderEditor(newEditor: CodeMirrorEditor): void; + /** * Set the CodeMirror editor instance for the query editor. */ setQueryEditor(newEditor: CodeMirrorEditorWithOperationFacts): void; + /** * Set the CodeMirror editor instance for the response editor. */ setResponseEditor(newEditor: CodeMirrorEditor): void; + /** * Set the CodeMirror editor instance for the variables editor. */ setVariableEditor(newEditor: CodeMirrorEditor): void; + + /** + * Changes the operation name and invokes the `onEditOperationName` callback. + */ + setOperationName(operationName: string): void; + + /** + * Invoked when the operation name changes. Possible triggers are: + * - Editing the contents of the query editor + * - Selecting an operation for execution in a document that contains multiple + * operation definitions + * @param operationName The operation name after it has been changed. + */ + onEditOperationName?(operationName: string): void; + /** * A map of fragment definitions using the fragment name as key which are * made available to include in the query. */ externalFragments: Map; + /** * Invoked when the state of the tabs changes. Possible triggers are: * - Updating any editor contents inside the currently active tab @@ -124,11 +145,6 @@ export type EditorContextType = { */ closeTab(index: number): void; - /** - * Changes the operation name and invokes the `onEditOperationName` callback. - */ - setOperationName(operationName: string): void; - /** * The contents of the headers editor when initially rendering the provider * component. @@ -168,7 +184,10 @@ export type EditorContextType = { const EditorContext = createNullableContext('EditorContext'); -type EditorContextProviderProps = Pick & { +type EditorContextProviderProps = Pick< + EditorStore, + 'onTabChange' | 'onEditOperationName' +> & { children: ReactNode; /** * The initial contents of the query editor when loading GraphiQL and there @@ -209,14 +228,6 @@ type EditorContextProviderProps = Pick & { *``` */ defaultTabs?: TabDefinition[]; - /** - * Invoked when the operation name changes. Possible triggers are: - * - Editing the contents of the query editor - * - Selecting an operation for execution in a document that contains multiple - * operation definitions - * @param operationName The operation name after it has been changed. - */ - onEditOperationName?(operationName: string): void; /** * This prop can be used to set the contents of the query editor. Every time * this prop changes, the contents of the query editor are replaced. Note @@ -258,8 +269,8 @@ type EditorContextProviderProps = Pick & { }; export const editorStore = createStore((set, get) => ({ - tabs: [], - activeTabIndex: 0, + tabs: null!, + activeTabIndex: null!, updateActiveTabValues(partialTab) { set(current => { const { onTabChange } = get(); @@ -285,12 +296,23 @@ export const editorStore = createStore((set, get) => ({ setVariableEditor(variableEditor) { set({ variableEditor }); }, + setOperationName(operationName) { + const { queryEditor, onEditOperationName, updateActiveTabValues } = get(); + if (!queryEditor) { + return; + } + + updateQueryEditor(queryEditor, operationName); + updateActiveTabValues({ operationName }); + onEditOperationName?.(operationName); + }, externalFragments: null!, onTabChange: undefined, })); export const EditorContextProvider: FC = ({ externalFragments, + onEditOperationName, ...props }) => { const storage = useStorage(); @@ -432,18 +454,6 @@ export const EditorContextProvider: FC = ({ }); }; - const { onEditOperationName } = props; - const setOperationName: EditorContextType['setOperationName'] = - operationName => { - if (!queryEditor) { - return; - } - - updateQueryEditor(queryEditor, operationName); - editorStore.getState().updateActiveTabValues({ operationName }); - onEditOperationName?.(operationName); - }; - const $externalFragments = (() => { const map = new Map(); if (Array.isArray(externalFragments)) { @@ -469,8 +479,9 @@ export const EditorContextProvider: FC = ({ ...tabState, externalFragments: $externalFragments, onTabChange, + onEditOperationName, }); - }, [$externalFragments, onTabChange, tabState]); + }, [$externalFragments, onTabChange, tabState, onEditOperationName]); const validationRules = props.validationRules || []; @@ -479,7 +490,6 @@ export const EditorContextProvider: FC = ({ changeTab, moveTab, closeTab, - setOperationName, initialQuery: initialState.query, initialVariables: initialState.variables, From dba736ee2c3c20eaf39981641c7a0cd91859c286 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 21:57:06 +0200 Subject: [PATCH 014/255] upd --- .../graphiql-react/src/editor/context.tsx | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index a66cc311ceb..47ed7ff4f8c 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -120,6 +120,11 @@ interface EditorStore extends TabsState { * @param tabState The tab state after it has been updated. */ onTabChange?(tabState: TabsState): void; + + /** + * Headers to be set when opening a new tab + */ + defaultHeaders?: string; } export type EditorContextType = { @@ -186,7 +191,7 @@ const EditorContext = createNullableContext('EditorContext'); type EditorContextProviderProps = Pick< EditorStore, - 'onTabChange' | 'onEditOperationName' + 'onTabChange' | 'onEditOperationName' | 'defaultHeaders' > & { children: ReactNode; /** @@ -261,11 +266,6 @@ type EditorContextProviderProps = Pick< * typing in the editor. */ variables?: string; - - /** - * Headers to be set when opening a new tab - */ - defaultHeaders?: string; }; export const editorStore = createStore((set, get) => ({ @@ -301,18 +301,20 @@ export const editorStore = createStore((set, get) => ({ if (!queryEditor) { return; } - - updateQueryEditor(queryEditor, operationName); + queryEditor.operationName = operationName; updateActiveTabValues({ operationName }); onEditOperationName?.(operationName); }, + onEditOperationName: undefined, externalFragments: null!, onTabChange: undefined, + defaultHeaders: undefined, })); export const EditorContextProvider: FC = ({ externalFragments, onEditOperationName, + defaultHeaders, ...props }) => { const storage = useStorage(); @@ -349,7 +351,7 @@ export const EditorContextProvider: FC = ({ headers, defaultTabs: props.defaultTabs, defaultQuery: props.defaultQuery || DEFAULT_QUERY, - defaultHeaders: props.defaultHeaders, + defaultHeaders, shouldPersistHeaders, }); storeTabs(tabState); @@ -360,7 +362,7 @@ export const EditorContextProvider: FC = ({ (tabState.activeTabIndex === 0 ? tabState.tabs[0].query : null) ?? '', variables: variables ?? '', - headers: headers ?? props.defaultHeaders ?? '', + headers: headers ?? defaultHeaders ?? '', response, tabState, }; @@ -391,7 +393,7 @@ export const EditorContextProvider: FC = ({ } }, [props.shouldPersistHeaders, setShouldPersistHeaders]); - const { onTabChange, defaultHeaders, defaultQuery, children } = props; + const { onTabChange, defaultQuery, children } = props; const addTab: EditorContextType['addTab'] = () => { setTabState(current => { @@ -480,8 +482,15 @@ export const EditorContextProvider: FC = ({ externalFragments: $externalFragments, onTabChange, onEditOperationName, + defaultHeaders, }); - }, [$externalFragments, onTabChange, tabState, onEditOperationName]); + }, [ + $externalFragments, + onTabChange, + tabState, + onEditOperationName, + defaultHeaders, + ]); const validationRules = props.validationRules || []; @@ -507,14 +516,6 @@ export const EditorContextProvider: FC = ({ ); }; -// To make react-compiler happy, otherwise it fails due to mutating props -function updateQueryEditor( - queryEditor: CodeMirrorEditorWithOperationFacts, - operationName: string, -) { - queryEditor.operationName = operationName; -} - export function useEditorStore() { return { ...useStore(editorStore), From d16a30c856b5cacbfec3a251f8a408591fcbcef5 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 22:01:35 +0200 Subject: [PATCH 015/255] upd --- .../graphiql-react/src/editor/context.tsx | 171 +++++++++--------- 1 file changed, 83 insertions(+), 88 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 47ed7ff4f8c..9d7807a6852 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -41,6 +41,27 @@ export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { }; interface EditorStore extends TabsState { + /** + * Add a new tab. + */ + addTab(): void; + /** + * Switch to a different tab. + * @param index The index of the tab that should be switched to. + */ + changeTab(index: number): void; + /** + * Move a tab to a new spot. + * @param newOrder The new order for the tabs. + */ + moveTab(newOrder: TabState[]): void; + /** + * Close a tab. If the currently active tab is closed, the tab before it will + * become active. If there is no tab before the closed one, the tab after it + * will become active. + * @param index The index of the tab that should be closed. + */ + closeTab(index: number): void; /** * Update the state for the tab that is currently active. This will be * reflected in the `tabs` object and the state will be persisted in storage @@ -128,28 +149,6 @@ interface EditorStore extends TabsState { } export type EditorContextType = { - /** - * Add a new tab. - */ - addTab(): void; - /** - * Switch to a different tab. - * @param index The index of the tab that should be switched to. - */ - changeTab(index: number): void; - /** - * Move a tab to a new spot. - * @param newOrder The new order for the tabs. - */ - moveTab(newOrder: TabState[]): void; - /** - * Close a tab. If the currently active tab is closed, the tab before it will - * become active. If there is no tab before the closed one, the tab after it - * will become active. - * @param index The index of the tab that should be closed. - */ - closeTab(index: number): void; - /** * The contents of the headers editor when initially rendering the provider * component. @@ -271,6 +270,68 @@ type EditorContextProviderProps = Pick< export const editorStore = createStore((set, get) => ({ tabs: null!, activeTabIndex: null!, + addTab() { + set(current => { + const { defaultQuery, defaultHeaders, onTabChange } = get(); + + // Make sure the current tab stores the latest values + const updatedValues = synchronizeActiveTabValues(current); + const updated = { + tabs: [ + ...updatedValues.tabs, + createTab({ + headers: defaultHeaders, + query: defaultQuery, + }), + ], + activeTabIndex: updatedValues.tabs.length, + }; + storeTabs(updated); + setEditorValues(updated.tabs[updated.activeTabIndex]); + onTabChange?.(updated); + return updated; + }); + }, + changeTab(index) { + set(current => { + const { onTabChange } = get(); + const updated = { + ...current, + activeTabIndex: index, + }; + storeTabs(updated); + setEditorValues(updated.tabs[updated.activeTabIndex]); + onTabChange?.(updated); + return updated; + }); + }, + moveTab(newOrder) { + set(current => { + const { onTabChange } = get(); + const activeTab = current.tabs[current.activeTabIndex]; + const updated = { + tabs: newOrder, + activeTabIndex: newOrder.indexOf(activeTab), + }; + storeTabs(updated); + setEditorValues(updated.tabs[updated.activeTabIndex]); + onTabChange?.(updated); + return updated; + }); + }, + closeTab(index) { + set(current => { + const { onTabChange } = get(); + const updated = { + tabs: current.tabs.filter((_tab, i) => index !== i), + activeTabIndex: Math.max(current.activeTabIndex - 1, 0), + }; + storeTabs(updated); + setEditorValues(updated.tabs[updated.activeTabIndex]); + onTabChange?.(updated); + return updated; + }); + }, updateActiveTabValues(partialTab) { set(current => { const { onTabChange } = get(); @@ -395,67 +456,6 @@ export const EditorContextProvider: FC = ({ const { onTabChange, defaultQuery, children } = props; - const addTab: EditorContextType['addTab'] = () => { - setTabState(current => { - // Make sure the current tab stores the latest values - const updatedValues = synchronizeActiveTabValues(current); - const updated = { - tabs: [ - ...updatedValues.tabs, - createTab({ - headers: defaultHeaders, - query: defaultQuery ?? DEFAULT_QUERY, - }), - ], - activeTabIndex: updatedValues.tabs.length, - }; - storeTabs(updated); - setEditorValues(updated.tabs[updated.activeTabIndex]); - onTabChange?.(updated); - return updated; - }); - }; - - const changeTab: EditorContextType['changeTab'] = index => { - setTabState(current => { - const updated = { - ...current, - activeTabIndex: index, - }; - storeTabs(updated); - setEditorValues(updated.tabs[updated.activeTabIndex]); - onTabChange?.(updated); - return updated; - }); - }; - - const moveTab: EditorContextType['moveTab'] = newOrder => { - setTabState(current => { - const activeTab = current.tabs[current.activeTabIndex]; - const updated = { - tabs: newOrder, - activeTabIndex: newOrder.indexOf(activeTab), - }; - storeTabs(updated); - setEditorValues(updated.tabs[updated.activeTabIndex]); - onTabChange?.(updated); - return updated; - }); - }; - - const closeTab: EditorContextType['closeTab'] = index => { - setTabState(current => { - const updated = { - tabs: current.tabs.filter((_tab, i) => index !== i), - activeTabIndex: Math.max(current.activeTabIndex - 1, 0), - }; - storeTabs(updated); - setEditorValues(updated.tabs[updated.activeTabIndex]); - onTabChange?.(updated); - return updated; - }); - }; - const $externalFragments = (() => { const map = new Map(); if (Array.isArray(externalFragments)) { @@ -495,11 +495,6 @@ export const EditorContextProvider: FC = ({ const validationRules = props.validationRules || []; const value: EditorContextType = { - addTab, - changeTab, - moveTab, - closeTab, - initialQuery: initialState.query, initialVariables: initialState.variables, initialHeaders: initialState.headers, From 58cb9eb2885a896895f6b14a349cda7d25a4847a Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 22:04:53 +0200 Subject: [PATCH 016/255] default query --- .../graphiql-react/src/editor/context.tsx | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 9d7807a6852..50fea7a9467 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -45,16 +45,19 @@ interface EditorStore extends TabsState { * Add a new tab. */ addTab(): void; + /** * Switch to a different tab. * @param index The index of the tab that should be switched to. */ changeTab(index: number): void; + /** * Move a tab to a new spot. * @param newOrder The new order for the tabs. */ moveTab(newOrder: TabState[]): void; + /** * Close a tab. If the currently active tab is closed, the tab before it will * become active. If there is no tab before the closed one, the tab after it @@ -62,6 +65,7 @@ interface EditorStore extends TabsState { * @param index The index of the tab that should be closed. */ closeTab(index: number): void; + /** * Update the state for the tab that is currently active. This will be * reflected in the `tabs` object and the state will be persisted in storage @@ -142,6 +146,15 @@ interface EditorStore extends TabsState { */ onTabChange?(tabState: TabsState): void; + /** + * The initial contents of the query editor when loading GraphiQL and there + * is no other source for the editor state. Other sources can be: + * - The `query` prop + * - The value persisted in storage + * These default contents will only be used for the first tab. When opening + * more tabs the query editor will start out empty. + */ + defaultQuery?: string; /** * Headers to be set when opening a new tab */ @@ -190,18 +203,9 @@ const EditorContext = createNullableContext('EditorContext'); type EditorContextProviderProps = Pick< EditorStore, - 'onTabChange' | 'onEditOperationName' | 'defaultHeaders' + 'onTabChange' | 'onEditOperationName' | 'defaultHeaders' | 'defaultQuery' > & { children: ReactNode; - /** - * The initial contents of the query editor when loading GraphiQL and there - * is no other source for the editor state. Other sources can be: - * - The `query` prop - * - The value persisted in storage - * These default contents will only be used for the first tab. When opening - * more tabs the query editor will start out empty. - */ - defaultQuery?: string; /** * With this prop you can pass so-called "external" fragments that will be * included in the query document (depending on usage). You can either pass @@ -369,6 +373,7 @@ export const editorStore = createStore((set, get) => ({ onEditOperationName: undefined, externalFragments: null!, onTabChange: undefined, + defaultQuery: undefined, defaultHeaders: undefined, })); @@ -376,6 +381,9 @@ export const EditorContextProvider: FC = ({ externalFragments, onEditOperationName, defaultHeaders, + onTabChange, + defaultQuery, + children, ...props }) => { const storage = useStorage(); @@ -411,7 +419,7 @@ export const EditorContextProvider: FC = ({ variables, headers, defaultTabs: props.defaultTabs, - defaultQuery: props.defaultQuery || DEFAULT_QUERY, + defaultQuery: defaultQuery || DEFAULT_QUERY, defaultHeaders, shouldPersistHeaders, }); @@ -429,7 +437,7 @@ export const EditorContextProvider: FC = ({ }; }); - const [tabState, setTabState] = useState(initialState.tabState); + const [tabState] = useState(initialState.tabState); const setShouldPersistHeaders = // eslint-disable-line react-hooks/exhaustive-deps -- false positive, function is optimized by react-compiler, no need to wrap with useCallback (persist: boolean) => { @@ -454,8 +462,6 @@ export const EditorContextProvider: FC = ({ } }, [props.shouldPersistHeaders, setShouldPersistHeaders]); - const { onTabChange, defaultQuery, children } = props; - const $externalFragments = (() => { const map = new Map(); if (Array.isArray(externalFragments)) { @@ -482,6 +488,7 @@ export const EditorContextProvider: FC = ({ externalFragments: $externalFragments, onTabChange, onEditOperationName, + defaultQuery, defaultHeaders, }); }, [ @@ -489,6 +496,7 @@ export const EditorContextProvider: FC = ({ onTabChange, tabState, onEditOperationName, + defaultQuery, defaultHeaders, ]); From 518ed77f686bc45d23f3c8332635e0d0d5365ea0 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 22:10:27 +0200 Subject: [PATCH 017/255] persist headers --- .../graphiql-react/src/editor/context.tsx | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 50fea7a9467..595548f47e0 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -9,7 +9,7 @@ import { import { VariableToType } from 'graphql-language-service'; import { FC, ReactNode, useContext, useEffect, useRef, useState } from 'react'; -import { useStorage } from '../storage'; +import { storageStore, useStorage } from '../storage'; import { createNullableContext } from '../utility/context'; import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor'; import { useSynchronizeValue } from './hooks'; @@ -121,6 +121,16 @@ interface EditorStore extends TabsState { */ setOperationName(operationName: string): void; + /** + * If the contents of the headers editor are persisted in storage. + */ + shouldPersistHeaders: boolean; + + /** + * Changes if headers should be persisted. + */ + setShouldPersistHeaders(persist: boolean): void; + /** * Invoked when the operation name changes. Possible triggers are: * - Editing the contents of the query editor @@ -188,15 +198,6 @@ export type EditorContextType = { * provided by the GraphQL spec. */ validationRules: ValidationRule[]; - - /** - * If the contents of the headers editor are persisted in storage. - */ - shouldPersistHeaders: boolean; - /** - * Changes if headers should be persisted. - */ - setShouldPersistHeaders(persist: boolean): void; }; const EditorContext = createNullableContext('EditorContext'); @@ -370,6 +371,21 @@ export const editorStore = createStore((set, get) => ({ updateActiveTabValues({ operationName }); onEditOperationName?.(operationName); }, + shouldPersistHeaders: false, + setShouldPersistHeaders(persist) { + const { headerEditor, tabs, activeTabIndex } = get(); + const { storage } = storageStore.getState(); + if (persist) { + storage.set(STORAGE_KEY_HEADERS, headerEditor?.getValue() ?? ''); + const serializedTabs = serializeTabState({ tabs, activeTabIndex }, true); + storage.set(STORAGE_KEY_TABS, serializedTabs); + } else { + storage.set(STORAGE_KEY_HEADERS, ''); + clearHeadersFromTabs(); + } + set({ shouldPersistHeaders: persist }); + storage.set(PERSIST_HEADERS_STORAGE_KEY, persist.toString()); + }, onEditOperationName: undefined, externalFragments: null!, onTabChange: undefined, @@ -387,11 +403,17 @@ export const EditorContextProvider: FC = ({ ...props }) => { const storage = useStorage(); - const { headerEditor, queryEditor, responseEditor, variableEditor } = + const { + headerEditor, + queryEditor, + responseEditor, + variableEditor, + setShouldPersistHeaders, + } = // TODO: refactor to use useEditorStore useStore(editorStore); - const [shouldPersistHeaders, setShouldPersistHeadersInternal] = useState( + const [shouldPersistHeaders] = useState( () => { const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; return props.shouldPersistHeaders !== false && isStored @@ -439,20 +461,6 @@ export const EditorContextProvider: FC = ({ const [tabState] = useState(initialState.tabState); - const setShouldPersistHeaders = // eslint-disable-line react-hooks/exhaustive-deps -- false positive, function is optimized by react-compiler, no need to wrap with useCallback - (persist: boolean) => { - if (persist) { - storage.set(STORAGE_KEY_HEADERS, headerEditor?.getValue() ?? ''); - const serializedTabs = serializeTabState(tabState, true); - storage.set(STORAGE_KEY_TABS, serializedTabs); - } else { - storage.set(STORAGE_KEY_HEADERS, ''); - clearHeadersFromTabs(); - } - setShouldPersistHeadersInternal(persist); - storage.set(PERSIST_HEADERS_STORAGE_KEY, persist.toString()); - }; - const lastShouldPersistHeadersProp = useRef(undefined); useEffect(() => { const propValue = Boolean(props.shouldPersistHeaders); @@ -509,9 +517,6 @@ export const EditorContextProvider: FC = ({ initialResponse: initialState.response, validationRules, - - shouldPersistHeaders, - setShouldPersistHeaders, }; return ( From 0662a03a2ac197409a4513c19f0d00f598d145d1 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 22:18:36 +0200 Subject: [PATCH 018/255] upd --- .../graphiql-react/src/editor/context.tsx | 122 +++++++++--------- 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 595548f47e0..d364393456b 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -7,10 +7,16 @@ import { visit, } from 'graphql'; import { VariableToType } from 'graphql-language-service'; -import { FC, ReactNode, useContext, useEffect, useRef, useState } from 'react'; +import { + FC, + ReactElement, + ReactNode, + useEffect, + useRef, + useState, +} from 'react'; import { storageStore, useStorage } from '../storage'; -import { createNullableContext } from '../utility/context'; import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor'; import { useSynchronizeValue } from './hooks'; import { STORAGE_KEY_QUERY } from './query-editor'; @@ -58,6 +64,37 @@ interface EditorStore extends TabsState { */ moveTab(newOrder: TabState[]): void; + /** + * The contents of the headers editor when initially rendering the provider + * component. + */ + + initialHeaders: string; + /** + * The contents of the query editor when initially rendering the provider + * component. + */ + + initialQuery: string; + /** + * The contents of the response editor when initially rendering the provider + * component. + */ + + initialResponse: string; + /** + * The contents of the variables editor when initially rendering the provider + * component. + */ + + initialVariables: string; + + /** + * A list of custom validation rules that are run in addition to the rules + * provided by the GraphQL spec. + */ + validationRules: ValidationRule[]; + /** * Close a tab. If the currently active tab is closed, the tab before it will * become active. If there is no tab before the closed one, the tab after it @@ -171,37 +208,6 @@ interface EditorStore extends TabsState { defaultHeaders?: string; } -export type EditorContextType = { - /** - * The contents of the headers editor when initially rendering the provider - * component. - */ - initialHeaders: string; - /** - * The contents of the query editor when initially rendering the provider - * component. - */ - initialQuery: string; - /** - * The contents of the response editor when initially rendering the provider - * component. - */ - initialResponse: string; - /** - * The contents of the variables editor when initially rendering the provider - * component. - */ - initialVariables: string; - - /** - * A list of custom validation rules that are run in addition to the rules - * provided by the GraphQL spec. - */ - validationRules: ValidationRule[]; -}; - -const EditorContext = createNullableContext('EditorContext'); - type EditorContextProviderProps = Pick< EditorStore, 'onTabChange' | 'onEditOperationName' | 'defaultHeaders' | 'defaultQuery' @@ -391,6 +397,11 @@ export const editorStore = createStore((set, get) => ({ onTabChange: undefined, defaultQuery: undefined, defaultHeaders: undefined, + validationRules: null!, + initialHeaders: null!, + initialQuery: null!, + initialResponse: null!, + initialVariables: null!, })); export const EditorContextProvider: FC = ({ @@ -400,6 +411,7 @@ export const EditorContextProvider: FC = ({ onTabChange, defaultQuery, children, + validationRules = [], ...props }) => { const storage = useStorage(); @@ -413,14 +425,12 @@ export const EditorContextProvider: FC = ({ // TODO: refactor to use useEditorStore useStore(editorStore); - const [shouldPersistHeaders] = useState( - () => { - const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; - return props.shouldPersistHeaders !== false && isStored - ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' - : Boolean(props.shouldPersistHeaders); - }, - ); + const [shouldPersistHeaders] = useState(() => { + const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; + return props.shouldPersistHeaders !== false && isStored + ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' + : Boolean(props.shouldPersistHeaders); + }); useSynchronizeValue(headerEditor, props.headers); useSynchronizeValue(queryEditor, props.query); @@ -498,6 +508,11 @@ export const EditorContextProvider: FC = ({ onEditOperationName, defaultQuery, defaultHeaders, + initialQuery: initialState.query, + initialVariables: initialState.variables, + initialHeaders: initialState.headers, + initialResponse: initialState.response, + validationRules, }); }, [ $externalFragments, @@ -506,30 +521,13 @@ export const EditorContextProvider: FC = ({ onEditOperationName, defaultQuery, defaultHeaders, - ]); - - const validationRules = props.validationRules || []; - - const value: EditorContextType = { - initialQuery: initialState.query, - initialVariables: initialState.variables, - initialHeaders: initialState.headers, - initialResponse: initialState.response, - + initialState, validationRules, - }; + ]); - return ( - {children} - ); + return children as ReactElement; }; -export function useEditorStore() { - return { - ...useStore(editorStore), - // TODO: this is temporary and will be removed over to zustand only usage - ...useContext(EditorContext), - }; -} +export const useEditorStore = () => useStore(editorStore); const PERSIST_HEADERS_STORAGE_KEY = 'shouldPersistHeaders'; From cf1a268a2bcf186c45b6d3f85dbab3dd7ec8faf8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 22:19:07 +0200 Subject: [PATCH 019/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index b3d46686336..c90fead268b 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -1,9 +1,4 @@ -import { - fillLeafs, - GetDefaultFieldNamesFn, - mergeAst, - MaybePromise, -} from '@graphiql/toolkit'; +import { fillLeafs, mergeAst, MaybePromise } from '@graphiql/toolkit'; import type { EditorChange, EditorConfiguration } from 'codemirror'; import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; import copyToClipboard from 'copy-to-clipboard'; From 839d938700b7e289deb9aa6864e6a50b661b7755 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:12:15 +0200 Subject: [PATCH 020/255] upd --- packages/graphiql-react/src/editor/tabs.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/graphiql-react/src/editor/tabs.ts b/packages/graphiql-react/src/editor/tabs.ts index e7100e729ef..9ff6dfdac10 100644 --- a/packages/graphiql-react/src/editor/tabs.ts +++ b/packages/graphiql-react/src/editor/tabs.ts @@ -215,14 +215,13 @@ export function serializeTabState( ); } -export function storeTabs(currentState: TabsState) { +export function storeTabs({ tabs, activeTabIndex }: TabsState) { const { storage } = storageStore.getState(); const { shouldPersistHeaders } = editorStore.getState(); const store = debounce(500, (value: string) => { storage.set(STORAGE_KEY, value); }); - - store(serializeTabState(currentState, shouldPersistHeaders)); + store(serializeTabState({ tabs, activeTabIndex }, shouldPersistHeaders)); } export function setEditorValues({ From f91224574fde8a5d963c951fe721c33eca2fc9fa Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:13:19 +0200 Subject: [PATCH 021/255] upd --- .../graphiql-react/src/editor/context.tsx | 115 +++++++++--------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index d364393456b..bd62bbb13d1 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -64,37 +64,6 @@ interface EditorStore extends TabsState { */ moveTab(newOrder: TabState[]): void; - /** - * The contents of the headers editor when initially rendering the provider - * component. - */ - - initialHeaders: string; - /** - * The contents of the query editor when initially rendering the provider - * component. - */ - - initialQuery: string; - /** - * The contents of the response editor when initially rendering the provider - * component. - */ - - initialResponse: string; - /** - * The contents of the variables editor when initially rendering the provider - * component. - */ - - initialVariables: string; - - /** - * A list of custom validation rules that are run in addition to the rules - * provided by the GraphQL spec. - */ - validationRules: ValidationRule[]; - /** * Close a tab. If the currently active tab is closed, the tab before it will * become active. If there is no tab before the closed one, the tab after it @@ -158,6 +127,37 @@ interface EditorStore extends TabsState { */ setOperationName(operationName: string): void; + /** + * The contents of the headers editor when initially rendering the provider + * component. + */ + + initialHeaders: string; + /** + * The contents of the query editor when initially rendering the provider + * component. + */ + + initialQuery: string; + /** + * The contents of the response editor when initially rendering the provider + * component. + */ + + initialResponse: string; + /** + * The contents of the variables editor when initially rendering the provider + * component. + */ + + initialVariables: string; + + /** + * A map of fragment definitions using the fragment name as key which are + * made available to include in the query. + */ + externalFragments: Map; + /** * If the contents of the headers editor are persisted in storage. */ @@ -168,6 +168,16 @@ interface EditorStore extends TabsState { */ setShouldPersistHeaders(persist: boolean): void; + /** + * The initial contents of the query editor when loading GraphiQL and there + * is no other source for the editor state. Other sources can be: + * - The `query` prop + * - The value persisted in storage + * These default contents will only be used for the first tab. When opening + * more tabs the query editor will start out empty. + */ + defaultQuery?: string; + /** * Invoked when the operation name changes. Possible triggers are: * - Editing the contents of the query editor @@ -177,12 +187,6 @@ interface EditorStore extends TabsState { */ onEditOperationName?(operationName: string): void; - /** - * A map of fragment definitions using the fragment name as key which are - * made available to include in the query. - */ - externalFragments: Map; - /** * Invoked when the state of the tabs changes. Possible triggers are: * - Updating any editor contents inside the currently active tab @@ -194,14 +198,11 @@ interface EditorStore extends TabsState { onTabChange?(tabState: TabsState): void; /** - * The initial contents of the query editor when loading GraphiQL and there - * is no other source for the editor state. Other sources can be: - * - The `query` prop - * - The value persisted in storage - * These default contents will only be used for the first tab. When opening - * more tabs the query editor will start out empty. + * A list of custom validation rules that are run in addition to the rules + * provided by the GraphQL spec. */ - defaultQuery?: string; + validationRules: ValidationRule[]; + /** * Headers to be set when opening a new tab */ @@ -415,15 +416,7 @@ export const EditorContextProvider: FC = ({ ...props }) => { const storage = useStorage(); - const { - headerEditor, - queryEditor, - responseEditor, - variableEditor, - setShouldPersistHeaders, - } = - // TODO: refactor to use useEditorStore - useStore(editorStore); + const isMounted = useEditorStore(store => Boolean(store.tabs)); const [shouldPersistHeaders] = useState(() => { const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; @@ -431,11 +424,17 @@ export const EditorContextProvider: FC = ({ ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' : Boolean(props.shouldPersistHeaders); }); - - useSynchronizeValue(headerEditor, props.headers); - useSynchronizeValue(queryEditor, props.query); - useSynchronizeValue(responseEditor, props.response); - useSynchronizeValue(variableEditor, props.variables); + // const { headerEditor, queryEditor, responseEditor, variableEditor } = + // useEditorStore(o => ({ + // headerEditor: o.headerEditor, + // queryEditor: o.queryEditor, + // responseEditor: o.responseEditor, + // variableEditor: o.variableEditor, + // })); + // useSynchronizeValue(headerEditor, props.headers); + // useSynchronizeValue(queryEditor, props.query); + // useSynchronizeValue(responseEditor, props.response); + // useSynchronizeValue(variableEditor, props.variables); // We store this in state but never update it. By passing a function we only // need to compute it lazily during the initial render. From 0abb0ec0c88d8ba2e2c489ae945abf0b0232d88b Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:14:39 +0200 Subject: [PATCH 022/255] upd --- .../graphiql-react/src/editor/context.tsx | 54 ++++++++++++++++--- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index bd62bbb13d1..9e32f5e4bd1 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -499,34 +499,72 @@ export const EditorContextProvider: FC = ({ return map; })(); + const initialRendered = useRef(false); + useEffect(() => { + if (initialRendered.current) { + return; + } + initialRendered.current = true; + + // We only need to compute it lazily during the initial render. + const query = props.query ?? storage.get(STORAGE_KEY_QUERY) ?? null; + const variables = + props.variables ?? storage.get(STORAGE_KEY_VARIABLES) ?? null; + const headers = props.headers ?? storage.get(STORAGE_KEY_HEADERS) ?? null; + const response = props.response ?? ''; + + const tabState = getDefaultTabState({ + query, + variables, + headers, + defaultTabs: props.defaultTabs, + defaultQuery: defaultQuery || DEFAULT_QUERY, + defaultHeaders, + shouldPersistHeaders, + }); + storeTabs(tabState); + editorStore.setState({ ...tabState, + initialQuery: + query ?? + (tabState.activeTabIndex === 0 ? tabState.tabs[0].query : null) ?? + '', + initialVariables: variables ?? '', + initialHeaders: headers ?? defaultHeaders ?? '', + initialResponse: response, + }); + }, []); + + useEffect(() => { + editorStore.setState({ externalFragments: $externalFragments, onTabChange, onEditOperationName, defaultQuery, defaultHeaders, - initialQuery: initialState.query, - initialVariables: initialState.variables, - initialHeaders: initialState.headers, - initialResponse: initialState.response, validationRules, }); }, [ $externalFragments, onTabChange, - tabState, onEditOperationName, defaultQuery, defaultHeaders, - initialState, validationRules, ]); - return children as ReactElement; + // Ensure store was initialized + return isMounted && (children as ReactElement); }; -export const useEditorStore = () => useStore(editorStore); +function useEditorStore(): EditorStore; +function useEditorStore(selector: (state: EditorStore) => T): T; +function useEditorStore(selector?: (state: EditorStore) => T) { + return useStore(editorStore, selector!); +} + +export { useEditorStore }; const PERSIST_HEADERS_STORAGE_KEY = 'shouldPersistHeaders'; From e8403f6b8f5493855712e781ebb4ef9ef73032eb Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:52:59 +0200 Subject: [PATCH 023/255] upd --- .../graphiql-react/src/utility/context.ts | 38 ------------------- .../src/utility/create-bounded-use-store.ts | 10 +++++ 2 files changed, 10 insertions(+), 38 deletions(-) delete mode 100644 packages/graphiql-react/src/utility/context.ts create mode 100644 packages/graphiql-react/src/utility/create-bounded-use-store.ts diff --git a/packages/graphiql-react/src/utility/context.ts b/packages/graphiql-react/src/utility/context.ts deleted file mode 100644 index 5d4403234fe..00000000000 --- a/packages/graphiql-react/src/utility/context.ts +++ /dev/null @@ -1,38 +0,0 @@ -'use no memo'; - -import { Context, createContext, useContext } from 'react'; - -export function createNullableContext(name: string): Context { - const context = createContext(null); - context.displayName = name; - return context; -} - -export function createContextHook(context: Context) { - function useGivenContext(options: { nonNull: true; caller?: Function }): T; - function useGivenContext(options: { - nonNull?: boolean; - caller?: Function; - }): T | null; - function useGivenContext(): T | null; - function useGivenContext(options?: { - nonNull?: boolean; - caller?: Function; - }): T | null { - const value = useContext(context); - if (value === null && options?.nonNull) { - throw new Error( - `Tried to use \`${ - options.caller?.name || 'a component' - }\` without the necessary context. Make sure to render the \`${ - context.displayName - }Provider\` component higher up the tree.`, - ); - } - return value; - } - Object.defineProperty(useGivenContext, 'name', { - value: `use${context.displayName}`, - }); - return useGivenContext; -} diff --git a/packages/graphiql-react/src/utility/create-bounded-use-store.ts b/packages/graphiql-react/src/utility/create-bounded-use-store.ts new file mode 100644 index 00000000000..a0b44b40902 --- /dev/null +++ b/packages/graphiql-react/src/utility/create-bounded-use-store.ts @@ -0,0 +1,10 @@ +import { ExtractState, StoreApi, useStore } from 'zustand'; + +// https://zustand.docs.pmnd.rs/guides/typescript#bounded-usestore-hook-for-vanilla-stores +export const createBoundedUseStore = (store => selector => + useStore(store, selector)) as >( + store: S, +) => { + (): ExtractState; + (selector: (state: ExtractState) => T): T; +}; From 5824175a8af56e8225ffd06f48c27849d0f732a5 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:55:45 +0200 Subject: [PATCH 024/255] upd --- .../graphiql-plugin-doc-explorer/src/context.ts | 14 +++++++------- packages/graphiql-plugin-history/src/context.tsx | 13 ++++++++----- packages/graphiql-react/src/execution.tsx | 7 +++---- packages/graphiql-react/src/plugin.tsx | 7 +++---- packages/graphiql-react/src/schema.ts | 9 +++------ packages/graphiql-react/src/storage.tsx | 9 ++++----- packages/graphiql-react/src/utility/index.ts | 2 +- 7 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/graphiql-plugin-doc-explorer/src/context.ts b/packages/graphiql-plugin-doc-explorer/src/context.ts index 3bca32fa3d9..03e333b8551 100644 --- a/packages/graphiql-plugin-doc-explorer/src/context.ts +++ b/packages/graphiql-plugin-doc-explorer/src/context.ts @@ -15,8 +15,12 @@ import { isUnionType, } from 'graphql'; import { FC, ReactElement, ReactNode, useEffect } from 'react'; -import { SchemaContextType, useSchemaStore } from '@graphiql/react'; -import { createStore, useStore } from 'zustand'; +import { + SchemaContextType, + useSchemaStore, + createBoundedUseStore, +} from '@graphiql/react'; +import { createStore } from 'zustand'; export type DocExplorerFieldDef = | GraphQLField @@ -257,11 +261,7 @@ export const DocExplorerContextProvider: FC<{ return children as ReactElement; }; -function useDocExplorerStore( - selector: (state: DocExplorerContextType) => T, -): T { - return useStore(docExplorerStore, selector); -} +const useDocExplorerStore = createBoundedUseStore(docExplorerStore); export const useDocExplorer = () => useDocExplorerStore(state => state.explorerNavStack); diff --git a/packages/graphiql-plugin-history/src/context.tsx b/packages/graphiql-plugin-history/src/context.tsx index 00dee498f99..c8580f1c697 100644 --- a/packages/graphiql-plugin-history/src/context.tsx +++ b/packages/graphiql-plugin-history/src/context.tsx @@ -1,8 +1,13 @@ // eslint-disable-next-line react/jsx-filename-extension -- TODO import { FC, ReactElement, ReactNode, useEffect } from 'react'; -import { createStore, useStore } from 'zustand'; +import { createStore } from 'zustand'; import { HistoryStore, QueryStoreItem } from '@graphiql/toolkit'; -import { useExecutionStore, useEditorStore, useStorage } from '@graphiql/react'; +import { + useExecutionStore, + useEditorStore, + useStorage, + createBoundedUseStore, +} from '@graphiql/react'; const historyStore = createStore((set, get) => ({ historyStorage: null!, @@ -142,9 +147,7 @@ export const HistoryContextProvider: FC = ({ return children as ReactElement; }; -function useHistoryStore(selector: (state: HistoryContextType) => T): T { - return useStore(historyStore, selector); -} +const useHistoryStore = createBoundedUseStore(historyStore); export const useHistory = () => useHistoryStore(state => state.historyStorage.queries); diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index c59e5fb4e8c..9fb62e513f5 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -20,9 +20,10 @@ import setValue from 'set-value'; import getValue from 'get-value'; import { getAutoCompleteLeafs } from './editor'; -import { createStore, useStore } from 'zustand'; +import { createStore } from 'zustand'; import { editorStore } from './editor/context'; import { schemaStore } from './schema'; +import { createBoundedUseStore } from './utility'; export type ExecutionContextType = { /** @@ -287,9 +288,7 @@ export const ExecutionContextProvider: FC = ({ return children as ReactElement; }; -export function useExecutionStore() { - return useStore(executionStore); -} +export const useExecutionStore = createBoundedUseStore(executionStore); function tryParseJsonObject({ json, diff --git a/packages/graphiql-react/src/plugin.tsx b/packages/graphiql-react/src/plugin.tsx index 3a407c68ebc..60778f830c5 100644 --- a/packages/graphiql-react/src/plugin.tsx +++ b/packages/graphiql-react/src/plugin.tsx @@ -1,6 +1,7 @@ // eslint-disable-next-line react/jsx-filename-extension -- TODO import { ComponentType, FC, ReactElement, ReactNode, useEffect } from 'react'; -import { createStore, useStore } from 'zustand'; +import { createStore } from 'zustand'; +import { createBoundedUseStore } from './utility'; export type GraphiQLPlugin = { /** @@ -129,8 +130,6 @@ export const PluginContextProvider: FC = ({ return children as ReactElement; }; -export function usePluginStore() { - return useStore(pluginStore); -} +export const usePluginStore = createBoundedUseStore(pluginStore); // const STORAGE_KEY = 'visiblePlugin'; diff --git a/packages/graphiql-react/src/schema.ts b/packages/graphiql-react/src/schema.ts index 0e2c120c5ed..7c176bf67bb 100644 --- a/packages/graphiql-react/src/schema.ts +++ b/packages/graphiql-react/src/schema.ts @@ -16,9 +16,10 @@ import { validateSchema, } from 'graphql'; import { Dispatch, FC, ReactElement, ReactNode, useEffect } from 'react'; -import { createStore, useStore } from 'zustand'; +import { createStore } from 'zustand'; import { useEditorStore } from './editor'; import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; +import { createBoundedUseStore } from './utility'; type MaybeGraphQLSchema = GraphQLSchema | null | undefined; @@ -367,11 +368,7 @@ export const SchemaContextProvider: FC = ({ return children as ReactElement; }; -export function useSchemaStore( - selector?: (state: SchemaContextType) => T, -): T { - return useStore(schemaStore, selector!); -} +export const useSchemaStore = createBoundedUseStore(schemaStore); type IntrospectionArgs = { /** diff --git a/packages/graphiql-react/src/storage.tsx b/packages/graphiql-react/src/storage.tsx index 201bc07c342..d238419e8f9 100644 --- a/packages/graphiql-react/src/storage.tsx +++ b/packages/graphiql-react/src/storage.tsx @@ -1,7 +1,8 @@ // eslint-disable-next-line react/jsx-filename-extension -- TODO import { Storage, StorageAPI } from '@graphiql/toolkit'; import { FC, ReactElement, ReactNode, useEffect } from 'react'; -import { useStore, createStore } from 'zustand'; +import { createStore } from 'zustand'; +import { createBoundedUseStore } from './utility'; type StorageContextType = { storage: StorageAPI; @@ -35,8 +36,6 @@ export const StorageContextProvider: FC = ({ return $storage && (children as ReactElement); }; -function useStorage() { - return useStore(storageStore, state => state.storage); -} +const useStorageStore = createBoundedUseStore(storageStore); -export { useStorage }; +export const useStorage = () => useStorageStore(store => store.storage); diff --git a/packages/graphiql-react/src/utility/index.ts b/packages/graphiql-react/src/utility/index.ts index e12bd205fe7..9f73b06bc03 100644 --- a/packages/graphiql-react/src/utility/index.ts +++ b/packages/graphiql-react/src/utility/index.ts @@ -1,4 +1,4 @@ -export { createNullableContext, createContextHook } from './context'; +export { createBoundedUseStore } from './create-bounded-use-store'; export { debounce } from './debounce'; export { isMacOs } from './is-macos'; export { useDragResize } from './resize'; From 418b8051b9688e81ad03cc03271f5b4c88d0ae7a Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:56:04 +0200 Subject: [PATCH 025/255] upd --- .../graphiql-react/src/editor/context.tsx | 30 +++++++------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 9e32f5e4bd1..552e2b59057 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -1,3 +1,4 @@ +// eslint-disable-next-line react/jsx-filename-extension -- TODO import { DocumentNode, FragmentDefinitionNode, @@ -7,14 +8,7 @@ import { visit, } from 'graphql'; import { VariableToType } from 'graphql-language-service'; -import { - FC, - ReactElement, - ReactNode, - useEffect, - useRef, - useState, -} from 'react'; +import { FC, ReactElement, ReactNode, useEffect, useRef } from 'react'; import { storageStore, useStorage } from '../storage'; import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor'; @@ -37,7 +31,8 @@ import { import { CodeMirrorEditor } from './types'; import { STORAGE_KEY as STORAGE_KEY_VARIABLES } from './variable-editor'; import { DEFAULT_QUERY } from '../constants'; -import { createStore, useStore } from 'zustand'; +import { createStore } from 'zustand'; +import { createBoundedUseStore } from '../utility'; export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { documentAST: DocumentNode | null; @@ -535,7 +530,7 @@ export const EditorContextProvider: FC = ({ initialHeaders: headers ?? defaultHeaders ?? '', initialResponse: response, }); - }, []); + }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount useEffect(() => { editorStore.setState({ @@ -555,16 +550,13 @@ export const EditorContextProvider: FC = ({ validationRules, ]); - // Ensure store was initialized - return isMounted && (children as ReactElement); + if (!isMounted) { + // Ensure store was initialized + return null; + } + return children as ReactElement; }; -function useEditorStore(): EditorStore; -function useEditorStore(selector: (state: EditorStore) => T): T; -function useEditorStore(selector?: (state: EditorStore) => T) { - return useStore(editorStore, selector!); -} - -export { useEditorStore }; +export const useEditorStore = createBoundedUseStore(editorStore); const PERSIST_HEADERS_STORAGE_KEY = 'shouldPersistHeaders'; From 7b9bc0bfb42f249a13d07e68f05d09edcefc6529 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:57:53 +0200 Subject: [PATCH 026/255] upd --- .../graphiql-react/src/editor/context.tsx | 88 ++++++------------- 1 file changed, 28 insertions(+), 60 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 552e2b59057..3d5c0bdd268 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -407,72 +407,32 @@ export const EditorContextProvider: FC = ({ onTabChange, defaultQuery, children, + shouldPersistHeaders = false, validationRules = [], ...props }) => { const storage = useStorage(); const isMounted = useEditorStore(store => Boolean(store.tabs)); - const [shouldPersistHeaders] = useState(() => { - const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; - return props.shouldPersistHeaders !== false && isStored - ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' - : Boolean(props.shouldPersistHeaders); - }); - // const { headerEditor, queryEditor, responseEditor, variableEditor } = - // useEditorStore(o => ({ - // headerEditor: o.headerEditor, - // queryEditor: o.queryEditor, - // responseEditor: o.responseEditor, - // variableEditor: o.variableEditor, - // })); - // useSynchronizeValue(headerEditor, props.headers); - // useSynchronizeValue(queryEditor, props.query); - // useSynchronizeValue(responseEditor, props.response); - // useSynchronizeValue(variableEditor, props.variables); - - // We store this in state but never update it. By passing a function we only - // need to compute it lazily during the initial render. - const [initialState] = useState(() => { - const query = props.query ?? storage.get(STORAGE_KEY_QUERY) ?? null; - const variables = - props.variables ?? storage.get(STORAGE_KEY_VARIABLES) ?? null; - const headers = props.headers ?? storage.get(STORAGE_KEY_HEADERS) ?? null; - const response = props.response ?? ''; - - const tabState = getDefaultTabState({ - query, - variables, - headers, - defaultTabs: props.defaultTabs, - defaultQuery: defaultQuery || DEFAULT_QUERY, - defaultHeaders, - shouldPersistHeaders, - }); - storeTabs(tabState); - - return { - query: - query ?? - (tabState.activeTabIndex === 0 ? tabState.tabs[0].query : null) ?? - '', - variables: variables ?? '', - headers: headers ?? defaultHeaders ?? '', - response, - tabState, - }; - }); - - const [tabState] = useState(initialState.tabState); - - const lastShouldPersistHeadersProp = useRef(undefined); - useEffect(() => { - const propValue = Boolean(props.shouldPersistHeaders); - if (lastShouldPersistHeadersProp?.current !== propValue) { - setShouldPersistHeaders(propValue); - lastShouldPersistHeadersProp.current = propValue; - } - }, [props.shouldPersistHeaders, setShouldPersistHeaders]); + const headerEditor = useEditorStore(store => store.headerEditor); + const queryEditor = useEditorStore(store => store.queryEditor); + const responseEditor = useEditorStore(store => store.responseEditor); + const variableEditor = useEditorStore(store => store.variableEditor); + + useSynchronizeValue(headerEditor, props.headers); + useSynchronizeValue(queryEditor, props.query); + useSynchronizeValue(responseEditor, props.response); + useSynchronizeValue(variableEditor, props.variables); + + // TODO: + // const lastShouldPersistHeadersProp = useRef(undefined); + // useEffect(() => { + // const propValue = shouldPersistHeaders; + // if (lastShouldPersistHeadersProp.current !== propValue) { + // editorStore.getState().setShouldPersistHeaders(propValue); + // lastShouldPersistHeadersProp.current = propValue; + // } + // }, [shouldPersistHeaders]); const $externalFragments = (() => { const map = new Map(); @@ -520,7 +480,15 @@ export const EditorContextProvider: FC = ({ }); storeTabs(tabState); + const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; + + const $shouldPersistHeaders = + shouldPersistHeaders !== false && isStored + ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' + : shouldPersistHeaders; + editorStore.setState({ + shouldPersistHeaders: $shouldPersistHeaders, ...tabState, initialQuery: query ?? From fec041bb806740f25449f81ee0135090746c7b88 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Tue, 13 May 2025 23:58:44 +0200 Subject: [PATCH 027/255] upd --- packages/graphiql-react/src/editor/tabs.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/graphiql-react/src/editor/tabs.ts b/packages/graphiql-react/src/editor/tabs.ts index 9ff6dfdac10..e8e58f129b9 100644 --- a/packages/graphiql-react/src/editor/tabs.ts +++ b/packages/graphiql-react/src/editor/tabs.ts @@ -1,12 +1,8 @@ 'use no memo'; // can't figure why it isn't optimized import { storageStore } from '../storage'; -// eslint-disable-next-line @typescript-eslint/no-restricted-imports -- fixme -import { useCallback } from 'react'; - import { debounce } from '../utility/debounce'; -import { CodeMirrorEditorWithOperationFacts, editorStore } from './context'; -import { CodeMirrorEditor } from './types'; +import { editorStore } from './context'; export type TabDefinition = { /** From 9afd50ce9857c258597560b56af50f42bf2603a2 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 00:08:27 +0200 Subject: [PATCH 028/255] reduce rerenders --- .../graphiql-react/src/editor/components/header-editor.tsx | 2 +- .../graphiql-react/src/editor/components/variable-editor.tsx | 2 +- packages/graphiql-react/src/editor/hooks.ts | 4 +--- packages/graphiql-react/src/schema.ts | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/graphiql-react/src/editor/components/header-editor.tsx b/packages/graphiql-react/src/editor/components/header-editor.tsx index 80d6c00e5d7..3652b96f7e2 100644 --- a/packages/graphiql-react/src/editor/components/header-editor.tsx +++ b/packages/graphiql-react/src/editor/components/header-editor.tsx @@ -18,7 +18,7 @@ export const HeaderEditor: FC = ({ isHidden, ...hookArgs }) => { - const { headerEditor } = useEditorStore(); + const headerEditor = useEditorStore(store => store.headerEditor); const ref = useHeaderEditor(hookArgs); useEffect(() => { diff --git a/packages/graphiql-react/src/editor/components/variable-editor.tsx b/packages/graphiql-react/src/editor/components/variable-editor.tsx index 5aaf3d9fab7..6abb67ebba5 100644 --- a/packages/graphiql-react/src/editor/components/variable-editor.tsx +++ b/packages/graphiql-react/src/editor/components/variable-editor.tsx @@ -22,7 +22,7 @@ export const VariableEditor: FC = ({ isHidden, ...hookArgs }) => { - const { variableEditor } = useEditorStore(); + const variableEditor = useEditorStore(store => store.variableEditor); const ref = useVariableEditor(hookArgs); useEffect(() => { diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index c90fead268b..cacc01340a0 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -302,9 +302,7 @@ export function getAutoCompleteLeafs() { export const useEditorState = (editor: 'query' | 'variable' | 'header') => { // eslint-disable-next-line react-hooks/react-compiler -- TODO: check why query builder update only 1st field https://github.com/graphql/graphiql/issues/3836 'use no memo'; - const context = useEditorStore(); - - const editorInstance = context[`${editor}Editor` as const]; + const editorInstance = useEditorStore(store => store[`${editor}Editor`]); let valueString = ''; const editorValue = editorInstance?.getValue() ?? false; if (editorValue && editorValue.length > 0) { diff --git a/packages/graphiql-react/src/schema.ts b/packages/graphiql-react/src/schema.ts index 7c176bf67bb..81bf1ebfa38 100644 --- a/packages/graphiql-react/src/schema.ts +++ b/packages/graphiql-react/src/schema.ts @@ -307,7 +307,7 @@ export const SchemaContextProvider: FC = ({ 'The `SchemaContextProvider` component requires a `fetcher` function to be passed as prop.', ); } - const { headerEditor } = useEditorStore(); + const headerEditor = useEditorStore(store => store.headerEditor); /** * Synchronize prop changes with state From 50b7ef3df382805972c87a6a19d55b2f8ab2cd3d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 00:26:18 +0200 Subject: [PATCH 029/255] add logs --- packages/graphiql-react/src/editor/context.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 3d5c0bdd268..e27bfe0e787 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -282,6 +282,8 @@ export const editorStore = createStore((set, get) => ({ const { defaultQuery, defaultHeaders, onTabChange } = get(); // Make sure the current tab stores the latest values + // eslint-disable-next-line no-console + console.log(2, 'addTab', current.tabs); const updatedValues = synchronizeActiveTabValues(current); const updated = { tabs: [ @@ -342,6 +344,8 @@ export const editorStore = createStore((set, get) => ({ updateActiveTabValues(partialTab) { set(current => { const { onTabChange } = get(); + // eslint-disable-next-line no-console + console.log(1, 'updateActiveTabValues', current.tabs); const updated = setPropertiesInActiveTab(current, partialTab); storeTabs(updated); onTabChange?.(updated); From 82f6c094102c53fa231c627054a8ff49fe620cbc Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 00:39:26 +0200 Subject: [PATCH 030/255] try --- packages/graphiql-react/src/editor/context.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index e27bfe0e787..173cd42bda9 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -343,6 +343,11 @@ export const editorStore = createStore((set, get) => ({ }, updateActiveTabValues(partialTab) { set(current => { + if (!current.tabs) { + // Vitest fails with TypeError: Cannot read properties of null (reading 'map') + // in `setPropertiesInActiveTab` when `tabs` is `null` + return current; + } const { onTabChange } = get(); // eslint-disable-next-line no-console console.log(1, 'updateActiveTabValues', current.tabs); From 7d9cd26ac574e2ad9c4c906cd127b303bfac52bf Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 00:49:58 +0200 Subject: [PATCH 031/255] cleanup --- packages/graphiql-react/src/editor/context.tsx | 4 ---- packages/graphiql-react/src/execution.tsx | 17 +++++++++-------- packages/graphiql-react/src/toolbar/execute.tsx | 4 ++-- .../src/create-fetcher/types.ts | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 173cd42bda9..d275756220b 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -282,8 +282,6 @@ export const editorStore = createStore((set, get) => ({ const { defaultQuery, defaultHeaders, onTabChange } = get(); // Make sure the current tab stores the latest values - // eslint-disable-next-line no-console - console.log(2, 'addTab', current.tabs); const updatedValues = synchronizeActiveTabValues(current); const updated = { tabs: [ @@ -349,8 +347,6 @@ export const editorStore = createStore((set, get) => ({ return current; } const { onTabChange } = get(); - // eslint-disable-next-line no-console - console.log(1, 'updateActiveTabValues', current.tabs); const updated = setPropertiesInActiveTab(current, partialTab); storeTabs(updated); onTabChange?.(updated); diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index 9fb62e513f5..d4ec2e6324d 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -34,12 +34,16 @@ export type ExecutionContextType = { */ isFetching: boolean; /** - * If there is currently a GraphQL request in-flight. For multipart - * requests like subscriptions, this will be `true` until the last batch - * has been fetched or the connection is closed from the client. - * @default false + * Represents an active GraphQL subscription. + * + * For multipart operations such as subscriptions, this + * will hold an `Unsubscribable` object while the request is in-flight. It + * remains non-null until the operation completes or is manually unsubscribed. + * + * @rmarks Use `subscription?.unsubscribe()` to cancel the request. + * @default null */ - isSubscribed: boolean; + subscription: Unsubscribable | null; /** * The operation name that will be sent with all GraphQL requests. * @default null @@ -53,7 +57,6 @@ export type ExecutionContextType = { * Stop the GraphQL request that is currently in-flight. */ stop(): void; - subscription: Unsubscribable | null; /** * A function to determine which field leafs are automatically added when * trying to execute a query with missing selection sets. It will be called @@ -93,7 +96,6 @@ export const executionStore = createStore< Pick >((set, get) => ({ isFetching: false, - isSubscribed: false, subscription: null, operationName: null, getDefaultFieldNames: undefined, @@ -279,7 +281,6 @@ export const ExecutionContextProvider: FC = ({ } useEffect(() => { executionStore.setState({ - // isSubscribed: Boolean(subscription), operationName, getDefaultFieldNames, }); diff --git a/packages/graphiql-react/src/toolbar/execute.tsx b/packages/graphiql-react/src/toolbar/execute.tsx index 9ce96a9be96..9addafbb2a9 100644 --- a/packages/graphiql-react/src/toolbar/execute.tsx +++ b/packages/graphiql-react/src/toolbar/execute.tsx @@ -7,12 +7,12 @@ import './execute.css'; export const ExecuteButton: FC = () => { const { queryEditor, setOperationName } = useEditorStore(); - const { isFetching, isSubscribed, operationName, run, stop } = + const { isFetching, subscription, operationName, run, stop } = useExecutionStore(); const operations = queryEditor?.operations || []; const hasOptions = operations.length > 1 && typeof operationName !== 'string'; - const isRunning = isFetching || isSubscribed; + const isRunning = isFetching || Boolean(subscription); const label = `${isRunning ? 'Stop' : 'Execute'} query (Ctrl-Enter)`; const buttonProps = { diff --git a/packages/graphiql-toolkit/src/create-fetcher/types.ts b/packages/graphiql-toolkit/src/create-fetcher/types.ts index 9ae06a67beb..f44d0b6a315 100644 --- a/packages/graphiql-toolkit/src/create-fetcher/types.ts +++ b/packages/graphiql-toolkit/src/create-fetcher/types.ts @@ -19,7 +19,7 @@ export type Observable = { ): Unsubscribable; }; -// These type just taken from https://github.com/ReactiveX/rxjs/blob/master/src/internal/types.ts#L41 +// This type just taken from https://github.com/ReactiveX/rxjs/blob/master/src/internal/types.ts#L41 export type Unsubscribable = { unsubscribe: () => void; }; From 30d298f12215e31dbfd3126084a41d55a845aaaa Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 00:52:33 +0200 Subject: [PATCH 032/255] cspell --- packages/graphiql-react/src/execution.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index d4ec2e6324d..90e171572d5 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -40,7 +40,7 @@ export type ExecutionContextType = { * will hold an `Unsubscribable` object while the request is in-flight. It * remains non-null until the operation completes or is manually unsubscribed. * - * @rmarks Use `subscription?.unsubscribe()` to cancel the request. + * @remarks Use `subscription?.unsubscribe()` to cancel the request. * @default null */ subscription: Unsubscribable | null; From 2e261cacee0129b5a62fda889d507c2916329b6d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:12:11 +0200 Subject: [PATCH 033/255] upd --- .eslintrc.js | 5 +++ .../graphiql-react/src/editor/completion.ts | 36 ++++++------------- packages/graphiql-react/src/editor/index.ts | 1 - packages/graphiql-react/src/index.ts | 1 - .../src/utils/getOperationFacts.ts | 4 +-- 5 files changed, 17 insertions(+), 30 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5f94c62ea4b..9d097d4f6d7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -142,6 +142,11 @@ module.exports = { property: 'navigator', message: 'Use `navigator` instead', }, + { + object: 'window', + property: 'getComputedStyle', + message: 'Use `getComputedStyle` instead', + }, ], 'no-return-assign': 'error', 'no-return-await': 'error', diff --git a/packages/graphiql-react/src/editor/completion.ts b/packages/graphiql-react/src/editor/completion.ts index 6dd601a1151..828f85084fa 100644 --- a/packages/graphiql-react/src/editor/completion.ts +++ b/packages/graphiql-react/src/editor/completion.ts @@ -8,9 +8,9 @@ import { isNonNullType, } from 'graphql'; import { markdown } from '../markdown'; -import { PluginContextType } from '../plugin'; +import { pluginStore } from '../plugin'; import { importCodeMirror } from './common'; -import { SchemaContextType } from '../schema'; +import { schemaStore } from '../schema'; /** * Render a custom UI for CodeMirror's hint which includes additional info @@ -19,11 +19,6 @@ import { SchemaContextType } from '../schema'; export function onHasCompletion( _cm: Editor, data: EditorChange | undefined, - { - schema, - setSchemaReference, - }: Pick, - plugin: PluginContextType | null, callback?: (type: GraphQLNamedType) => void, ): void { void importCodeMirror([], { useCommonAddons: false }).then(CodeMirror => { @@ -134,18 +129,11 @@ export function onHasCompletion( * default to using `border-box` for box sizing. When using * `content-box` this would not be necessary. */ + const { paddingBottom, maxHeight } = getComputedStyle(information); const defaultInformationPadding = - parseInt( - window - .getComputedStyle(information) - .paddingBottom.replace(/px$/, ''), - 10, - ) || 0; + parseInt(paddingBottom.replace(/px$/, ''), 10) || 0; const defaultInformationMaxHeight = - parseInt( - window.getComputedStyle(information).maxHeight.replace(/px$/, ''), - 10, - ) || 0; + parseInt(maxHeight.replace(/px$/, ''), 10) || 0; const handleScroll = () => { if (information) { information.style.paddingTop = @@ -240,18 +228,16 @@ export function onHasCompletion( }); function onClickHintInformation(event: Event) { - const referencePlugin = plugin?.referencePlugin; - if ( - !schema || - !referencePlugin || - !(event.currentTarget instanceof HTMLElement) - ) { + const { schema, setSchemaReference } = schemaStore.getState(); + const { referencePlugin, setVisiblePlugin } = pluginStore.getState(); + const element = event.currentTarget; + if (!schema || !referencePlugin || !(element instanceof HTMLElement)) { return; } - const typeName = event.currentTarget.textContent || ''; + const typeName = element.textContent ?? ''; const type = schema.getType(typeName); if (type) { - plugin.setVisiblePlugin(referencePlugin); + setVisiblePlugin(referencePlugin); setSchemaReference({ kind: 'Type', type }); callback?.(type); } diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index 8243aa21f19..9e2213eb88c 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -22,7 +22,6 @@ export { useQueryEditor } from './query-editor'; export { useResponseEditor } from './response-editor'; export { useVariableEditor } from './variable-editor'; -export type { EditorContextType } from './context'; export type { UseHeaderEditorArgs } from './header-editor'; export type { UseQueryEditorArgs } from './query-editor'; export type { diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 977958f3e1c..b1e8ee02073 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -36,7 +36,6 @@ export * from './toolbar'; export type { CommonEditorProps, - EditorContextType, KeyMap, ResponseTooltipType, TabsState, diff --git a/packages/graphql-language-service/src/utils/getOperationFacts.ts b/packages/graphql-language-service/src/utils/getOperationFacts.ts index 4abe09a3558..7c3b84e837e 100644 --- a/packages/graphql-language-service/src/utils/getOperationFacts.ts +++ b/packages/graphql-language-service/src/utils/getOperationFacts.ts @@ -90,9 +90,7 @@ export default function getOperationFacts( ...getOperationASTFacts(documentAST, schema), documentAST, }; - } catch { - return; - } + } catch {} } /** From 7119c6a72c2158cecb0773439b215e8d7b8d2680 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:12:29 +0200 Subject: [PATCH 034/255] upd --- packages/codemirror-graphql/src/utils/info-addon.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/codemirror-graphql/src/utils/info-addon.ts b/packages/codemirror-graphql/src/utils/info-addon.ts index cff916607ce..78a847ededb 100644 --- a/packages/codemirror-graphql/src/utils/info-addon.ts +++ b/packages/codemirror-graphql/src/utils/info-addon.ts @@ -119,17 +119,18 @@ function showPopup(cm: CodeMirror.Editor, box: DOMRect, info: HTMLDivElement) { document.body.append(popup); const popupBox = popup.getBoundingClientRect(); - const popupStyle = window.getComputedStyle(popup); + const { marginLeft, marginRight, marginBottom, marginTop } = getComputedStyle(popup); + const popupWidth = popupBox.right - popupBox.left + - parseFloat(popupStyle.marginLeft) + - parseFloat(popupStyle.marginRight); + parseFloat(marginLeft) + + parseFloat(marginRight); const popupHeight = popupBox.bottom - popupBox.top + - parseFloat(popupStyle.marginTop) + - parseFloat(popupStyle.marginBottom); + parseFloat(marginTop) + + parseFloat(marginBottom); let topPos = box.bottom; if ( From 31241e28de00bbd0a09419a0265bb39f6c939353 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:14:14 +0200 Subject: [PATCH 035/255] upd --- packages/graphiql-react/src/editor/components/query-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/editor/components/query-editor.tsx b/packages/graphiql-react/src/editor/components/query-editor.tsx index 9a4f67c456f..c4dfead3a4f 100644 --- a/packages/graphiql-react/src/editor/components/query-editor.tsx +++ b/packages/graphiql-react/src/editor/components/query-editor.tsx @@ -10,6 +10,6 @@ import '../style/auto-insertion.css'; import '../style/editor.css'; export const QueryEditor: FC = props => { - const ref = useQueryEditor(props, QueryEditor); + const ref = useQueryEditor(props); return
; }; From 037485db75e9bd8c6da1a90c005dce1614a208b8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:14:56 +0200 Subject: [PATCH 036/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 38 +++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index cacc01340a0..b31737f6202 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -381,26 +381,28 @@ export function useOptimisticState([ useEffect(() => { if (lastStateRef.current.last === upstreamState) { // No change; ignore - } else { - lastStateRef.current.last = upstreamState; - if (lastStateRef.current.pending === null) { - // Gracefully accept update from upstream - setOperationsText(upstreamState); - } else if (lastStateRef.current.pending === upstreamState) { - // They received our update and sent it back to us - clear pending, and - // send next if appropriate - lastStateRef.current.pending = null; - if (upstreamState !== state) { - // Change has occurred; upstream it - lastStateRef.current.pending = state; - upstreamSetState(state); - } - } else { - // They got a different update; overwrite our local state (!!) - lastStateRef.current.pending = null; - setOperationsText(upstreamState); + return; + } + lastStateRef.current.last = upstreamState; + if (lastStateRef.current.pending === null) { + // Gracefully accept update from upstream + setOperationsText(upstreamState); + return; + } + if (lastStateRef.current.pending === upstreamState) { + // They received our update and sent it back to us - clear pending, and + // send next if appropriate + lastStateRef.current.pending = null; + if (upstreamState !== state) { + // Change has occurred; upstream it + lastStateRef.current.pending = state; + upstreamSetState(state); } + return; } + // They got a different update; overwrite our local state (!!) + lastStateRef.current.pending = null; + setOperationsText(upstreamState); }, [upstreamState, state, upstreamSetState]); const setState = (newState: string) => { From 13b008b93d3ffd825786182c1d538afe112c79ed Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:15:15 +0200 Subject: [PATCH 037/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index b31737f6202..b81919846ac 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -132,13 +132,14 @@ export function useKeyMap( editor.removeKeyMap(key); } - if (callback) { - const keyMap: Record = {}; - for (const key of keys) { - keyMap[key] = () => callback(); - } - editor.addKeyMap(keyMap); + if (!callback) { + return; + } + const keyMap: Record = {}; + for (const key of keys) { + keyMap[key] = () => callback(); } + editor.addKeyMap(keyMap); }, [editor, keys, callback]); } From 93b1492ef065c80c9d37979e29f2354bc64e631b Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:15:33 +0200 Subject: [PATCH 038/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index b81919846ac..06e8143d28a 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -144,10 +144,6 @@ export function useKeyMap( } export type UseCopyQueryArgs = { - /** - * This is only meant to be used internally in `@graphiql/react`. - */ - caller?: Function; /** * Invoked when the current contents of the query editor are copied to the * clipboard. From 5e572fd8e5cd096e6d5e864f5879bae9e6c34567 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:16:35 +0200 Subject: [PATCH 039/255] upd --- packages/graphiql-react/src/editor/query-editor.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.ts index e591e387f70..3cca0fcb280 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.ts @@ -374,7 +374,7 @@ export function useQueryEditor( updateActiveTabValues, ]); - useSynchronizeSchema(queryEditor, schema ?? null, codeMirrorRef); + useSynchronizeSchema(queryEditor, codeMirrorRef); useSynchronizeValidationRules( queryEditor, validationRules ?? null, @@ -434,9 +434,10 @@ export function useQueryEditor( function useSynchronizeSchema( editor: CodeMirrorEditor | null, - schema: GraphQLSchema | null, codeMirrorRef: RefObject, ) { + const schema = useSchemaStore(store => store.schema); + useEffect(() => { if (!editor) { return; @@ -445,8 +446,8 @@ function useSynchronizeSchema( const didChange = editor.options.lint.schema !== schema; updateEditorSchema(editor, schema); - if (didChange && codeMirrorRef.current) { - codeMirrorRef.current.signal(editor, 'change', editor); + if (didChange) { + codeMirrorRef.current?.signal(editor, 'change', editor); } }, [editor, schema, codeMirrorRef]); } From 3648b78ec031573ff6b464f1c05b70d521d2e156 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:18:38 +0200 Subject: [PATCH 040/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index 06e8143d28a..67ab5e329ca 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -5,8 +5,7 @@ import copyToClipboard from 'copy-to-clipboard'; import { parse, print } from 'graphql'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports -- TODO: check why query builder update only 1st field https://github.com/graphql/graphiql/issues/3836 import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { usePluginStore } from '../plugin'; -import { schemaStore, useSchemaStore } from '../schema'; +import { schemaStore } from '../schema'; import { storageStore } from '../storage'; import { debounce } from '../utility'; import { onHasCompletion } from './completion'; @@ -83,8 +82,6 @@ export function useCompletion( editor: CodeMirrorEditor | null, callback?: (reference: SchemaReference) => void, ) { - const { schema, setSchemaReference } = useSchemaStore(); - const plugin = usePluginStore(); useEffect(() => { if (!editor) { return; @@ -94,12 +91,11 @@ export function useCompletion( instance: CodeMirrorEditor, changeObj?: EditorChange, ) => { - const schemaContext = { schema, setSchemaReference }; - onHasCompletion(instance, changeObj, schemaContext, plugin, type => { + onHasCompletion(instance, changeObj, type => { callback?.({ kind: 'Type', type, - schema: schema || undefined, + schema: schemaStore.getState().schema || undefined, }); }); }; @@ -114,7 +110,7 @@ export function useCompletion( 'hasCompletion', handleCompletion, ); - }, [callback, editor, plugin, schema, setSchemaReference]); + }, [callback, editor]); } type EmptyCallback = () => void; @@ -152,7 +148,7 @@ export type UseCopyQueryArgs = { onCopyQuery?: (query: string) => void; }; -export function useCopyQuery({ onCopyQuery }: UseCopyQueryArgs = {}) { +export function useCopyQuery() { return () => { const { queryEditor } = editorStore.getState(); if (!queryEditor) { @@ -197,9 +193,7 @@ function DEFAULT_PRETTIFY_QUERY(query: string): string { return print(parse(query)); } -export function usePrettifyEditors({ - onPrettifyQuery = DEFAULT_PRETTIFY_QUERY, -}: UsePrettifyEditorsArgs = {}) { +export function usePrettifyEditors() { return async () => { const { queryEditor, headerEditor, variableEditor } = editorStore.getState(); From bc43eb32d232e6bff87bd8115e33b058527b5fc4 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:20:03 +0200 Subject: [PATCH 041/255] upd --- .../graphiql-react/src/editor/query-editor.ts | 73 ++++++++----------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.ts index 3cca0fcb280..f7289239dee 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.ts @@ -14,8 +14,8 @@ import { import { RefObject, useEffect, useRef } from 'react'; import { executionStore } from '../execution'; import { markdown } from '../markdown'; -import { usePluginStore } from '../plugin'; -import { useSchemaStore } from '../schema'; +import { pluginStore } from '../plugin'; +import { schemaStore, useSchemaStore } from '../schema'; import { useStorage } from '../storage'; import { debounce } from '../utility/debounce'; import { @@ -28,8 +28,6 @@ import { CodeMirrorEditorWithOperationFacts, useEditorStore } from './context'; import { useCompletion, useCopyQuery, - UseCopyQueryArgs, - UsePrettifyEditorsArgs, useKeyMap, useMergeQuery, usePrettifyEditors, @@ -42,22 +40,20 @@ import { } from './types'; import { normalizeWhitespace } from './whitespace'; -export type UseQueryEditorArgs = WriteableEditorProps & - Pick & - Pick & { - /** - * Invoked when a reference to the GraphQL schema (type or field) is clicked - * as part of the editor or one of its tooltips. - * @param reference The reference that has been clicked. - */ - onClickReference?(reference: SchemaReference): void; - /** - * Invoked when the contents of the query editor change. - * @param value The new contents of the editor. - * @param documentAST The editor contents parsed into a GraphQL document. - */ - onEdit?(value: string, documentAST?: DocumentNode): void; - }; +export type UseQueryEditorArgs = WriteableEditorProps & { + /** + * Invoked when a reference to the GraphQL schema (type or field) is clicked + * as part of the editor or one of its tooltips. + * @param reference The reference that has been clicked. + */ + onClickReference?(reference: SchemaReference): void; + /** + * Invoked when the contents of the query editor change. + * @param value The new contents of the editor. + * @param documentAST The editor contents parsed into a GraphQL document. + */ + onEdit?(value: string, documentAST?: DocumentNode): void; +}; // To make react-compiler happy, otherwise complains about using dynamic imports in Component function importCodeMirrorImports() { @@ -114,19 +110,13 @@ function updateEditorExternalFragments( editor.options.hintOptions.externalFragments = externalFragmentList; } -export function useQueryEditor( - { - editorTheme = DEFAULT_EDITOR_THEME, - keyMap = DEFAULT_KEY_MAP, - onClickReference, - onCopyQuery, - onEdit, - onPrettifyQuery, - readOnly = false, - }: UseQueryEditorArgs = {}, - caller?: Function, -) { - const { schema, setSchemaReference } = useSchemaStore(); +export function useQueryEditor({ + editorTheme = DEFAULT_EDITOR_THEME, + keyMap = DEFAULT_KEY_MAP, + onClickReference, + onEdit, + readOnly = false, +}: UseQueryEditorArgs = {}) { const { externalFragments, initialQuery, @@ -138,10 +128,9 @@ export function useQueryEditor( updateActiveTabValues, } = useEditorStore(); const storage = useStorage(); - const plugin = usePluginStore(); - const copy = useCopyQuery({ caller: caller || _useQueryEditor, onCopyQuery }); + const copy = useCopyQuery(); const merge = useMergeQuery(); - const prettify = usePrettifyEditors({ onPrettifyQuery }); + const prettify = usePrettifyEditors(); const ref = useRef(null); const codeMirrorRef = useRef(undefined); @@ -150,16 +139,17 @@ export function useQueryEditor( >(() => {}); useEffect(() => { + const { referencePlugin, setVisiblePlugin } = pluginStore.getState(); + const { setSchemaReference } = schemaStore.getState(); onClickReferenceRef.current = reference => { - const referencePlugin = plugin?.referencePlugin; if (!referencePlugin) { return; } - plugin.setVisiblePlugin(referencePlugin); + setVisiblePlugin(referencePlugin); setSchemaReference(reference); onClickReference?.(reference); }; - }, [onClickReference, plugin, setSchemaReference]); + }, [onClickReference]); useEffect(() => { let isActive = true; @@ -306,7 +296,7 @@ export function useQueryEditor( editorInstance: CodeMirrorEditorWithOperationFacts, ) { const operationFacts = getOperationFacts( - schema, + schemaStore.getState().schema, editorInstance.getValue(), ); @@ -367,7 +357,6 @@ export function useQueryEditor( }, [ onEdit, queryEditor, - schema, setOperationName, storage, variableEditor, @@ -436,7 +425,7 @@ function useSynchronizeSchema( editor: CodeMirrorEditor | null, codeMirrorRef: RefObject, ) { - const schema = useSchemaStore(store => store.schema); + const schema = useSchemaStore(store => store.schema ?? null); useEffect(() => { if (!editor) { From 05954584ff3cc4533a0b14df4761de8c35d9baf8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 16:20:42 +0200 Subject: [PATCH 042/255] upd --- packages/graphiql/src/GraphiQL.tsx | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index b705ac33138..781e81faec3 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -175,7 +175,6 @@ type AddSuffix, Suffix extends string> = { export type GraphiQLInterfaceProps = WriteableEditorProps & AddSuffix, 'Query'> & - Pick & AddSuffix, 'Variables'> & AddSuffix, 'Headers'> & Pick & { @@ -327,11 +326,7 @@ export const GraphiQLInterface: FC = props => { acc.logo = curr; break; case GraphiQL.Toolbar: - // @ts-expect-error -- fix type error - acc.toolbar = cloneElement(curr, { - onCopyQuery: props.onCopyQuery, - onPrettifyQuery: props.onPrettifyQuery, - }); + acc.toolbar = curr; break; case GraphiQL.Footer: acc.footer = curr; @@ -341,13 +336,7 @@ export const GraphiQLInterface: FC = props => { }, { logo: , - toolbar: ( -
@@ -659,9 +672,9 @@ export const GraphiQLInterface: FC = props => {
{isExecutionFetching && } {footer}
@@ -679,7 +692,7 @@ export const GraphiQLInterface: FC = props => {
- +
= props => { - {props.showPersistHeadersSettings ? ( + {showPersistHeadersSettings ? (
From 661df4b284b2b459b98ce8e2720202eb4a468586 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:15:00 +0200 Subject: [PATCH 063/255] upd --- packages/graphiql/src/GraphiQL.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 7ff6e4ac512..b1bd5b2b6da 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -411,12 +411,6 @@ export const GraphiQLInterface: FC = ({ const handleTabClose: MouseEventHandler = async event => { const tabButton = event.currentTarget.previousSibling as HTMLButtonElement; const index = Number(tabButton.id.replace(TAB_CLASS_PREFIX, '')); - - /** TODO: - * Move everything after into `editorContext.closeTab` once zustand will be used instead of - * React context, since now we can't use execution context inside editor context, since editor - * context is used in execution context. - */ const shouldCloseTab = confirmCloseTab ? await confirmCloseTab(index) : true; From 787d47ce04258626c832fd47f4d6dd965efe042c Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:17:46 +0200 Subject: [PATCH 064/255] upd --- packages/graphiql/src/GraphiQL.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index b1bd5b2b6da..493f4b6bb3d 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -117,11 +117,6 @@ const GraphiQL_: FC = ({ '`toolbar.additionalComponent` was removed. Use render props on `GraphiQL.Toolbar` component instead.', ); } - const graphiqlProps = { - plugins: [referencePlugin, HISTORY_PLUGIN, ...plugins], - referencePlugin, - ...props, - }; const interfaceProps: GraphiQLInterfaceProps = { // TODO check if `showPersistHeadersSettings` is needed showPersistHeadersSettings: @@ -142,7 +137,11 @@ const GraphiQL_: FC = ({ className, }; return ( - + {children} From 9a5e29f8b580ce19f474a5fad2584551177996ed Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:30:14 +0200 Subject: [PATCH 065/255] upd --- packages/graphiql-react/src/constants.ts | 16 ++++++++++++---- .../graphiql-react/src/editor/header-editor.ts | 5 +++-- .../graphiql-react/src/editor/query-editor.ts | 3 ++- .../graphiql-react/src/editor/variable-editor.ts | 5 +++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index 088deb13103..188b94df8ea 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -1,3 +1,11 @@ +export const KEY_MAP = { + prettify: ['Shift-Ctrl-P'], + mergeFragments: ['Shift-Ctrl-M'], + runQuery: ['Ctrl-Enter'], + autoComplete: ['Ctrl-Space'], + copyQuery: ['Shift-Ctrl-C'] +} + export const DEFAULT_QUERY = `# Welcome to GraphiQL # # GraphiQL is an in-browser tool for writing, validating, and @@ -20,13 +28,13 @@ export const DEFAULT_QUERY = `# Welcome to GraphiQL # # Keyboard shortcuts: # -# Prettify query: Shift-Ctrl-P (or press the prettify button) +# Prettify query: ${KEY_MAP.prettify[0]} (or press the prettify button) # -# Merge fragments: Shift-Ctrl-M (or press the merge button) +# Merge fragments: ${KEY_MAP.mergeFragments[0]} (or press the merge button) # -# Run Query: Ctrl-Enter (or press the play button) +# Run Query: ${KEY_MAP.runQuery[0]} (or press the play button) # -# Auto Complete: Ctrl-Space (or just start typing) +# Auto Complete: ${KEY_MAP.autoComplete[0]} (or just start typing) # `; diff --git a/packages/graphiql-react/src/editor/header-editor.ts b/packages/graphiql-react/src/editor/header-editor.ts index 7592d7bfb04..3cb0f54f41a 100644 --- a/packages/graphiql-react/src/editor/header-editor.ts +++ b/packages/graphiql-react/src/editor/header-editor.ts @@ -16,6 +16,7 @@ import { } from './hooks'; import { WriteableEditorProps } from './types'; import { useExecutionStore } from '../execution'; +import { KEY_MAP } from '../constants'; export type UseHeaderEditorArgs = WriteableEditorProps & { /** @@ -115,8 +116,8 @@ export function useHeaderEditor({ ); useKeyMap(headerEditor, ['Cmd-Enter', 'Ctrl-Enter'], run); - useKeyMap(headerEditor, ['Shift-Ctrl-P'], prettifyEditors); - useKeyMap(headerEditor, ['Shift-Ctrl-M'], mergeQuery); + useKeyMap(headerEditor, KEY_MAP.prettify, prettifyEditors); + useKeyMap(headerEditor, KEY_MAP.mergeFragments, mergeQuery); return ref; } diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.ts index 20b0f53a43f..34772644132 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.ts @@ -38,6 +38,7 @@ import { WriteableEditorProps, } from './types'; import { normalizeWhitespace } from './whitespace'; +import { KEY_MAP } from '../constants'; export type UseQueryEditorArgs = WriteableEditorProps & { /** @@ -391,7 +392,7 @@ export function useQueryEditor({ useKeyMap(queryEditor, ['Shift-Ctrl-C'], copyQuery); // Shift-Ctrl-P is hard coded in Firefox for private browsing so adding an alternative to prettify useKeyMap(queryEditor, ['Shift-Ctrl-P', 'Shift-Ctrl-F'], prettifyEditors); - useKeyMap(queryEditor, ['Shift-Ctrl-M'], mergeQuery); + useKeyMap(queryEditor, KEY_MAP.mergeFragments, mergeQuery); return ref; } diff --git a/packages/graphiql-react/src/editor/variable-editor.ts b/packages/graphiql-react/src/editor/variable-editor.ts index e7cb22b98ed..3d6a909ba67 100644 --- a/packages/graphiql-react/src/editor/variable-editor.ts +++ b/packages/graphiql-react/src/editor/variable-editor.ts @@ -18,6 +18,7 @@ import { useSynchronizeOption, } from './hooks'; import { WriteableEditorProps } from './types'; +import { KEY_MAP } from '../constants'; export type UseVariableEditorArgs = WriteableEditorProps & { /** @@ -126,8 +127,8 @@ export function useVariableEditor({ useCompletion(variableEditor, onClickReference); useKeyMap(variableEditor, ['Cmd-Enter', 'Ctrl-Enter'], run); - useKeyMap(variableEditor, ['Shift-Ctrl-P'], prettifyEditors); - useKeyMap(variableEditor, ['Shift-Ctrl-M'], mergeQuery); + useKeyMap(variableEditor, KEY_MAP.prettify, prettifyEditors); + useKeyMap(variableEditor, KEY_MAP.mergeFragments, mergeQuery); return ref; } From 95e5e85420cd23fb274b9d5cf45b4c281af2b340 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:31:35 +0200 Subject: [PATCH 066/255] upd --- packages/graphiql-react/src/constants.ts | 2 +- packages/graphiql-react/src/editor/header-editor.ts | 2 +- packages/graphiql-react/src/editor/query-editor.ts | 2 +- packages/graphiql-react/src/editor/variable-editor.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index 188b94df8ea..032ce426aae 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -1,7 +1,7 @@ export const KEY_MAP = { prettify: ['Shift-Ctrl-P'], mergeFragments: ['Shift-Ctrl-M'], - runQuery: ['Ctrl-Enter'], + runQuery: ['Ctrl-Enter', 'Cmd-Enter'], autoComplete: ['Ctrl-Space'], copyQuery: ['Shift-Ctrl-C'] } diff --git a/packages/graphiql-react/src/editor/header-editor.ts b/packages/graphiql-react/src/editor/header-editor.ts index 3cb0f54f41a..f2f1c8713b0 100644 --- a/packages/graphiql-react/src/editor/header-editor.ts +++ b/packages/graphiql-react/src/editor/header-editor.ts @@ -115,7 +115,7 @@ export function useHeaderEditor({ 'headers', ); - useKeyMap(headerEditor, ['Cmd-Enter', 'Ctrl-Enter'], run); + useKeyMap(headerEditor, KEY_MAP.runQuery, run); useKeyMap(headerEditor, KEY_MAP.prettify, prettifyEditors); useKeyMap(headerEditor, KEY_MAP.mergeFragments, mergeQuery); diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.ts index 34772644132..6f26976b65d 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.ts @@ -388,7 +388,7 @@ export function useQueryEditor({ run(); }; - useKeyMap(queryEditor, ['Cmd-Enter', 'Ctrl-Enter'], runAtCursor); + useKeyMap(queryEditor, KEY_MAP.runQuery, runAtCursor); useKeyMap(queryEditor, ['Shift-Ctrl-C'], copyQuery); // Shift-Ctrl-P is hard coded in Firefox for private browsing so adding an alternative to prettify useKeyMap(queryEditor, ['Shift-Ctrl-P', 'Shift-Ctrl-F'], prettifyEditors); diff --git a/packages/graphiql-react/src/editor/variable-editor.ts b/packages/graphiql-react/src/editor/variable-editor.ts index 3d6a909ba67..55ce95c2126 100644 --- a/packages/graphiql-react/src/editor/variable-editor.ts +++ b/packages/graphiql-react/src/editor/variable-editor.ts @@ -126,7 +126,7 @@ export function useVariableEditor({ useCompletion(variableEditor, onClickReference); - useKeyMap(variableEditor, ['Cmd-Enter', 'Ctrl-Enter'], run); + useKeyMap(variableEditor, KEY_MAP.runQuery, run); useKeyMap(variableEditor, KEY_MAP.prettify, prettifyEditors); useKeyMap(variableEditor, KEY_MAP.mergeFragments, mergeQuery); From 27554e281fbed0fa032211e000bce871aa748aaa Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:32:39 +0200 Subject: [PATCH 067/255] upd --- packages/graphiql-react/src/editor/query-editor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.ts index 6f26976b65d..04621a76091 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.ts @@ -389,7 +389,7 @@ export function useQueryEditor({ }; useKeyMap(queryEditor, KEY_MAP.runQuery, runAtCursor); - useKeyMap(queryEditor, ['Shift-Ctrl-C'], copyQuery); + useKeyMap(queryEditor, KEY_MAP.copyQuery, copyQuery); // Shift-Ctrl-P is hard coded in Firefox for private browsing so adding an alternative to prettify useKeyMap(queryEditor, ['Shift-Ctrl-P', 'Shift-Ctrl-F'], prettifyEditors); useKeyMap(queryEditor, KEY_MAP.mergeFragments, mergeQuery); From 454cb9747b74e7cb340b9862885de8cdcd7cecf6 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:38:25 +0200 Subject: [PATCH 068/255] upd --- packages/graphiql-react/src/constants.ts | 5 +++-- packages/graphiql-react/src/index.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index 032ce426aae..1952fae26fa 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -3,8 +3,9 @@ export const KEY_MAP = { mergeFragments: ['Shift-Ctrl-M'], runQuery: ['Ctrl-Enter', 'Cmd-Enter'], autoComplete: ['Ctrl-Space'], - copyQuery: ['Shift-Ctrl-C'] -} + copyQuery: ['Shift-Ctrl-C'], + refetchSchema: ['Shift-Ctrl-R'], +} as const export const DEFAULT_QUERY = `# Welcome to GraphiQL # diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index e29efcd229b..24954c53147 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -50,3 +50,4 @@ export type { GraphiQLPlugin, PluginContextType } from './plugin'; export type { SchemaContextType } from './schema'; export type { Theme } from './theme'; export { clsx as cn } from 'clsx'; +export { KEY_MAP } from './constants' From ca5386138ff45b02c01fd400451858d36f8dc0b8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:39:05 +0200 Subject: [PATCH 069/255] upd --- packages/graphiql/src/GraphiQL.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 493f4b6bb3d..5b9dc7c2769 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -54,6 +54,7 @@ import { WriteableEditorProps, isMacOs, cn, + KEY_MAP, } from '@graphiql/react'; import { HistoryContextProvider, @@ -798,14 +799,11 @@ const SHORT_KEYS = Object.entries({ 'Search in editor': [modifier, 'F'], 'Search in documentation': [modifier, 'K'], 'Execute query': [modifier, 'Enter'], - 'Prettify editors': ['Ctrl', 'Shift', 'P'], - 'Merge fragments definitions into operation definition': [ - 'Ctrl', - 'Shift', - 'M', - ], - 'Copy query': ['Ctrl', 'Shift', 'C'], - 'Re-fetch schema using introspection': ['Ctrl', 'Shift', 'R'], + 'Prettify editors': KEY_MAP.prettify[0].split('-'), + 'Merge fragments definitions into operation definition': + KEY_MAP.mergeFragments[0].split('-'), + 'Copy query': KEY_MAP.copyQuery[0].split('-'), + 'Re-fetch schema using introspection': KEY_MAP.refetchSchema[0].split('-'), }); interface ShortKeysProps { From 5d076024198b4fb209d23bed02359610c4148fa6 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 18:53:30 +0200 Subject: [PATCH 070/255] upd --- packages/graphiql-react/src/constants.ts | 6 +++-- packages/graphiql-react/src/editor/hooks.ts | 2 +- packages/graphiql/src/GraphiQL.tsx | 26 +++++++++++++-------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index 1952fae26fa..899aa5ae1d0 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -1,11 +1,13 @@ -export const KEY_MAP = { +export const KEY_MAP = Object.freeze({ prettify: ['Shift-Ctrl-P'], mergeFragments: ['Shift-Ctrl-M'], runQuery: ['Ctrl-Enter', 'Cmd-Enter'], autoComplete: ['Ctrl-Space'], copyQuery: ['Shift-Ctrl-C'], refetchSchema: ['Shift-Ctrl-R'], -} as const + searchInEditor: ['Ctrl-F'], + searchInDocs: ['Ctrl-K'], +}); export const DEFAULT_QUERY = `# Welcome to GraphiQL # diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index 26723fec7fe..9442794dc91 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -117,7 +117,7 @@ type EmptyCallback = () => void; export function useKeyMap( editor: CodeMirrorEditor | null, - keys: string[], + keys: string[] | readonly string[], callback?: EmptyCallback, ) { useEffect(() => { diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 5b9dc7c2769..de286f3ab9b 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -793,17 +793,23 @@ export const GraphiQLInterface: FC = ({ ); }; -const modifier = isMacOs ? '⌘' : 'Ctrl'; +const KeyMap = structuredClone(KEY_MAP); -const SHORT_KEYS = Object.entries({ - 'Search in editor': [modifier, 'F'], - 'Search in documentation': [modifier, 'K'], - 'Execute query': [modifier, 'Enter'], - 'Prettify editors': KEY_MAP.prettify[0].split('-'), +if (isMacOs) { + for (const key of ['searchInEditor', 'searchInDocs', 'runQuery']) { + KeyMap[key][0] = KeyMap[key][0].replace('Ctrl', '⌘'); + } +} + +const SHORT_KEYS: [string, string[]][] = Object.entries({ + 'Search in editor': KeyMap.searchInEditor, + 'Search in documentation': KeyMap.searchInDocs, + 'Execute query': KeyMap.runQuery, + 'Prettify editors': KeyMap.prettify, 'Merge fragments definitions into operation definition': - KEY_MAP.mergeFragments[0].split('-'), - 'Copy query': KEY_MAP.copyQuery[0].split('-'), - 'Re-fetch schema using introspection': KEY_MAP.refetchSchema[0].split('-'), + KeyMap.mergeFragments, + 'Copy query': KeyMap.copyQuery, + 'Re-fetch schema using introspection': KeyMap.refetchSchema, }); interface ShortKeysProps { @@ -825,7 +831,7 @@ const ShortKeys: FC = ({ keyMap = 'sublime' }) => { {SHORT_KEYS.map(([title, keys]) => ( - {keys.map((key, index, array) => ( + {keys[0].split('-').map((key, index, array) => ( {key} {index !== array.length - 1 && ' + '} From a7ceaa8ed5a28156c6e59c888715f0e86b16a1ba Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:03:49 +0200 Subject: [PATCH 071/255] upd --- .../graphiql-react/src/editor/context.tsx | 20 ++++++------------- packages/graphiql-react/src/storage.tsx | 9 +++++++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index 9101fd27029..f4e35052f91 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -9,7 +9,7 @@ import { print, } from 'graphql'; import { VariableToType } from 'graphql-language-service'; -import { FC, ReactElement, ReactNode, useEffect, useRef } from 'react'; +import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { MaybePromise } from '@graphiql/toolkit'; import { storageStore, useStorage } from '../storage'; @@ -500,14 +500,7 @@ export const EditorContextProvider: FC = ({ return map; })(); - const initialRendered = useRef(false); - useEffect(() => { - if (initialRendered.current) { - return; - } - initialRendered.current = true; - // We only need to compute it lazily during the initial render. const query = props.query ?? storage.get(STORAGE_KEY_QUERY) ?? null; const variables = @@ -515,7 +508,7 @@ export const EditorContextProvider: FC = ({ const headers = props.headers ?? storage.get(STORAGE_KEY_HEADERS) ?? null; const response = props.response ?? ''; - const tabState = getDefaultTabState({ + const { tabs, activeTabIndex } = getDefaultTabState({ query, variables, headers, @@ -524,7 +517,7 @@ export const EditorContextProvider: FC = ({ defaultHeaders, shouldPersistHeaders, }); - storeTabs(tabState); + storeTabs({ tabs, activeTabIndex }); const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; @@ -535,11 +528,10 @@ export const EditorContextProvider: FC = ({ editorStore.setState({ shouldPersistHeaders: $shouldPersistHeaders, - ...tabState, + tabs, + activeTabIndex, initialQuery: - query ?? - (tabState.activeTabIndex === 0 ? tabState.tabs[0].query : null) ?? - '', + query ?? (activeTabIndex === 0 ? tabs[0].query : null) ?? '', initialVariables: variables ?? '', initialHeaders: headers ?? defaultHeaders ?? '', initialResponse: response, diff --git a/packages/graphiql-react/src/storage.tsx b/packages/graphiql-react/src/storage.tsx index d238419e8f9..6defafdaf50 100644 --- a/packages/graphiql-react/src/storage.tsx +++ b/packages/graphiql-react/src/storage.tsx @@ -27,13 +27,18 @@ export const StorageContextProvider: FC = ({ storage, children, }) => { - const $storage = useStorage(); + const isMounted = useStorageStore(store => Boolean(store.storage)); useEffect(() => { storageStore.setState({ storage: new StorageAPI(storage) }); }, [storage]); - return $storage && (children as ReactElement); + if (!isMounted) { + // Ensure storage was initialized + return null; + } + + return children as ReactElement; }; const useStorageStore = createBoundedUseStore(storageStore); From 2bc1a06f43fd9712f7c5e8d6a780d9554ebc0db1 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:06:05 +0200 Subject: [PATCH 072/255] upd --- packages/graphiql-react/src/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index 899aa5ae1d0..0e91c5e1889 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -11,8 +11,8 @@ export const KEY_MAP = Object.freeze({ export const DEFAULT_QUERY = `# Welcome to GraphiQL # -# GraphiQL is an in-browser tool for writing, validating, and -# testing GraphQL queries. +# GraphiQL is an in-browser tool for writing, validating, and testing +# GraphQL queries. # # Type queries into this side of the screen, and you will see intelligent # typeaheads aware of the current GraphQL type schema and live syntax and From 04747d34560b597698d61611aec02bcc47c25c54 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:11:09 +0200 Subject: [PATCH 073/255] upd --- packages/graphiql-react/src/toolbar/execute.tsx | 3 ++- packages/graphiql/src/GraphiQL.tsx | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/toolbar/execute.tsx b/packages/graphiql-react/src/toolbar/execute.tsx index 9addafbb2a9..9cd9c2bb87a 100644 --- a/packages/graphiql-react/src/toolbar/execute.tsx +++ b/packages/graphiql-react/src/toolbar/execute.tsx @@ -3,6 +3,7 @@ import { useEditorStore } from '../editor'; import { useExecutionStore } from '../execution'; import { PlayIcon, StopIcon } from '../icons'; import { DropdownMenu, Tooltip } from '../ui'; +import { KEY_MAP } from '../constants'; import './execute.css'; export const ExecuteButton: FC = () => { @@ -14,7 +15,7 @@ export const ExecuteButton: FC = () => { const hasOptions = operations.length > 1 && typeof operationName !== 'string'; const isRunning = isFetching || Boolean(subscription); - const label = `${isRunning ? 'Stop' : 'Execute'} query (Ctrl-Enter)`; + const label = `${isRunning ? 'Stop' : 'Execute'} query (${KEY_MAP.runQuery[0]})`; const buttonProps = { type: 'button' as const, className: 'graphiql-execute-button', diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index de286f3ab9b..3ffafc9bc51 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -906,7 +906,7 @@ const GraphiQLToolbar: FC<{ const prettify = ( @@ -915,14 +915,17 @@ const GraphiQLToolbar: FC<{ const merge = ( ); const copy = ( - + ); From ef835fb2738748831e3c2b9a7147f370dbe71547 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:11:57 +0200 Subject: [PATCH 074/255] upd --- packages/graphiql-react/src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/constants.ts b/packages/graphiql-react/src/constants.ts index 0e91c5e1889..c51034910b2 100644 --- a/packages/graphiql-react/src/constants.ts +++ b/packages/graphiql-react/src/constants.ts @@ -7,7 +7,7 @@ export const KEY_MAP = Object.freeze({ refetchSchema: ['Shift-Ctrl-R'], searchInEditor: ['Ctrl-F'], searchInDocs: ['Ctrl-K'], -}); +} as const); export const DEFAULT_QUERY = `# Welcome to GraphiQL # From 6bcc55d2436b849eac9a3d1e1330e40319b3d90c Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:14:22 +0200 Subject: [PATCH 075/255] upd --- packages/graphiql/src/GraphiQL.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 3ffafc9bc51..394f8b5fb80 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -793,15 +793,16 @@ export const GraphiQLInterface: FC = ({ ); }; -const KeyMap = structuredClone(KEY_MAP); +const KeyMap: typeof KEY_MAP = structuredClone(KEY_MAP); if (isMacOs) { for (const key of ['searchInEditor', 'searchInDocs', 'runQuery']) { + // @ts-expect-error -- fixme KeyMap[key][0] = KeyMap[key][0].replace('Ctrl', '⌘'); } } -const SHORT_KEYS: [string, string[]][] = Object.entries({ +const SHORT_KEYS: [string, readonly string[]][] = Object.entries({ 'Search in editor': KeyMap.searchInEditor, 'Search in documentation': KeyMap.searchInDocs, 'Execute query': KeyMap.runQuery, @@ -915,7 +916,7 @@ const GraphiQLToolbar: FC<{ const merge = ( From 6682497d2945646105b8c0788c543307ed7a6f79 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:15:15 +0200 Subject: [PATCH 076/255] upd --- packages/graphiql/src/GraphiQL.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 394f8b5fb80..79b771762c4 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -334,20 +334,20 @@ export const GraphiQLInterface: FC = ({ }, ); - const onClickReference = () => { + function onClickReference() { if (pluginResize.hiddenElement === 'first') { pluginResize.setHiddenElement(null); } - }; + } - const handleClearData = () => { + function handleClearData() { try { storageContext.clear(); setClearStorageStatus('success'); } catch { setClearStorageStatus('error'); } - }; + } const handlePersistHeaders: MouseEventHandler = event => { setShouldPersistHeaders(event.currentTarget.dataset.value === 'true'); From 1fe7eaf5d6c7b4a710d23a7991d125d5019319fd Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:17:06 +0200 Subject: [PATCH 077/255] upd --- packages/graphiql/src/GraphiQL.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 79b771762c4..cd7c4aedab2 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -349,11 +349,13 @@ export const GraphiQLInterface: FC = ({ } } - const handlePersistHeaders: MouseEventHandler = event => { + type ButtonHandler = MouseEventHandler; + + const handlePersistHeaders: ButtonHandler = event => { setShouldPersistHeaders(event.currentTarget.dataset.value === 'true'); }; - const handleChangeTheme: MouseEventHandler = event => { + const handleChangeTheme: ButtonHandler = event => { const selectedTheme = event.currentTarget.dataset.theme as | 'light' | 'dark' @@ -361,13 +363,13 @@ export const GraphiQLInterface: FC = ({ setTheme(selectedTheme || null); }; - const handleShowDialog: MouseEventHandler = event => { + const handleShowDialog: ButtonHandler = event => { setShowDialog( event.currentTarget.dataset.value as 'short-keys' | 'settings', ); }; - const handlePluginClick: MouseEventHandler = event => { + const handlePluginClick: ButtonHandler = event => { const pluginIndex = Number(event.currentTarget.dataset.index!); const plugin = plugins.find((_, index) => pluginIndex === index)!; const isVisible = plugin === visiblePlugin; @@ -380,7 +382,7 @@ export const GraphiQLInterface: FC = ({ } }; - const handleToolsTabClick: MouseEventHandler = event => { + const handleToolsTabClick: ButtonHandler = event => { if (editorToolsResize.hiddenElement === 'second') { editorToolsResize.setHiddenElement(null); } @@ -389,26 +391,26 @@ export const GraphiQLInterface: FC = ({ ); }; - const toggleEditorTools: MouseEventHandler = () => { + const toggleEditorTools: ButtonHandler = () => { editorToolsResize.setHiddenElement( editorToolsResize.hiddenElement === 'second' ? null : 'second', ); }; - const handleOpenShortKeysDialog = (isOpen: boolean) => { + function handleOpenShortKeysDialog(isOpen: boolean) { if (!isOpen) { setShowDialog(null); } - }; + } - const handleOpenSettingsDialog = (isOpen: boolean) => { + function handleOpenSettingsDialog(isOpen: boolean) { if (!isOpen) { setShowDialog(null); setClearStorageStatus(null); } - }; + } - const handleTabClose: MouseEventHandler = async event => { + const handleTabClose: ButtonHandler = async event => { const tabButton = event.currentTarget.previousSibling as HTMLButtonElement; const index = Number(tabButton.id.replace(TAB_CLASS_PREFIX, '')); const shouldCloseTab = confirmCloseTab @@ -421,7 +423,7 @@ export const GraphiQLInterface: FC = ({ closeTab(index); }; - const handleTabClick: MouseEventHandler = event => { + const handleTabClick: ButtonHandler = event => { const index = Number(event.currentTarget.id.replace(TAB_CLASS_PREFIX, '')); changeTab(index); }; From 7e85d71af004b1345825ce039fbc5506cdd09666 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:17:24 +0200 Subject: [PATCH 078/255] upd --- packages/graphiql/src/GraphiQL.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index cd7c4aedab2..0649b702182 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -207,6 +207,8 @@ const THEMES = ['light', 'dark', 'system'] as const; const TAB_CLASS_PREFIX = 'graphiql-session-tab-'; +type ButtonHandler = MouseEventHandler; + export const GraphiQLInterface: FC = ({ forcedTheme, isHeadersEditorEnabled, @@ -349,8 +351,6 @@ export const GraphiQLInterface: FC = ({ } } - type ButtonHandler = MouseEventHandler; - const handlePersistHeaders: ButtonHandler = event => { setShouldPersistHeaders(event.currentTarget.dataset.value === 'true'); }; From 695832c705dad0ad97870c11f7225ce3c2c0d5ba Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:19:59 +0200 Subject: [PATCH 079/255] upd --- packages/graphiql/src/GraphiQL.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 0649b702182..d3d8ef8b0d4 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -898,8 +898,6 @@ const DefaultToolbarRenderProps: FC<{ const GraphiQLToolbar: FC<{ children?: typeof DefaultToolbarRenderProps; }> = ({ children = DefaultToolbarRenderProps }) => { - // eslint-disable-next-line react-hooks/react-compiler - 'use no memo'; if (typeof children !== 'function') { throw new TypeError( 'The `GraphiQL.Toolbar` component requires a render prop function as its child.', From c062b23b1dcf3bbf3e68203780569871bb996bf1 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:20:36 +0200 Subject: [PATCH 080/255] upd --- packages/graphiql/src/GraphiQL.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index d3d8ef8b0d4..c22eeb908c3 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -119,7 +119,7 @@ const GraphiQL_: FC = ({ ); } const interfaceProps: GraphiQLInterfaceProps = { - // TODO check if `showPersistHeadersSettings` is needed + // TODO check if `showPersistHeadersSettings` prop is needed, or we can just use `shouldPersistHeaders` instead showPersistHeadersSettings: showPersistHeadersSettings ?? props.shouldPersistHeaders !== false, editorTheme, From 9b3c029f4f67937455225ef97c718e946303d9e7 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:32:21 +0200 Subject: [PATCH 081/255] upd --- packages/graphiql/src/GraphiQL.tsx | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index c22eeb908c3..3bc067d304a 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -795,16 +795,17 @@ export const GraphiQLInterface: FC = ({ ); }; -const KeyMap: typeof KEY_MAP = structuredClone(KEY_MAP); - -if (isMacOs) { - for (const key of ['searchInEditor', 'searchInDocs', 'runQuery']) { - // @ts-expect-error -- fixme - KeyMap[key][0] = KeyMap[key][0].replace('Ctrl', '⌘'); - } -} - -const SHORT_KEYS: [string, readonly string[]][] = Object.entries({ +const KeyMap = Object.fromEntries( + Object.entries(KEY_MAP).map(([key, [cmd]]) => { + const value = + isMacOs && ['searchInEditor', 'searchInDocs', 'runQuery'].includes(key) + ? cmd.replace('Ctrl', '⌘') + : cmd; + return [key, value]; + }), +) as Record; + +const SHORT_KEYS = Object.entries({ 'Search in editor': KeyMap.searchInEditor, 'Search in documentation': KeyMap.searchInDocs, 'Execute query': KeyMap.runQuery, @@ -834,7 +835,7 @@ const ShortKeys: FC = ({ keyMap = 'sublime' }) => { {SHORT_KEYS.map(([title, keys]) => ( - {keys[0].split('-').map((key, index, array) => ( + {keys.split('-').map((key, index, array) => ( {key} {index !== array.length - 1 && ' + '} @@ -907,7 +908,7 @@ const GraphiQLToolbar: FC<{ const prettify = ( @@ -916,7 +917,7 @@ const GraphiQLToolbar: FC<{ const merge = ( @@ -925,7 +926,7 @@ const GraphiQLToolbar: FC<{ const copy = ( From 43e83d0a9b65c000b6563b706187f0ca251c51ef Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:32:46 +0200 Subject: [PATCH 082/255] upd --- packages/graphiql/src/GraphiQL.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 3bc067d304a..4f6fd0188fb 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -796,11 +796,11 @@ export const GraphiQLInterface: FC = ({ }; const KeyMap = Object.fromEntries( - Object.entries(KEY_MAP).map(([key, [cmd]]) => { + Object.entries(KEY_MAP).map(([key, commands]) => { const value = isMacOs && ['searchInEditor', 'searchInDocs', 'runQuery'].includes(key) - ? cmd.replace('Ctrl', '⌘') - : cmd; + ? commands[0].replace('Ctrl', '⌘') + : commands[0]; return [key, value]; }), ) as Record; From efa078af7483c75b7a521e2573d94a104c04cbb3 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 19:33:33 +0200 Subject: [PATCH 083/255] upd --- packages/graphiql/src/GraphiQL.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 4f6fd0188fb..a71e961dcaa 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -796,13 +796,16 @@ export const GraphiQLInterface: FC = ({ }; const KeyMap = Object.fromEntries( - Object.entries(KEY_MAP).map(([key, commands]) => { - const value = - isMacOs && ['searchInEditor', 'searchInDocs', 'runQuery'].includes(key) - ? commands[0].replace('Ctrl', '⌘') - : commands[0]; - return [key, value]; - }), + Object.entries(KEY_MAP).map( + // @ts-expect-error -- fixme + ([key, [cmd]]) => { + const value = + isMacOs && ['searchInEditor', 'searchInDocs', 'runQuery'].includes(key) + ? cmd.replace('Ctrl', '⌘') + : cmd; + return [key, value]; + }, + ), ) as Record; const SHORT_KEYS = Object.entries({ From a7d21a22e39f4ae124a786876a0a22f3e4c2c069 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 23:02:53 +0200 Subject: [PATCH 084/255] upd --- packages/graphiql-react/src/index.ts | 2 +- packages/graphiql/src/GraphiQL.tsx | 35 +++++++++++----------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 24954c53147..2cddfe7a848 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -50,4 +50,4 @@ export type { GraphiQLPlugin, PluginContextType } from './plugin'; export type { SchemaContextType } from './schema'; export type { Theme } from './theme'; export { clsx as cn } from 'clsx'; -export { KEY_MAP } from './constants' +export { KEY_MAP } from './constants'; diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index a71e961dcaa..806dd88db46 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -795,28 +795,19 @@ export const GraphiQLInterface: FC = ({ ); }; -const KeyMap = Object.fromEntries( - Object.entries(KEY_MAP).map( - // @ts-expect-error -- fixme - ([key, [cmd]]) => { - const value = - isMacOs && ['searchInEditor', 'searchInDocs', 'runQuery'].includes(key) - ? cmd.replace('Ctrl', '⌘') - : cmd; - return [key, value]; - }, - ), -) as Record; +function withMacOS(key: string) { + return isMacOs ? key.replace('Ctrl', '⌘') : key; +} const SHORT_KEYS = Object.entries({ - 'Search in editor': KeyMap.searchInEditor, - 'Search in documentation': KeyMap.searchInDocs, - 'Execute query': KeyMap.runQuery, - 'Prettify editors': KeyMap.prettify, + 'Search in editor': withMacOS(KEY_MAP.searchInEditor[0]), + 'Search in documentation': withMacOS(KEY_MAP.searchInDocs[0]), + 'Execute query': withMacOS(KEY_MAP.runQuery[0]), + 'Prettify editors': KEY_MAP.prettify[0], 'Merge fragments definitions into operation definition': - KeyMap.mergeFragments, - 'Copy query': KeyMap.copyQuery, - 'Re-fetch schema using introspection': KeyMap.refetchSchema, + KEY_MAP.mergeFragments[0], + 'Copy query': KEY_MAP.copyQuery[0], + 'Re-fetch schema using introspection': KEY_MAP.refetchSchema[0], }); interface ShortKeysProps { @@ -911,7 +902,7 @@ const GraphiQLToolbar: FC<{ const prettify = ( @@ -920,7 +911,7 @@ const GraphiQLToolbar: FC<{ const merge = ( @@ -929,7 +920,7 @@ const GraphiQLToolbar: FC<{ const copy = ( From 52079878f7a9bf6c2d839f420125af6b6799f00e Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 23:42:51 +0200 Subject: [PATCH 085/255] fixes --- .../src/__tests__/components.spec.tsx | 54 ++++++------------- packages/graphiql-react/src/execution.tsx | 1 - packages/graphiql-react/src/schema.ts | 1 - 3 files changed, 16 insertions(+), 40 deletions(-) diff --git a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx index d0d913bb2ed..f1ae6e44167 100644 --- a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx +++ b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx @@ -1,34 +1,9 @@ -import type { Mock } from 'vitest'; import { fireEvent, render } from '@testing-library/react'; import type { ComponentProps } from 'react'; import { formatQuery, HistoryItem } from '../components'; import { HistoryContextProvider } from '../context'; -import { - useEditorStore, - Tooltip, - StorageContextProvider, -} from '@graphiql/react'; - -vi.mock('@graphiql/react', async () => { - const originalModule = await vi.importActual('@graphiql/react'); - const mockedSetQueryEditor = vi.fn(); - const mockedSetVariableEditor = vi.fn(); - const mockedSetHeaderEditor = vi.fn(); - return { - ...originalModule, - useEditorStore() { - return { - queryEditor: { setValue: mockedSetQueryEditor }, - variableEditor: { setValue: mockedSetVariableEditor }, - headerEditor: { setValue: mockedSetHeaderEditor }, - tabs: [], - }; - }, - useExecutionStore() { - return {}; - }, - }; -}); +import { Tooltip, GraphiQLProvider } from '@graphiql/react'; +import { editorStore } from '../../../graphiql-react/dist/editor/context'; const mockQuery = /* GraphQL */ ` query Test($string: String) { @@ -49,11 +24,11 @@ type QueryHistoryItemProps = ComponentProps; const QueryHistoryItemWithContext: typeof HistoryItem = props => { return ( - + - + ); }; @@ -78,15 +53,6 @@ function getMockProps( } describe('QueryHistoryItem', () => { - const store = useEditorStore(); - const mockedSetQueryEditor = store.queryEditor!.setValue as Mock; - const mockedSetVariableEditor = store.variableEditor!.setValue as Mock; - const mockedSetHeaderEditor = store.headerEditor!.setValue as Mock; - beforeEach(() => { - mockedSetQueryEditor.mockClear(); - mockedSetVariableEditor.mockClear(); - mockedSetHeaderEditor.mockClear(); - }); it('renders operationName if label is not provided', () => { const otherMockProps = { item: { operationName: mockOperationName } }; const props = getMockProps(otherMockProps); @@ -108,6 +74,18 @@ describe('QueryHistoryItem', () => { }); it('selects the item when history label button is clicked', () => { + const mockedSetQueryEditor = vi.fn(); + const mockedSetVariableEditor = vi.fn(); + const mockedSetHeaderEditor = vi.fn(); + editorStore.setState({ + // @ts-expect-error -- ignore + queryEditor: { setValue: mockedSetQueryEditor, getValue: () => '' }, + // @ts-expect-error -- ignore + variableEditor: { setValue: mockedSetVariableEditor, getValue: () => '' }, + // @ts-expect-error -- ignore + headerEditor: { setValue: mockedSetHeaderEditor, getValue: () => '' }, + }); + const otherMockProps = { item: { operationName: mockOperationName } }; const mockProps = getMockProps(otherMockProps); const { container } = render( diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index d0894464d4b..22ba79a473d 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -22,7 +22,6 @@ import getValue from 'get-value'; import { getAutoCompleteLeafs } from './editor'; import { createStore } from 'zustand'; import { editorStore } from './editor/context'; -import { schemaStore } from './schema'; import { createBoundedUseStore } from './utility'; export type ExecutionContextType = { diff --git a/packages/graphiql-react/src/schema.ts b/packages/graphiql-react/src/schema.ts index 720edfdbad8..966ec12845e 100644 --- a/packages/graphiql-react/src/schema.ts +++ b/packages/graphiql-react/src/schema.ts @@ -1,5 +1,4 @@ import { - Fetcher, FetcherOpts, fetcherReturnToPromise, formatError, From ed991731bc6b7d60e955298f05aafe8a519863af Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 23:43:56 +0200 Subject: [PATCH 086/255] fixes --- .../src/__tests__/components.spec.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx index f1ae6e44167..909e6854f09 100644 --- a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx +++ b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx @@ -78,11 +78,8 @@ describe('QueryHistoryItem', () => { const mockedSetVariableEditor = vi.fn(); const mockedSetHeaderEditor = vi.fn(); editorStore.setState({ - // @ts-expect-error -- ignore - queryEditor: { setValue: mockedSetQueryEditor, getValue: () => '' }, - // @ts-expect-error -- ignore - variableEditor: { setValue: mockedSetVariableEditor, getValue: () => '' }, - // @ts-expect-error -- ignore + queryEditor: { setValue: mockedSetQueryEditor }, + variableEditor: { setValue: mockedSetVariableEditor }, headerEditor: { setValue: mockedSetHeaderEditor, getValue: () => '' }, }); From 0747d7eda93c6ddf37e2089d813aa20e05470410 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 23:49:13 +0200 Subject: [PATCH 087/255] fixes --- .changeset/chilly-sloths-heal.md | 7 ++++++ packages/graphiql-react/src/index.ts | 35 ++++++++++++++++------------ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/.changeset/chilly-sloths-heal.md b/.changeset/chilly-sloths-heal.md index 8c223392e39..314090e9e50 100644 --- a/.changeset/chilly-sloths-heal.md +++ b/.changeset/chilly-sloths-heal.md @@ -15,3 +15,10 @@ - remove `fetcher` prop from `SchemaContextProvider` and `schemaStore` - add `fetcher` to `executionStore` - add `onCopyQuery` and `onPrettifyQuery` props to `EditorContextProvider` +- remove exports (use `GraphiQLProvider`) + - EditorContextProvider + - ExecutionContextProvider + - PluginContextProvider + - SchemaContextProvider + - StorageContextProvider + diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 2cddfe7a848..9d8d4a1bf89 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -1,32 +1,37 @@ import './style/root.css'; export { - EditorContextProvider, - HeaderEditor, - ImagePreview, + useEditorStore, + QueryEditor, + useQueryEditor, + + VariableEditor, + useVariableEditor, + + HeaderEditor, + useHeaderEditor, + ResponseEditor, - getAutoCompleteLeafs, + useResponseEditor, + copyQuery, - useEditorStore, - useHeaderEditor, - mergeQuery, prettifyEditors, - useQueryEditor, - useResponseEditor, - useVariableEditor, + mergeQuery, + + ImagePreview, + getAutoCompleteLeafs, useEditorState, useOperationsEditorState, useOptimisticState, useVariablesEditorState, useHeadersEditorState, - VariableEditor, } from './editor'; -export { ExecutionContextProvider, useExecutionStore } from './execution'; -export { PluginContextProvider, usePluginStore } from './plugin'; +export { useExecutionStore } from './execution'; +export { usePluginStore } from './plugin'; export { GraphiQLProvider } from './provider'; -export { SchemaContextProvider, useSchemaStore } from './schema'; -export { StorageContextProvider, useStorage } from './storage'; +export { useSchemaStore } from './schema'; +export { useStorage } from './storage'; export { useTheme } from './theme'; export * from './utility'; From f553781ca099827c24f96d8dec6b2f54f000cfbc Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 23:55:32 +0200 Subject: [PATCH 088/255] fixes --- .changeset/chilly-sloths-heal.md | 2 ++ packages/graphiql-react/src/execution.tsx | 2 +- packages/graphiql-react/src/index.ts | 15 ++++----------- packages/graphiql-react/src/plugin.tsx | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.changeset/chilly-sloths-heal.md b/.changeset/chilly-sloths-heal.md index 314090e9e50..b7d82b50216 100644 --- a/.changeset/chilly-sloths-heal.md +++ b/.changeset/chilly-sloths-heal.md @@ -21,4 +21,6 @@ - PluginContextProvider - SchemaContextProvider - StorageContextProvider + - ExecutionContextType + - PluginContextType diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/execution.tsx index 22ba79a473d..fde831955ae 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/execution.tsx @@ -24,7 +24,7 @@ import { createStore } from 'zustand'; import { editorStore } from './editor/context'; import { createBoundedUseStore } from './utility'; -export type ExecutionContextType = { +type ExecutionContextType = { /** * If there is currently a GraphQL request in-flight. For multipart * requests like subscriptions, this will be `true` while fetching the diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 9d8d4a1bf89..ac668be64d3 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -2,30 +2,24 @@ import './style/root.css'; export { useEditorStore, - QueryEditor, useQueryEditor, - + useOperationsEditorState, VariableEditor, useVariableEditor, - + useVariablesEditorState, HeaderEditor, useHeaderEditor, - + useHeadersEditorState, ResponseEditor, useResponseEditor, - copyQuery, prettifyEditors, mergeQuery, - ImagePreview, getAutoCompleteLeafs, useEditorState, - useOperationsEditorState, useOptimisticState, - useVariablesEditorState, - useHeadersEditorState, } from './editor'; export { useExecutionStore } from './execution'; export { usePluginStore } from './plugin'; @@ -50,8 +44,7 @@ export type { UseVariableEditorArgs, WriteableEditorProps, } from './editor'; -export type { ExecutionContextType } from './execution'; -export type { GraphiQLPlugin, PluginContextType } from './plugin'; +export type { GraphiQLPlugin } from './plugin'; export type { SchemaContextType } from './schema'; export type { Theme } from './theme'; export { clsx as cn } from 'clsx'; diff --git a/packages/graphiql-react/src/plugin.tsx b/packages/graphiql-react/src/plugin.tsx index 60778f830c5..6b4da222687 100644 --- a/packages/graphiql-react/src/plugin.tsx +++ b/packages/graphiql-react/src/plugin.tsx @@ -20,7 +20,7 @@ export type GraphiQLPlugin = { title: string; }; -export type PluginContextType = { +type PluginContextType = { /** * A list of all current plugins, including the built-in ones (the doc * explorer and the history). From 39242be1f2726ed9c3ba160c3c47f093ed3c752f Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Wed, 14 May 2025 23:56:26 +0200 Subject: [PATCH 089/255] fixes --- packages/graphiql-react/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index ac668be64d3..20010c6aa02 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -2,20 +2,26 @@ import './style/root.css'; export { useEditorStore, + // QueryEditor, useQueryEditor, useOperationsEditorState, + // VariableEditor, useVariableEditor, useVariablesEditorState, + // HeaderEditor, useHeaderEditor, useHeadersEditorState, + // ResponseEditor, useResponseEditor, + // copyQuery, prettifyEditors, mergeQuery, + // ImagePreview, getAutoCompleteLeafs, useEditorState, From 2ecda33bff7121dd1ea68d9b673cc1968500e947 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:18:20 +0200 Subject: [PATCH 090/255] upd --- .changeset/warm-shoes-boil.md | 6 ++++ .../src/editor/components/header-editor.tsx | 33 ------------------- .../src/editor/components/index.ts | 1 - .../{header-editor.ts => header-editor.tsx} | 20 +++++++++-- packages/graphiql-react/src/index.ts | 4 --- packages/graphiql-react/src/style/root.css | 4 +++ 6 files changed, 28 insertions(+), 40 deletions(-) create mode 100644 .changeset/warm-shoes-boil.md delete mode 100644 packages/graphiql-react/src/editor/components/header-editor.tsx rename packages/graphiql-react/src/editor/{header-editor.ts => header-editor.tsx} (88%) diff --git a/.changeset/warm-shoes-boil.md b/.changeset/warm-shoes-boil.md new file mode 100644 index 00000000000..b0d2455b2c9 --- /dev/null +++ b/.changeset/warm-shoes-boil.md @@ -0,0 +1,6 @@ +--- +'@graphiql/react': patch +'graphiql': patch +--- + +remove `useQueryEditor`, `useVariableEditor`, `useHeaderEditor`, `useResponseEditor` hooks diff --git a/packages/graphiql-react/src/editor/components/header-editor.tsx b/packages/graphiql-react/src/editor/components/header-editor.tsx deleted file mode 100644 index 3652b96f7e2..00000000000 --- a/packages/graphiql-react/src/editor/components/header-editor.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { FC, useEffect } from 'react'; -import { clsx } from 'clsx'; -import { useEditorStore } from '../context'; -import { useHeaderEditor, UseHeaderEditorArgs } from '../header-editor'; -import '../style/codemirror.css'; -import '../style/fold.css'; -import '../style/editor.css'; - -type HeaderEditorProps = UseHeaderEditorArgs & { - /** - * Visually hide the header editor. - * @default false - */ - isHidden?: boolean; -}; - -export const HeaderEditor: FC = ({ - isHidden, - ...hookArgs -}) => { - const headerEditor = useEditorStore(store => store.headerEditor); - const ref = useHeaderEditor(hookArgs); - - useEffect(() => { - if (!isHidden) { - headerEditor?.refresh(); - } - }, [headerEditor, isHidden]); - - return ( -
- ); -}; diff --git a/packages/graphiql-react/src/editor/components/index.ts b/packages/graphiql-react/src/editor/components/index.ts index 9fbe6db2a47..11285da8312 100644 --- a/packages/graphiql-react/src/editor/components/index.ts +++ b/packages/graphiql-react/src/editor/components/index.ts @@ -1,4 +1,3 @@ -export { HeaderEditor } from './header-editor'; export { ImagePreview } from './image-preview'; export { QueryEditor } from './query-editor'; export { ResponseEditor } from './response-editor'; diff --git a/packages/graphiql-react/src/editor/header-editor.ts b/packages/graphiql-react/src/editor/header-editor.tsx similarity index 88% rename from packages/graphiql-react/src/editor/header-editor.ts rename to packages/graphiql-react/src/editor/header-editor.tsx index f2f1c8713b0..63fa5f5f413 100644 --- a/packages/graphiql-react/src/editor/header-editor.ts +++ b/packages/graphiql-react/src/editor/header-editor.tsx @@ -17,13 +17,20 @@ import { import { WriteableEditorProps } from './types'; import { useExecutionStore } from '../execution'; import { KEY_MAP } from '../constants'; +import { clsx } from 'clsx'; -export type UseHeaderEditorArgs = WriteableEditorProps & { +type UseHeaderEditorArgs = WriteableEditorProps & { /** * Invoked when the contents of the headers editor change. * @param value The new contents of the editor. */ onEdit?(value: string): void; + + /** + * Visually hide the header editor. + * @default false + */ + isHidden?: boolean; }; // To make react-compiler happy, otherwise complains about using dynamic imports in Component @@ -39,6 +46,7 @@ export function useHeaderEditor({ keyMap = DEFAULT_KEY_MAP, onEdit, readOnly = false, + isHidden = false, }: UseHeaderEditorArgs = {}) { const { initialHeaders, @@ -119,7 +127,15 @@ export function useHeaderEditor({ useKeyMap(headerEditor, KEY_MAP.prettify, prettifyEditors); useKeyMap(headerEditor, KEY_MAP.mergeFragments, mergeQuery); - return ref; + useEffect(() => { + if (!isHidden) { + headerEditor?.refresh(); + } + }, [headerEditor, isHidden]); + + return ( +
+); } export const STORAGE_KEY = 'headers'; diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 20010c6aa02..fec7fa63c60 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -4,19 +4,15 @@ export { useEditorStore, // QueryEditor, - useQueryEditor, useOperationsEditorState, // VariableEditor, - useVariableEditor, useVariablesEditorState, // HeaderEditor, - useHeaderEditor, useHeadersEditorState, // ResponseEditor, - useResponseEditor, // copyQuery, prettifyEditors, diff --git a/packages/graphiql-react/src/style/root.css b/packages/graphiql-react/src/style/root.css index 8c8dbcfa09f..7f078d1ed3f 100644 --- a/packages/graphiql-react/src/style/root.css +++ b/packages/graphiql-react/src/style/root.css @@ -1,3 +1,7 @@ +@import '../editor/style/codemirror.css'; +@import '../editor/style/fold.css'; +@import '../editor/style/editor.css'; + /* a very simple box-model reset, intentionally does not include pseudo elements */ .graphiql-container * { box-sizing: border-box; From ad4a8a9aa7fd7e1857d0c8271caa6763e01c8e65 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:21:27 +0200 Subject: [PATCH 091/255] upd --- .../graphiql-react/src/editor/header-editor.tsx | 14 +++++--------- packages/graphiql-react/src/editor/index.ts | 4 +--- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/graphiql-react/src/editor/header-editor.tsx b/packages/graphiql-react/src/editor/header-editor.tsx index 63fa5f5f413..5aa632e13cf 100644 --- a/packages/graphiql-react/src/editor/header-editor.tsx +++ b/packages/graphiql-react/src/editor/header-editor.tsx @@ -19,7 +19,7 @@ import { useExecutionStore } from '../execution'; import { KEY_MAP } from '../constants'; import { clsx } from 'clsx'; -type UseHeaderEditorArgs = WriteableEditorProps & { +type HeaderEditorProps = WriteableEditorProps & { /** * Invoked when the contents of the headers editor change. * @param value The new contents of the editor. @@ -41,13 +41,13 @@ function importCodeMirrorImports() { ]); } -export function useHeaderEditor({ +export function HeaderEditor({ editorTheme = DEFAULT_EDITOR_THEME, keyMap = DEFAULT_KEY_MAP, onEdit, readOnly = false, isHidden = false, -}: UseHeaderEditorArgs = {}) { +}: HeaderEditorProps) { const { initialHeaders, headerEditor, @@ -55,7 +55,7 @@ export function useHeaderEditor({ shouldPersistHeaders, } = useEditorStore(); const run = useExecutionStore(store => store.run); - const ref = useRef(null); + const ref = useRef(null!); useEffect(() => { let isActive = true; @@ -67,10 +67,6 @@ export function useHeaderEditor({ } const container = ref.current; - if (!container) { - return; - } - const newEditor = CodeMirror(container, { value: initialHeaders, lineNumbers: true, @@ -135,7 +131,7 @@ export function useHeaderEditor({ return (
-); + ); } export const STORAGE_KEY = 'headers'; diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index 1c548e52080..b298df247cf 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -1,12 +1,11 @@ export { - HeaderEditor, ImagePreview, QueryEditor, ResponseEditor, VariableEditor, } from './components'; export { EditorContextProvider, useEditorStore } from './context'; -export { useHeaderEditor } from './header-editor'; +export { HeaderEditor } from './header-editor'; export { getAutoCompleteLeafs, copyQuery, @@ -22,7 +21,6 @@ export { useQueryEditor } from './query-editor'; export { useResponseEditor } from './response-editor'; export { useVariableEditor } from './variable-editor'; -export type { UseHeaderEditorArgs } from './header-editor'; export type { UseQueryEditorArgs } from './query-editor'; export type { ResponseTooltipType, From 0e8823f73a7812e1c5d8075ddb0faec21199c66e Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:23:37 +0200 Subject: [PATCH 092/255] upd --- .../src/editor/components/query-editor.tsx | 15 --------------- .../editor/{query-editor.ts => query-editor.tsx} | 2 +- packages/graphiql-react/src/style/root.css | 6 ++++++ 3 files changed, 7 insertions(+), 16 deletions(-) delete mode 100644 packages/graphiql-react/src/editor/components/query-editor.tsx rename packages/graphiql-react/src/editor/{query-editor.ts => query-editor.tsx} (99%) diff --git a/packages/graphiql-react/src/editor/components/query-editor.tsx b/packages/graphiql-react/src/editor/components/query-editor.tsx deleted file mode 100644 index c4dfead3a4f..00000000000 --- a/packages/graphiql-react/src/editor/components/query-editor.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FC } from 'react'; -import { useQueryEditor, UseQueryEditorArgs } from '../query-editor'; -import '../style/codemirror.css'; -import '../style/fold.css'; -import '../style/lint.css'; -import '../style/hint.css'; -import '../style/info.css'; -import '../style/jump.css'; -import '../style/auto-insertion.css'; -import '../style/editor.css'; - -export const QueryEditor: FC = props => { - const ref = useQueryEditor(props); - return
; -}; diff --git a/packages/graphiql-react/src/editor/query-editor.ts b/packages/graphiql-react/src/editor/query-editor.tsx similarity index 99% rename from packages/graphiql-react/src/editor/query-editor.ts rename to packages/graphiql-react/src/editor/query-editor.tsx index 04621a76091..f6726597f6c 100644 --- a/packages/graphiql-react/src/editor/query-editor.ts +++ b/packages/graphiql-react/src/editor/query-editor.tsx @@ -394,7 +394,7 @@ export function useQueryEditor({ useKeyMap(queryEditor, ['Shift-Ctrl-P', 'Shift-Ctrl-F'], prettifyEditors); useKeyMap(queryEditor, KEY_MAP.mergeFragments, mergeQuery); - return ref; + return
; } function useSynchronizeSchema( diff --git a/packages/graphiql-react/src/style/root.css b/packages/graphiql-react/src/style/root.css index 7f078d1ed3f..ec24198fff9 100644 --- a/packages/graphiql-react/src/style/root.css +++ b/packages/graphiql-react/src/style/root.css @@ -2,6 +2,12 @@ @import '../editor/style/fold.css'; @import '../editor/style/editor.css'; +@import '../editor/style/lint.css'; +@import '../editor/style/hint.css'; +@import '../editor/style/info.css'; +@import '../editor/style/jump.css'; +@import '../editor/style/auto-insertion.css'; + /* a very simple box-model reset, intentionally does not include pseudo elements */ .graphiql-container * { box-sizing: border-box; From a83cf69d0f11118af654704c5d87f30e18ec1519 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:25:24 +0200 Subject: [PATCH 093/255] upd --- packages/graphiql-react/src/editor/index.ts | 10 ++-------- .../graphiql-react/src/editor/query-editor.tsx | 14 +++++--------- packages/graphiql-react/src/index.ts | 2 -- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index b298df247cf..904e9212c9c 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -1,9 +1,4 @@ -export { - ImagePreview, - QueryEditor, - ResponseEditor, - VariableEditor, -} from './components'; +export { ImagePreview, ResponseEditor, VariableEditor } from './components'; export { EditorContextProvider, useEditorStore } from './context'; export { HeaderEditor } from './header-editor'; export { @@ -17,11 +12,10 @@ export { useVariablesEditorState, useHeadersEditorState, } from './hooks'; -export { useQueryEditor } from './query-editor'; +export { QueryEditor } from './query-editor'; export { useResponseEditor } from './response-editor'; export { useVariableEditor } from './variable-editor'; -export type { UseQueryEditorArgs } from './query-editor'; export type { ResponseTooltipType, UseResponseEditorArgs, diff --git a/packages/graphiql-react/src/editor/query-editor.tsx b/packages/graphiql-react/src/editor/query-editor.tsx index f6726597f6c..d1215a54b59 100644 --- a/packages/graphiql-react/src/editor/query-editor.tsx +++ b/packages/graphiql-react/src/editor/query-editor.tsx @@ -40,7 +40,7 @@ import { import { normalizeWhitespace } from './whitespace'; import { KEY_MAP } from '../constants'; -export type UseQueryEditorArgs = WriteableEditorProps & { +type QueryEditorProps = WriteableEditorProps & { /** * Invoked when a reference to the GraphQL schema (type or field) is clicked * as part of the editor or one of its tooltips. @@ -108,13 +108,13 @@ function updateEditorExternalFragments( editor.options.hintOptions.externalFragments = externalFragmentList; } -export function useQueryEditor({ +export function QueryEditor({ editorTheme = DEFAULT_EDITOR_THEME, keyMap = DEFAULT_KEY_MAP, onClickReference, onEdit, readOnly = false, -}: UseQueryEditorArgs = {}) { +}: QueryEditorProps) { const { initialQuery, queryEditor, @@ -124,11 +124,11 @@ export function useQueryEditor({ updateActiveTabValues, } = useEditorStore(); const storage = useStorage(); - const ref = useRef(null); + const ref = useRef(null!); const codeMirrorRef = useRef(undefined); const onClickReferenceRef = useRef< - NonNullable + NonNullable >(() => {}); useEffect(() => { @@ -156,10 +156,6 @@ export function useQueryEditor({ codeMirrorRef.current = CodeMirror; const container = ref.current; - if (!container) { - return; - } - const newEditor = CodeMirror(container, { value: initialQuery, lineNumbers: true, diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index fec7fa63c60..508d231591f 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -40,8 +40,6 @@ export type { KeyMap, ResponseTooltipType, TabsState, - UseHeaderEditorArgs, - UseQueryEditorArgs, UseResponseEditorArgs, UseVariableEditorArgs, WriteableEditorProps, From 62ea76e22ccd3696b83618f5e1805ea8a04aee81 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:28:27 +0200 Subject: [PATCH 094/255] upd --- .../src/editor/components/index.ts | 2 - .../src/editor/components/variable-editor.tsx | 37 ------------------- ...variable-editor.ts => variable-editor.tsx} | 25 +++++++++---- 3 files changed, 18 insertions(+), 46 deletions(-) delete mode 100644 packages/graphiql-react/src/editor/components/variable-editor.tsx rename packages/graphiql-react/src/editor/{variable-editor.ts => variable-editor.tsx} (90%) diff --git a/packages/graphiql-react/src/editor/components/index.ts b/packages/graphiql-react/src/editor/components/index.ts index 11285da8312..d154e3e8202 100644 --- a/packages/graphiql-react/src/editor/components/index.ts +++ b/packages/graphiql-react/src/editor/components/index.ts @@ -1,4 +1,2 @@ export { ImagePreview } from './image-preview'; -export { QueryEditor } from './query-editor'; export { ResponseEditor } from './response-editor'; -export { VariableEditor } from './variable-editor'; diff --git a/packages/graphiql-react/src/editor/components/variable-editor.tsx b/packages/graphiql-react/src/editor/components/variable-editor.tsx deleted file mode 100644 index 6abb67ebba5..00000000000 --- a/packages/graphiql-react/src/editor/components/variable-editor.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { FC, useEffect } from 'react'; -import { clsx } from 'clsx'; - -import { useEditorStore } from '../context'; -import { useVariableEditor, UseVariableEditorArgs } from '../variable-editor'; - -import '../style/codemirror.css'; -import '../style/fold.css'; -import '../style/lint.css'; -import '../style/hint.css'; -import '../style/editor.css'; - -type VariableEditorProps = UseVariableEditorArgs & { - /** - * Visually hide the header editor. - * @default false - */ - isHidden?: boolean; -}; - -export const VariableEditor: FC = ({ - isHidden, - ...hookArgs -}) => { - const variableEditor = useEditorStore(store => store.variableEditor); - const ref = useVariableEditor(hookArgs); - - useEffect(() => { - if (!isHidden) { - variableEditor?.refresh(); - } - }, [variableEditor, isHidden]); - - return ( -
- ); -}; diff --git a/packages/graphiql-react/src/editor/variable-editor.ts b/packages/graphiql-react/src/editor/variable-editor.tsx similarity index 90% rename from packages/graphiql-react/src/editor/variable-editor.ts rename to packages/graphiql-react/src/editor/variable-editor.tsx index 55ce95c2126..aae531845d7 100644 --- a/packages/graphiql-react/src/editor/variable-editor.ts +++ b/packages/graphiql-react/src/editor/variable-editor.tsx @@ -19,6 +19,7 @@ import { } from './hooks'; import { WriteableEditorProps } from './types'; import { KEY_MAP } from '../constants'; +import { clsx } from 'clsx'; export type UseVariableEditorArgs = WriteableEditorProps & { /** @@ -32,6 +33,11 @@ export type UseVariableEditorArgs = WriteableEditorProps & { * @param value The new contents of the editor. */ onEdit?(value: string): void; + /** + * Visually hide the header editor. + * @default false + */ + isHidden?: boolean; }; // To make react-compiler happy, otherwise complains about using dynamic imports in Component @@ -49,11 +55,12 @@ export function useVariableEditor({ onClickReference, onEdit, readOnly = false, -}: UseVariableEditorArgs = {}) { + isHidden = false +}: UseVariableEditorArgs) { const { initialVariables, variableEditor, setVariableEditor } = useEditorStore(); const run = useExecutionStore(store => store.run); - const ref = useRef(null); + const ref = useRef(null!); useEffect(() => { let isActive = true; @@ -63,10 +70,6 @@ export function useVariableEditor({ return; } const container = ref.current; - if (!container) { - return; - } - const newEditor = CodeMirror(container, { value: initialVariables, lineNumbers: true, @@ -130,7 +133,15 @@ export function useVariableEditor({ useKeyMap(variableEditor, KEY_MAP.prettify, prettifyEditors); useKeyMap(variableEditor, KEY_MAP.mergeFragments, mergeQuery); - return ref; + useEffect(() => { + if (!isHidden) { + variableEditor?.refresh(); + } + }, [variableEditor, isHidden]); + + return ( +
+ ); } export const STORAGE_KEY = 'variables'; From 796aa2091dfd3f717665e76854201689089adb2a Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:30:02 +0200 Subject: [PATCH 095/255] upd --- packages/graphiql-react/src/editor/index.ts | 5 ++--- packages/graphiql-react/src/editor/variable-editor.tsx | 9 +++++---- packages/graphiql-react/src/index.ts | 1 - 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index 904e9212c9c..cdafceaab9d 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -1,4 +1,4 @@ -export { ImagePreview, ResponseEditor, VariableEditor } from './components'; +export { ImagePreview, ResponseEditor } from './components'; export { EditorContextProvider, useEditorStore } from './context'; export { HeaderEditor } from './header-editor'; export { @@ -14,13 +14,12 @@ export { } from './hooks'; export { QueryEditor } from './query-editor'; export { useResponseEditor } from './response-editor'; -export { useVariableEditor } from './variable-editor'; +export { VariableEditor } from './variable-editor'; export type { ResponseTooltipType, UseResponseEditorArgs, } from './response-editor'; export type { TabsState } from './tabs'; -export type { UseVariableEditorArgs } from './variable-editor'; export type { CommonEditorProps, KeyMap, WriteableEditorProps } from './types'; diff --git a/packages/graphiql-react/src/editor/variable-editor.tsx b/packages/graphiql-react/src/editor/variable-editor.tsx index aae531845d7..889dbaef294 100644 --- a/packages/graphiql-react/src/editor/variable-editor.tsx +++ b/packages/graphiql-react/src/editor/variable-editor.tsx @@ -21,7 +21,7 @@ import { WriteableEditorProps } from './types'; import { KEY_MAP } from '../constants'; import { clsx } from 'clsx'; -export type UseVariableEditorArgs = WriteableEditorProps & { +type VariableEditorProps = WriteableEditorProps & { /** * Invoked when a reference to the GraphQL schema (type or field) is clicked * as part of the editor or one of its tooltips. @@ -49,14 +49,14 @@ function importCodeMirrorImports() { ]); } -export function useVariableEditor({ +export function VariableEditor({ editorTheme = DEFAULT_EDITOR_THEME, keyMap = DEFAULT_KEY_MAP, onClickReference, onEdit, readOnly = false, - isHidden = false -}: UseVariableEditorArgs) { + isHidden = false, +}: VariableEditorProps) { const { initialVariables, variableEditor, setVariableEditor } = useEditorStore(); const run = useExecutionStore(store => store.run); @@ -95,6 +95,7 @@ export function useVariableEditor({ gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], extraKeys: commonKeys, }); + function showHint() { newEditor.showHint({ completeSingle: false, container }); } diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 508d231591f..28a37688052 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -41,7 +41,6 @@ export type { ResponseTooltipType, TabsState, UseResponseEditorArgs, - UseVariableEditorArgs, WriteableEditorProps, } from './editor'; export type { GraphiQLPlugin } from './plugin'; From ce31c60bd2705f94de53b577ce996383162ac5aa Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:31:05 +0200 Subject: [PATCH 096/255] upd --- packages/graphiql-react/src/editor/components/index.ts | 2 -- .../src/editor/{components => }/image-preview.tsx | 5 +---- packages/graphiql-react/src/editor/index.ts | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 packages/graphiql-react/src/editor/components/index.ts rename packages/graphiql-react/src/editor/{components => }/image-preview.tsx (95%) diff --git a/packages/graphiql-react/src/editor/components/index.ts b/packages/graphiql-react/src/editor/components/index.ts deleted file mode 100644 index d154e3e8202..00000000000 --- a/packages/graphiql-react/src/editor/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { ImagePreview } from './image-preview'; -export { ResponseEditor } from './response-editor'; diff --git a/packages/graphiql-react/src/editor/components/image-preview.tsx b/packages/graphiql-react/src/editor/image-preview.tsx similarity index 95% rename from packages/graphiql-react/src/editor/components/image-preview.tsx rename to packages/graphiql-react/src/editor/image-preview.tsx index 83a9c790629..3655d1d8d7b 100644 --- a/packages/graphiql-react/src/editor/components/image-preview.tsx +++ b/packages/graphiql-react/src/editor/image-preview.tsx @@ -15,14 +15,11 @@ const ImagePreview_: FC = props => { }); const [mime, setMime] = useState(null); - const ref = useRef(null); + const ref = useRef(null!); const src = tokenToURL(props.token)?.href; useEffect(() => { - if (!ref.current) { - return; - } if (!src) { setDimensions({ width: null, height: null }); setMime(null); diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index cdafceaab9d..ac394dc0588 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -1,4 +1,4 @@ -export { ImagePreview, ResponseEditor } from './components'; +export { ImagePreview } from './image-preview'; export { EditorContextProvider, useEditorStore } from './context'; export { HeaderEditor } from './header-editor'; export { From 0274e8744fa5c96c31be39c1cb48a45192aa1f21 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:33:31 +0200 Subject: [PATCH 097/255] upd --- .../src/editor/components/response-editor.tsx | 19 ------------------- packages/graphiql-react/src/editor/index.ts | 7 ++----- .../src/editor/response-editor.tsx | 16 ++++++++++++---- 3 files changed, 14 insertions(+), 28 deletions(-) delete mode 100644 packages/graphiql-react/src/editor/components/response-editor.tsx diff --git a/packages/graphiql-react/src/editor/components/response-editor.tsx b/packages/graphiql-react/src/editor/components/response-editor.tsx deleted file mode 100644 index fb7c98ecc53..00000000000 --- a/packages/graphiql-react/src/editor/components/response-editor.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useResponseEditor, UseResponseEditorArgs } from '../response-editor'; -import { FC } from 'react'; -import '../style/codemirror.css'; -import '../style/fold.css'; -import '../style/info.css'; -import '../style/editor.css'; - -export const ResponseEditor: FC = props => { - const ref = useResponseEditor(props); - return ( -
- ); -}; diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index ac394dc0588..c1639626262 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -13,13 +13,10 @@ export { useHeadersEditorState, } from './hooks'; export { QueryEditor } from './query-editor'; -export { useResponseEditor } from './response-editor'; +export { ResponseEditor } from './response-editor'; export { VariableEditor } from './variable-editor'; -export type { - ResponseTooltipType, - UseResponseEditorArgs, -} from './response-editor'; +export type { ResponseTooltipType } from './response-editor'; export type { TabsState } from './tabs'; export type { CommonEditorProps, KeyMap, WriteableEditorProps } from './types'; diff --git a/packages/graphiql-react/src/editor/response-editor.tsx b/packages/graphiql-react/src/editor/response-editor.tsx index c21bf96c643..d13152ea996 100644 --- a/packages/graphiql-react/src/editor/response-editor.tsx +++ b/packages/graphiql-react/src/editor/response-editor.tsx @@ -26,7 +26,7 @@ export type ResponseTooltipType = ComponentType<{ token: Token; }>; -export type UseResponseEditorArgs = CommonEditorProps & { +type ResponseEditorProps = CommonEditorProps & { /** * Customize the tooltip when hovering over properties in the response editor. */ @@ -52,11 +52,11 @@ function importCodeMirrorImports() { ); } -export function useResponseEditor({ +export function ResponseEditor({ responseTooltip, editorTheme = DEFAULT_EDITOR_THEME, keyMap = DEFAULT_KEY_MAP, -}: UseResponseEditorArgs = {}) { +}: ResponseEditorProps) { const { fetchError, validationErrors } = useSchemaStore(); const { initialResponse, responseEditor, setResponseEditor } = useEditorStore(); @@ -138,5 +138,13 @@ export function useResponseEditor({ } }, [responseEditor, fetchError, validationErrors]); - return ref; + return ( +
+ ); } From 65790ea0444b018d0a9812535cea3b84ddae1bd6 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:36:07 +0200 Subject: [PATCH 098/255] upd --- .../src/editor/response-editor.tsx | 8 ++------ .../src/{editor => }/style/auto-insertion.css | 0 .../src/{editor => }/style/codemirror.css | 2 +- .../src/{editor => }/style/editor.css | 0 .../src/{editor => }/style/fold.css | 0 .../src/{editor => }/style/hint.css | 0 .../src/{editor => }/style/info.css | 0 .../src/{editor => }/style/jump.css | 0 .../src/{editor => }/style/lint.css | 2 +- packages/graphiql-react/src/style/root.css | 18 +++++++++--------- packages/graphiql/README.md | 2 +- 11 files changed, 14 insertions(+), 18 deletions(-) rename packages/graphiql-react/src/{editor => }/style/auto-insertion.css (100%) rename packages/graphiql-react/src/{editor => }/style/codemirror.css (99%) rename packages/graphiql-react/src/{editor => }/style/editor.css (100%) rename packages/graphiql-react/src/{editor => }/style/fold.css (100%) rename packages/graphiql-react/src/{editor => }/style/hint.css (100%) rename packages/graphiql-react/src/{editor => }/style/info.css (100%) rename packages/graphiql-react/src/{editor => }/style/jump.css (100%) rename packages/graphiql-react/src/{editor => }/style/lint.css (99%) diff --git a/packages/graphiql-react/src/editor/response-editor.tsx b/packages/graphiql-react/src/editor/response-editor.tsx index d13152ea996..149d4e18f3c 100644 --- a/packages/graphiql-react/src/editor/response-editor.tsx +++ b/packages/graphiql-react/src/editor/response-editor.tsx @@ -10,7 +10,7 @@ import { DEFAULT_KEY_MAP, importCodeMirror, } from './common'; -import { ImagePreview } from './components'; +import { ImagePreview } from './image-preview'; import { useEditorStore } from './context'; import { useSynchronizeOption } from './hooks'; import { CodeMirrorEditor, CommonEditorProps } from './types'; @@ -60,7 +60,7 @@ export function ResponseEditor({ const { fetchError, validationErrors } = useSchemaStore(); const { initialResponse, responseEditor, setResponseEditor } = useEditorStore(); - const ref = useRef(null); + const ref = useRef(null!); const responseTooltipRef = useRef( responseTooltip, @@ -102,10 +102,6 @@ export function ResponseEditor({ ); const container = ref.current; - if (!container) { - return; - } - const newEditor = CodeMirror(container, { value: initialResponse, lineWrapping: true, diff --git a/packages/graphiql-react/src/editor/style/auto-insertion.css b/packages/graphiql-react/src/style/auto-insertion.css similarity index 100% rename from packages/graphiql-react/src/editor/style/auto-insertion.css rename to packages/graphiql-react/src/style/auto-insertion.css diff --git a/packages/graphiql-react/src/editor/style/codemirror.css b/packages/graphiql-react/src/style/codemirror.css similarity index 99% rename from packages/graphiql-react/src/editor/style/codemirror.css rename to packages/graphiql-react/src/style/codemirror.css index e1911ab2711..eb2066ef8eb 100644 --- a/packages/graphiql-react/src/editor/style/codemirror.css +++ b/packages/graphiql-react/src/style/codemirror.css @@ -168,7 +168,7 @@ .graphiql-container .cm-searching { background-color: hsla(var(--color-warning), var(--alpha-background-light)); /** - * When cycling through search results, CodeMirror overlays the current + * When cycling through search results, CodeMirror overlays the current * selection with another element that has the .CodeMirror-selected class * applied. This adds another background color (see above), but this extra * box does not quite match the height of this element. To match them up we diff --git a/packages/graphiql-react/src/editor/style/editor.css b/packages/graphiql-react/src/style/editor.css similarity index 100% rename from packages/graphiql-react/src/editor/style/editor.css rename to packages/graphiql-react/src/style/editor.css diff --git a/packages/graphiql-react/src/editor/style/fold.css b/packages/graphiql-react/src/style/fold.css similarity index 100% rename from packages/graphiql-react/src/editor/style/fold.css rename to packages/graphiql-react/src/style/fold.css diff --git a/packages/graphiql-react/src/editor/style/hint.css b/packages/graphiql-react/src/style/hint.css similarity index 100% rename from packages/graphiql-react/src/editor/style/hint.css rename to packages/graphiql-react/src/style/hint.css diff --git a/packages/graphiql-react/src/editor/style/info.css b/packages/graphiql-react/src/style/info.css similarity index 100% rename from packages/graphiql-react/src/editor/style/info.css rename to packages/graphiql-react/src/style/info.css diff --git a/packages/graphiql-react/src/editor/style/jump.css b/packages/graphiql-react/src/style/jump.css similarity index 100% rename from packages/graphiql-react/src/editor/style/jump.css rename to packages/graphiql-react/src/style/jump.css diff --git a/packages/graphiql-react/src/editor/style/lint.css b/packages/graphiql-react/src/style/lint.css similarity index 99% rename from packages/graphiql-react/src/editor/style/lint.css rename to packages/graphiql-react/src/style/lint.css index 7dc8a064bfc..5aaa1473112 100644 --- a/packages/graphiql-react/src/editor/style/lint.css +++ b/packages/graphiql-react/src/style/lint.css @@ -5,7 +5,7 @@ .CodeMirror-lint-mark-warning { background-repeat: repeat-x; /** - * The following two are very specific to the font size, so we use + * The following two are very specific to the font size, so we use * "magic values" instead of variables. */ background-size: 10px 3px; diff --git a/packages/graphiql-react/src/style/root.css b/packages/graphiql-react/src/style/root.css index ec24198fff9..ff1828fb554 100644 --- a/packages/graphiql-react/src/style/root.css +++ b/packages/graphiql-react/src/style/root.css @@ -1,12 +1,12 @@ -@import '../editor/style/codemirror.css'; -@import '../editor/style/fold.css'; -@import '../editor/style/editor.css'; - -@import '../editor/style/lint.css'; -@import '../editor/style/hint.css'; -@import '../editor/style/info.css'; -@import '../editor/style/jump.css'; -@import '../editor/style/auto-insertion.css'; +@import 'codemirror.css'; +@import 'fold.css'; +@import 'editor.css'; + +@import 'lint.css'; +@import 'hint.css'; +@import 'info.css'; +@import 'jump.css'; +@import 'auto-insertion.css'; /* a very simple box-model reset, intentionally does not include pseudo elements */ .graphiql-container * { diff --git a/packages/graphiql/README.md b/packages/graphiql/README.md index d851dfee0c9..73f60e89d81 100644 --- a/packages/graphiql/README.md +++ b/packages/graphiql/README.md @@ -202,4 +202,4 @@ has to be loaded for the theme prop to work. You can also create your own theme in CSS. As a reference, the default `graphiql` theme definition can be found -[here](../graphiql-react/src/editor/style/codemirror.css). +[here](../graphiql-react/src/style/codemirror.css). From 78fc130efcd15f24df391885f38d83d09260574e Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:37:17 +0200 Subject: [PATCH 099/255] upd --- packages/graphiql-react/src/editor/index.ts | 12 +++++------- packages/graphiql-react/src/index.ts | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index c1639626262..f3e24c0220c 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -1,6 +1,10 @@ +export { HeaderEditor } from './header-editor'; +export { QueryEditor } from './query-editor'; +export { ResponseEditor, type ResponseTooltipType } from './response-editor'; +export { VariableEditor } from './variable-editor'; + export { ImagePreview } from './image-preview'; export { EditorContextProvider, useEditorStore } from './context'; -export { HeaderEditor } from './header-editor'; export { getAutoCompleteLeafs, copyQuery, @@ -12,11 +16,5 @@ export { useVariablesEditorState, useHeadersEditorState, } from './hooks'; -export { QueryEditor } from './query-editor'; -export { ResponseEditor } from './response-editor'; -export { VariableEditor } from './variable-editor'; - -export type { ResponseTooltipType } from './response-editor'; export type { TabsState } from './tabs'; - export type { CommonEditorProps, KeyMap, WriteableEditorProps } from './types'; diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index 28a37688052..a165815649f 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -40,7 +40,6 @@ export type { KeyMap, ResponseTooltipType, TabsState, - UseResponseEditorArgs, WriteableEditorProps, } from './editor'; export type { GraphiQLPlugin } from './plugin'; From 6a14d7e4852efacc87a307cd2502651a69fb8009 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:38:40 +0200 Subject: [PATCH 100/255] upd --- packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts | 2 +- packages/graphiql-react/src/editor/query-editor.tsx | 2 +- packages/graphiql-react/src/{editor => utility}/whitespace.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/graphiql-react/src/{editor => utility}/whitespace.ts (100%) diff --git a/packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts b/packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts index 4a44b6aab21..21e06a97d53 100644 --- a/packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts +++ b/packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts @@ -1,4 +1,4 @@ -import { invalidCharacters, normalizeWhitespace } from '../whitespace'; +import { invalidCharacters, normalizeWhitespace } from '../../utility/whitespace'; describe('normalizeWhitespace', () => { it('removes unicode characters', () => { diff --git a/packages/graphiql-react/src/editor/query-editor.tsx b/packages/graphiql-react/src/editor/query-editor.tsx index d1215a54b59..a19874bee13 100644 --- a/packages/graphiql-react/src/editor/query-editor.tsx +++ b/packages/graphiql-react/src/editor/query-editor.tsx @@ -37,7 +37,7 @@ import { CodeMirrorType, WriteableEditorProps, } from './types'; -import { normalizeWhitespace } from './whitespace'; +import { normalizeWhitespace } from '../utility/whitespace'; import { KEY_MAP } from '../constants'; type QueryEditorProps = WriteableEditorProps & { diff --git a/packages/graphiql-react/src/editor/whitespace.ts b/packages/graphiql-react/src/utility/whitespace.ts similarity index 100% rename from packages/graphiql-react/src/editor/whitespace.ts rename to packages/graphiql-react/src/utility/whitespace.ts From b1ff040516925a867404966e862ae88e4521752d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:39:04 +0200 Subject: [PATCH 101/255] upd --- .../src/{editor/__tests__ => utility}/whitespace.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/graphiql-react/src/{editor/__tests__ => utility}/whitespace.spec.ts (73%) diff --git a/packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts b/packages/graphiql-react/src/utility/whitespace.spec.ts similarity index 73% rename from packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts rename to packages/graphiql-react/src/utility/whitespace.spec.ts index 21e06a97d53..41ebc49f553 100644 --- a/packages/graphiql-react/src/editor/__tests__/whitespace.spec.ts +++ b/packages/graphiql-react/src/utility/whitespace.spec.ts @@ -1,4 +1,4 @@ -import { invalidCharacters, normalizeWhitespace } from '../../utility/whitespace'; +import { invalidCharacters, normalizeWhitespace } from './whitespace'; describe('normalizeWhitespace', () => { it('removes unicode characters', () => { From e42dd2db4312c52dac10ae49af1202e9d6f62dfa Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:44:09 +0200 Subject: [PATCH 102/255] upd --- .changeset/warm-shoes-boil.md | 2 ++ packages/graphiql/src/GraphiQL.tsx | 17 +++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.changeset/warm-shoes-boil.md b/.changeset/warm-shoes-boil.md index b0d2455b2c9..b73defc07a9 100644 --- a/.changeset/warm-shoes-boil.md +++ b/.changeset/warm-shoes-boil.md @@ -4,3 +4,5 @@ --- remove `useQueryEditor`, `useVariableEditor`, `useHeaderEditor`, `useResponseEditor` hooks + +remove `UseHeaderEditorArgs`, `UseQueryEditorArgs`, `UseResponseEditorArgs`, `UseVariableEditorArgs` exports diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 806dd88db46..c2562966ca8 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -40,16 +40,12 @@ import { useDragResize, useEditorStore, useExecutionStore, - UseHeaderEditorArgs, mergeQuery, usePluginStore, prettifyEditors, - UseQueryEditorArgs, - UseResponseEditorArgs, useSchemaStore, useStorage, useTheme, - UseVariableEditorArgs, VariableEditor, WriteableEditorProps, isMacOs, @@ -156,11 +152,16 @@ type AddSuffix, Suffix extends string> = { [Key in keyof Obj as `${string & Key}${Suffix}`]: Obj[Key]; }; +type QueryEditorProps = ComponentPropsWithoutRef; +type VariableEditorProps = ComponentPropsWithoutRef; +type HeaderEditorProps = ComponentPropsWithoutRef; +type ResponseEditorProps = ComponentPropsWithoutRef; + export type GraphiQLInterfaceProps = WriteableEditorProps & - AddSuffix, 'Query'> & - AddSuffix, 'Variables'> & - AddSuffix, 'Headers'> & - Pick & { + AddSuffix, 'Query'> & + AddSuffix, 'Variables'> & + AddSuffix, 'Headers'> & + Pick & { children?: ReactNode; /** * Set the default state for the editor tools. From 4be721b4231ebc92e71dde752a1a9d17ff6ff170 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 10:59:34 +0200 Subject: [PATCH 103/255] upd --- packages/graphiql-react/src/editor/completion.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/editor/completion.ts b/packages/graphiql-react/src/editor/completion.ts index f7330a06061..b9667277b0d 100644 --- a/packages/graphiql-react/src/editor/completion.ts +++ b/packages/graphiql-react/src/editor/completion.ts @@ -8,9 +8,8 @@ import { isNonNullType, } from 'graphql'; import { markdown } from '../utility'; -import { pluginStore } from '../plugin'; +import { pluginStore, schemaStore } from '../stores'; import { importCodeMirror } from './common'; -import { schemaStore } from '../schema'; /** * Render a custom UI for CodeMirror's hint which includes additional info From 19bf02f9db6d185ee14e32c287a6af4aac7db470 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:00:02 +0200 Subject: [PATCH 104/255] upd --- packages/graphiql-react/src/editor/context.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/editor/context.tsx index f4e35052f91..87d503afb12 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/editor/context.tsx @@ -12,7 +12,7 @@ import { VariableToType } from 'graphql-language-service'; import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { MaybePromise } from '@graphiql/toolkit'; -import { storageStore, useStorage } from '../storage'; +import { storageStore, useStorage, executionStore } from '../stores'; import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor'; import { useSynchronizeValue } from './hooks'; import { STORAGE_KEY_QUERY } from './query-editor'; @@ -35,7 +35,6 @@ import { STORAGE_KEY as STORAGE_KEY_VARIABLES } from './variable-editor'; import { DEFAULT_QUERY } from '../constants'; import { createStore } from 'zustand'; import { createBoundedUseStore } from '../utility'; -import { executionStore } from '../execution'; export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { documentAST: DocumentNode | null; From b08df3cc9df17ce4bf1d6d43a8c8b240eaa06a50 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:00:28 +0200 Subject: [PATCH 105/255] upd --- packages/graphiql-react/src/toolbar/execute.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/toolbar/execute.tsx b/packages/graphiql-react/src/toolbar/execute.tsx index 9cd9c2bb87a..8482fed13b6 100644 --- a/packages/graphiql-react/src/toolbar/execute.tsx +++ b/packages/graphiql-react/src/toolbar/execute.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import { useEditorStore } from '../editor'; -import { useExecutionStore } from '../execution'; +import { useExecutionStore } from '../stores'; import { PlayIcon, StopIcon } from '../icons'; import { DropdownMenu, Tooltip } from '../ui'; import { KEY_MAP } from '../constants'; From f43edcc92a27ea195cac8b531b8d4f448284b8ee Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:01:53 +0200 Subject: [PATCH 106/255] upd --- packages/graphiql-react/src/stores/index.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/graphiql-react/src/stores/index.ts diff --git a/packages/graphiql-react/src/stores/index.ts b/packages/graphiql-react/src/stores/index.ts new file mode 100644 index 00000000000..bbd52d92e5c --- /dev/null +++ b/packages/graphiql-react/src/stores/index.ts @@ -0,0 +1,5 @@ +export { editorStore } from './editor'; +export { executionStore, useExecutionStore } from './execution'; +export { pluginStore } from './plugin'; +export { schemaStore } from './schema'; +export { storageStore, useStorage } from './storage'; From 9222dbf5503c4dee777b537004a3aa08a2c77c9b Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:02:06 +0200 Subject: [PATCH 107/255] upd --- packages/graphiql-react/src/editor/__tests__/tabs.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/editor/__tests__/tabs.spec.ts b/packages/graphiql-react/src/editor/__tests__/tabs.spec.ts index 5829be487ee..0f14810fc96 100644 --- a/packages/graphiql-react/src/editor/__tests__/tabs.spec.ts +++ b/packages/graphiql-react/src/editor/__tests__/tabs.spec.ts @@ -7,7 +7,7 @@ import { clearHeadersFromTabs, STORAGE_KEY, } from '../tabs'; -import { storageStore } from '../../storage'; +import { storageStore } from '../../stores/storage'; describe('createTab', () => { it('creates with default title', () => { From 22693e71b8c090b146c24dbda4354c7935cedff5 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:02:13 +0200 Subject: [PATCH 108/255] upd --- packages/graphiql-react/src/{ => stores}/storage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/graphiql-react/src/{ => stores}/storage.tsx (95%) diff --git a/packages/graphiql-react/src/storage.tsx b/packages/graphiql-react/src/stores/storage.tsx similarity index 95% rename from packages/graphiql-react/src/storage.tsx rename to packages/graphiql-react/src/stores/storage.tsx index 6defafdaf50..f3b58edd7b3 100644 --- a/packages/graphiql-react/src/storage.tsx +++ b/packages/graphiql-react/src/stores/storage.tsx @@ -2,7 +2,7 @@ import { Storage, StorageAPI } from '@graphiql/toolkit'; import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore } from 'zustand'; -import { createBoundedUseStore } from './utility'; +import { createBoundedUseStore } from '../utility'; type StorageContextType = { storage: StorageAPI; From c1b09c8866fb3fef6ee05516b03bdc72e307f304 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:02:54 +0200 Subject: [PATCH 109/255] upd --- packages/graphiql-react/src/{ => stores}/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/graphiql-react/src/{ => stores}/schema.ts (99%) diff --git a/packages/graphiql-react/src/schema.ts b/packages/graphiql-react/src/stores/schema.ts similarity index 99% rename from packages/graphiql-react/src/schema.ts rename to packages/graphiql-react/src/stores/schema.ts index 966ec12845e..abb2ed0a850 100644 --- a/packages/graphiql-react/src/schema.ts +++ b/packages/graphiql-react/src/stores/schema.ts @@ -18,7 +18,7 @@ import { Dispatch, FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore } from 'zustand'; import { useEditorStore } from './editor'; import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; -import { createBoundedUseStore } from './utility'; +import { createBoundedUseStore } from '../utility'; import { executionStore, useExecutionStore } from './execution'; type MaybeGraphQLSchema = GraphQLSchema | null | undefined; From e8008ae4a9376912478a03bdf6a64358234510fb Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:03:00 +0200 Subject: [PATCH 110/255] upd --- packages/graphiql-react/src/{ => stores}/plugin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/graphiql-react/src/{ => stores}/plugin.tsx (98%) diff --git a/packages/graphiql-react/src/plugin.tsx b/packages/graphiql-react/src/stores/plugin.tsx similarity index 98% rename from packages/graphiql-react/src/plugin.tsx rename to packages/graphiql-react/src/stores/plugin.tsx index 6b4da222687..25272beb086 100644 --- a/packages/graphiql-react/src/plugin.tsx +++ b/packages/graphiql-react/src/stores/plugin.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line react/jsx-filename-extension -- TODO import { ComponentType, FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore } from 'zustand'; -import { createBoundedUseStore } from './utility'; +import { createBoundedUseStore } from '../utility'; export type GraphiQLPlugin = { /** From 00da1c89008d1bf2960d3c294bf2c832658e4843 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:03:55 +0200 Subject: [PATCH 111/255] upd --- packages/graphiql-react/src/index.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index a165815649f..b17d5208fde 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -1,8 +1,6 @@ import './style/root.css'; export { - useEditorStore, - // QueryEditor, useOperationsEditorState, // @@ -23,11 +21,14 @@ export { useEditorState, useOptimisticState, } from './editor'; -export { useExecutionStore } from './execution'; -export { usePluginStore } from './plugin'; +export { + useEditorStore, + useExecutionStore, + usePluginStore, + useSchemaStore, + useStorage, +} from './stores'; export { GraphiQLProvider } from './provider'; -export { useSchemaStore } from './schema'; -export { useStorage } from './storage'; export { useTheme } from './theme'; export * from './utility'; @@ -42,8 +43,8 @@ export type { TabsState, WriteableEditorProps, } from './editor'; -export type { GraphiQLPlugin } from './plugin'; -export type { SchemaContextType } from './schema'; +export type { GraphiQLPlugin } from './stores/plugin'; +export type { SchemaContextType } from './stores/schema'; export type { Theme } from './theme'; export { clsx as cn } from 'clsx'; export { KEY_MAP } from './constants'; From a6dda075865d472f59ba90a834ea97b4b97285b7 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:05:46 +0200 Subject: [PATCH 112/255] upd --- .../graphiql-react/src/editor/query-editor.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/graphiql-react/src/editor/query-editor.tsx b/packages/graphiql-react/src/editor/query-editor.tsx index a19874bee13..ab358b7df08 100644 --- a/packages/graphiql-react/src/editor/query-editor.tsx +++ b/packages/graphiql-react/src/editor/query-editor.tsx @@ -12,18 +12,22 @@ import { OperationFacts, } from 'graphql-language-service'; import { RefObject, useEffect, useRef } from 'react'; -import { executionStore } from '../execution'; +import { + executionStore, + pluginStore, + schemaStore, + useSchemaStore, + useEditorStore, + useStorage, + CodeMirrorEditorWithOperationFacts, +} from '../stores'; import { markdown, debounce } from '../utility'; -import { pluginStore } from '../plugin'; -import { schemaStore, useSchemaStore } from '../schema'; -import { useStorage } from '../storage'; import { commonKeys, DEFAULT_EDITOR_THEME, DEFAULT_KEY_MAP, importCodeMirror, } from './common'; -import { CodeMirrorEditorWithOperationFacts, useEditorStore } from './context'; import { useCompletion, copyQuery, From 8f92b6a9a95eca2ebff0cf6c9b74cbbd000d2491 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:05:54 +0200 Subject: [PATCH 113/255] upd --- packages/graphiql-react/src/utility/resize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/utility/resize.ts b/packages/graphiql-react/src/utility/resize.ts index 97271d59ba8..0dacb8d0cb4 100644 --- a/packages/graphiql-react/src/utility/resize.ts +++ b/packages/graphiql-react/src/utility/resize.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { useStorage } from '../storage'; +import { useStorage } from '../stores'; import { debounce } from './debounce'; type ResizableElement = 'first' | 'second'; From f899b1819c5cd7a200b317dee4d8973d50b3b295 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:06:10 +0200 Subject: [PATCH 114/255] upd --- packages/graphiql-react/src/editor/response-editor.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/editor/response-editor.tsx b/packages/graphiql-react/src/editor/response-editor.tsx index 149d4e18f3c..07f37e8a012 100644 --- a/packages/graphiql-react/src/editor/response-editor.tsx +++ b/packages/graphiql-react/src/editor/response-editor.tsx @@ -2,7 +2,7 @@ import { formatError } from '@graphiql/toolkit'; import type { Position, Token } from 'codemirror'; import { ComponentType, useEffect, useRef, JSX } from 'react'; import { createRoot } from 'react-dom/client'; -import { useSchemaStore } from '../schema'; +import { useSchemaStore, useEditorStore } from '../stores'; import { commonKeys, @@ -11,7 +11,6 @@ import { importCodeMirror, } from './common'; import { ImagePreview } from './image-preview'; -import { useEditorStore } from './context'; import { useSynchronizeOption } from './hooks'; import { CodeMirrorEditor, CommonEditorProps } from './types'; From e2ce749c2161aa55c57db7c1f6e72ee330460a89 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:06:34 +0200 Subject: [PATCH 115/255] upd --- packages/graphiql-react/src/provider.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/provider.tsx b/packages/graphiql-react/src/provider.tsx index 20290b36a8a..47949757a7b 100644 --- a/packages/graphiql-react/src/provider.tsx +++ b/packages/graphiql-react/src/provider.tsx @@ -1,10 +1,10 @@ /* eslint sort-keys: "error" */ import type { ComponentPropsWithoutRef, FC } from 'react'; import { EditorContextProvider } from './editor'; -import { ExecutionContextProvider } from './execution'; -import { PluginContextProvider } from './plugin'; -import { SchemaContextProvider } from './schema'; -import { StorageContextProvider } from './storage'; +import { ExecutionContextProvider } from './stores/execution'; +import { PluginContextProvider } from './stores/plugin'; +import { SchemaContextProvider } from './stores/schema'; +import { StorageContextProvider } from './stores/storage'; type GraphiQLProviderProps = // From b7969bc6dc88bc65bdbc710b7f946e1c8248b5e3 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:06:41 +0200 Subject: [PATCH 116/255] upd --- packages/graphiql-react/src/theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/theme.ts b/packages/graphiql-react/src/theme.ts index 6e3cda415bb..f63efaa8acb 100644 --- a/packages/graphiql-react/src/theme.ts +++ b/packages/graphiql-react/src/theme.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { useStorage } from './storage'; +import { useStorage } from './stores'; /** * The value `null` semantically means that the user does not explicitly choose From 1aab8477408c0cc397ceeaed6d3c5df3a21f890e Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:07:03 +0200 Subject: [PATCH 117/255] upd --- packages/graphiql-react/src/editor/variable-editor.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/editor/variable-editor.tsx b/packages/graphiql-react/src/editor/variable-editor.tsx index 889dbaef294..c84105d773d 100644 --- a/packages/graphiql-react/src/editor/variable-editor.tsx +++ b/packages/graphiql-react/src/editor/variable-editor.tsx @@ -1,14 +1,13 @@ import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; import { useEffect, useRef } from 'react'; -import { useExecutionStore } from '../execution'; +import { useExecutionStore, useEditorStore } from '../stores'; import { commonKeys, DEFAULT_EDITOR_THEME, DEFAULT_KEY_MAP, importCodeMirror, } from './common'; -import { useEditorStore } from './context'; import { useChangeHandler, useCompletion, From 980c83a20c6fe6473d747846a123e4dcd9fef019 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:07:13 +0200 Subject: [PATCH 118/255] upd --- packages/graphiql-react/src/stores/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/stores/index.ts b/packages/graphiql-react/src/stores/index.ts index bbd52d92e5c..011875fdb76 100644 --- a/packages/graphiql-react/src/stores/index.ts +++ b/packages/graphiql-react/src/stores/index.ts @@ -1,5 +1,9 @@ -export { editorStore } from './editor'; +export { + editorStore, + useEditorStore, + CodeMirrorEditorWithOperationFacts, +} from './editor'; export { executionStore, useExecutionStore } from './execution'; export { pluginStore } from './plugin'; -export { schemaStore } from './schema'; +export { schemaStore, useSchemaStore } from './schema'; export { storageStore, useStorage } from './storage'; From 1882e2017b34d3b8f8738a6ffd47dae96ab258c6 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:07:40 +0200 Subject: [PATCH 119/255] upd --- packages/graphiql-react/src/editor/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index f3e24c0220c..c5ab538b274 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -4,7 +4,6 @@ export { ResponseEditor, type ResponseTooltipType } from './response-editor'; export { VariableEditor } from './variable-editor'; export { ImagePreview } from './image-preview'; -export { EditorContextProvider, useEditorStore } from './context'; export { getAutoCompleteLeafs, copyQuery, From fa90c0c315dbbe304f5017f79cfdf761908e27da Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:08:14 +0200 Subject: [PATCH 120/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index 9442794dc91..1e8a0f4db97 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -5,13 +5,16 @@ import copyToClipboard from 'copy-to-clipboard'; import { print } from 'graphql'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports -- TODO: check why query builder update only 1st field https://github.com/graphql/graphiql/issues/3836 import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { schemaStore } from '../schema'; -import { storageStore } from '../storage'; +import { + schemaStore, + storageStore, + editorStore, + useEditorStore, + executionStore, +} from '../stores'; import { debounce } from '../utility'; import { onHasCompletion } from './completion'; -import { editorStore, useEditorStore } from './context'; import { CodeMirrorEditor } from './types'; -import { executionStore } from '../execution'; export function useSynchronizeValue( editor: CodeMirrorEditor | null, From 036ebde0fc594d9f52e8a56401cfb66be9487d94 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:08:28 +0200 Subject: [PATCH 121/255] upd --- packages/graphiql-react/src/editor/tabs.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/graphiql-react/src/editor/tabs.ts b/packages/graphiql-react/src/editor/tabs.ts index e8e58f129b9..53c5094db90 100644 --- a/packages/graphiql-react/src/editor/tabs.ts +++ b/packages/graphiql-react/src/editor/tabs.ts @@ -1,8 +1,7 @@ 'use no memo'; // can't figure why it isn't optimized -import { storageStore } from '../storage'; -import { debounce } from '../utility/debounce'; -import { editorStore } from './context'; +import { storageStore, editorStore } from '../stores'; +import { debounce } from '../utility'; export type TabDefinition = { /** From 329039129b8a21bd807fa34abd099f8d0b433de2 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:08:48 +0200 Subject: [PATCH 122/255] upd --- packages/graphiql-react/src/{ => stores}/execution.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename packages/graphiql-react/src/{ => stores}/execution.tsx (98%) diff --git a/packages/graphiql-react/src/execution.tsx b/packages/graphiql-react/src/stores/execution.tsx similarity index 98% rename from packages/graphiql-react/src/execution.tsx rename to packages/graphiql-react/src/stores/execution.tsx index fde831955ae..6a582ba706d 100644 --- a/packages/graphiql-react/src/execution.tsx +++ b/packages/graphiql-react/src/stores/execution.tsx @@ -19,10 +19,10 @@ import { FC, ReactElement, ReactNode, useEffect } from 'react'; import setValue from 'set-value'; import getValue from 'get-value'; -import { getAutoCompleteLeafs } from './editor'; +import { getAutoCompleteLeafs } from '../editor'; import { createStore } from 'zustand'; -import { editorStore } from './editor/context'; -import { createBoundedUseStore } from './utility'; +import { editorStore } from './editor'; +import { createBoundedUseStore } from '../utility'; type ExecutionContextType = { /** From b78624a45ded92a0553613d65400a760df1d9a34 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:09:00 +0200 Subject: [PATCH 123/255] upd --- packages/graphiql-react/src/editor/header-editor.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/editor/header-editor.tsx b/packages/graphiql-react/src/editor/header-editor.tsx index 5aa632e13cf..61449fd1cea 100644 --- a/packages/graphiql-react/src/editor/header-editor.tsx +++ b/packages/graphiql-react/src/editor/header-editor.tsx @@ -6,7 +6,7 @@ import { DEFAULT_KEY_MAP, importCodeMirror, } from './common'; -import { useEditorStore } from './context'; +import { useEditorStore, useExecutionStore } from '../stores'; import { useChangeHandler, useKeyMap, @@ -15,7 +15,6 @@ import { useSynchronizeOption, } from './hooks'; import { WriteableEditorProps } from './types'; -import { useExecutionStore } from '../execution'; import { KEY_MAP } from '../constants'; import { clsx } from 'clsx'; From 005091eedd137b51d43c12d14bc3198d137cb6de Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:09:38 +0200 Subject: [PATCH 124/255] upd --- .../src/{editor/context.tsx => stores/editor.tsx} | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename packages/graphiql-react/src/{editor/context.tsx => stores/editor.tsx} (97%) diff --git a/packages/graphiql-react/src/editor/context.tsx b/packages/graphiql-react/src/stores/editor.tsx similarity index 97% rename from packages/graphiql-react/src/editor/context.tsx rename to packages/graphiql-react/src/stores/editor.tsx index 87d503afb12..57a63cb01b0 100644 --- a/packages/graphiql-react/src/editor/context.tsx +++ b/packages/graphiql-react/src/stores/editor.tsx @@ -12,10 +12,11 @@ import { VariableToType } from 'graphql-language-service'; import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { MaybePromise } from '@graphiql/toolkit'; -import { storageStore, useStorage, executionStore } from '../stores'; -import { STORAGE_KEY as STORAGE_KEY_HEADERS } from './header-editor'; -import { useSynchronizeValue } from './hooks'; -import { STORAGE_KEY_QUERY } from './query-editor'; +import { storageStore, useStorage } from './storage'; +import { executionStore } from './execution'; +import { STORAGE_KEY as STORAGE_KEY_HEADERS } from '../editor/header-editor'; +import { useSynchronizeValue } from '../editor/hooks'; +import { STORAGE_KEY_QUERY } from '../editor/query-editor'; import { createTab, getDefaultTabState, @@ -29,9 +30,9 @@ import { clearHeadersFromTabs, serializeTabState, STORAGE_KEY as STORAGE_KEY_TABS, -} from './tabs'; -import { CodeMirrorEditor } from './types'; -import { STORAGE_KEY as STORAGE_KEY_VARIABLES } from './variable-editor'; +} from '../editor/tabs'; +import { CodeMirrorEditor } from '../editor/types'; +import { STORAGE_KEY as STORAGE_KEY_VARIABLES } from '../editor/variable-editor'; import { DEFAULT_QUERY } from '../constants'; import { createStore } from 'zustand'; import { createBoundedUseStore } from '../utility'; From 154cb8bf0d506a0639dfe3bca848ca5c810117c9 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:12:18 +0200 Subject: [PATCH 125/255] upd --- packages/graphiql-react/src/provider.tsx | 2 +- packages/graphiql-react/src/stores/index.ts | 2 +- packages/graphiql-react/src/toolbar/execute.tsx | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/provider.tsx b/packages/graphiql-react/src/provider.tsx index 47949757a7b..ecd2aa0c4ea 100644 --- a/packages/graphiql-react/src/provider.tsx +++ b/packages/graphiql-react/src/provider.tsx @@ -1,6 +1,6 @@ /* eslint sort-keys: "error" */ import type { ComponentPropsWithoutRef, FC } from 'react'; -import { EditorContextProvider } from './editor'; +import { EditorContextProvider } from './stores/editor'; import { ExecutionContextProvider } from './stores/execution'; import { PluginContextProvider } from './stores/plugin'; import { SchemaContextProvider } from './stores/schema'; diff --git a/packages/graphiql-react/src/stores/index.ts b/packages/graphiql-react/src/stores/index.ts index 011875fdb76..7103e46fc73 100644 --- a/packages/graphiql-react/src/stores/index.ts +++ b/packages/graphiql-react/src/stores/index.ts @@ -4,6 +4,6 @@ export { CodeMirrorEditorWithOperationFacts, } from './editor'; export { executionStore, useExecutionStore } from './execution'; -export { pluginStore } from './plugin'; +export { pluginStore, usePluginStore } from './plugin'; export { schemaStore, useSchemaStore } from './schema'; export { storageStore, useStorage } from './storage'; diff --git a/packages/graphiql-react/src/toolbar/execute.tsx b/packages/graphiql-react/src/toolbar/execute.tsx index 8482fed13b6..275e24e64fc 100644 --- a/packages/graphiql-react/src/toolbar/execute.tsx +++ b/packages/graphiql-react/src/toolbar/execute.tsx @@ -1,6 +1,5 @@ import { FC } from 'react'; -import { useEditorStore } from '../editor'; -import { useExecutionStore } from '../stores'; +import { useEditorStore, useExecutionStore } from '../stores'; import { PlayIcon, StopIcon } from '../icons'; import { DropdownMenu, Tooltip } from '../ui'; import { KEY_MAP } from '../constants'; From f7996960b5712a73c8a9fceaf4659b6a3e6a7403 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:14:08 +0200 Subject: [PATCH 126/255] upd --- packages/graphiql-react/src/provider.tsx | 8 ++++---- packages/graphiql-react/src/stores/editor.tsx | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/graphiql-react/src/provider.tsx b/packages/graphiql-react/src/provider.tsx index ecd2aa0c4ea..01b1f806e33 100644 --- a/packages/graphiql-react/src/provider.tsx +++ b/packages/graphiql-react/src/provider.tsx @@ -1,6 +1,6 @@ /* eslint sort-keys: "error" */ import type { ComponentPropsWithoutRef, FC } from 'react'; -import { EditorContextProvider } from './stores/editor'; +import { EditorStore } from './stores/editor'; import { ExecutionContextProvider } from './stores/execution'; import { PluginContextProvider } from './stores/plugin'; import { SchemaContextProvider } from './stores/schema'; @@ -8,7 +8,7 @@ import { StorageContextProvider } from './stores/storage'; type GraphiQLProviderProps = // - ComponentPropsWithoutRef & + ComponentPropsWithoutRef & ComponentPropsWithoutRef & ComponentPropsWithoutRef & ComponentPropsWithoutRef & @@ -93,7 +93,7 @@ export const GraphiQLProvider: FC = ({ }; return ( - + @@ -101,7 +101,7 @@ export const GraphiQLProvider: FC = ({ - + ); }; diff --git a/packages/graphiql-react/src/stores/editor.tsx b/packages/graphiql-react/src/stores/editor.tsx index 57a63cb01b0..d1f4edacf4e 100644 --- a/packages/graphiql-react/src/stores/editor.tsx +++ b/packages/graphiql-react/src/stores/editor.tsx @@ -44,7 +44,7 @@ export type CodeMirrorEditorWithOperationFacts = CodeMirrorEditor & { variableToType: VariableToType | null; }; -interface EditorStore extends TabsState { +interface EditorStoreType extends TabsState { /** * Add a new tab. */ @@ -225,8 +225,8 @@ interface EditorStore extends TabsState { onPrettifyQuery: (query: string) => MaybePromise; } -type EditorContextProviderProps = Pick< - EditorStore, +type EditorStoreProps = Pick< + EditorStoreType, | 'onTabChange' | 'onEditOperationName' | 'defaultHeaders' @@ -297,13 +297,13 @@ type EditorContextProviderProps = Pick< * typing in the editor. */ variables?: string; - onPrettifyQuery?: EditorStore['onPrettifyQuery']; + onPrettifyQuery?: EditorStoreType['onPrettifyQuery']; }; -const DEFAULT_PRETTIFY_QUERY: EditorStore['onPrettifyQuery'] = query => +const DEFAULT_PRETTIFY_QUERY: EditorStoreType['onPrettifyQuery'] = query => print(parse(query)); -export const editorStore = createStore((set, get) => ({ +export const editorStore = createStore((set, get) => ({ tabs: null!, activeTabIndex: null!, addTab() { @@ -444,7 +444,7 @@ export const editorStore = createStore((set, get) => ({ onPrettifyQuery: DEFAULT_PRETTIFY_QUERY, })); -export const EditorContextProvider: FC = ({ +export const EditorStore: FC = ({ externalFragments, onEditOperationName, defaultHeaders, From 9bb265f6156b0680b47e3200a1a764e9669ed449 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:15:13 +0200 Subject: [PATCH 127/255] upd --- packages/graphiql-react/src/provider.tsx | 8 ++++---- packages/graphiql-react/src/stores/execution.tsx | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/graphiql-react/src/provider.tsx b/packages/graphiql-react/src/provider.tsx index 01b1f806e33..92dc1d5fd90 100644 --- a/packages/graphiql-react/src/provider.tsx +++ b/packages/graphiql-react/src/provider.tsx @@ -1,7 +1,7 @@ /* eslint sort-keys: "error" */ import type { ComponentPropsWithoutRef, FC } from 'react'; import { EditorStore } from './stores/editor'; -import { ExecutionContextProvider } from './stores/execution'; +import { ExecutionStore } from './stores/execution'; import { PluginContextProvider } from './stores/plugin'; import { SchemaContextProvider } from './stores/schema'; import { StorageContextProvider } from './stores/storage'; @@ -9,7 +9,7 @@ import { StorageContextProvider } from './stores/storage'; type GraphiQLProviderProps = // ComponentPropsWithoutRef & - ComponentPropsWithoutRef & + ComponentPropsWithoutRef & ComponentPropsWithoutRef & ComponentPropsWithoutRef & ComponentPropsWithoutRef; @@ -95,11 +95,11 @@ export const GraphiQLProvider: FC = ({ - + {children} - + diff --git a/packages/graphiql-react/src/stores/execution.tsx b/packages/graphiql-react/src/stores/execution.tsx index 6a582ba706d..35fcb1d2cb0 100644 --- a/packages/graphiql-react/src/stores/execution.tsx +++ b/packages/graphiql-react/src/stores/execution.tsx @@ -24,7 +24,7 @@ import { createStore } from 'zustand'; import { editorStore } from './editor'; import { createBoundedUseStore } from '../utility'; -type ExecutionContextType = { +type ExecutionStoreType = { /** * If there is currently a GraphQL request in-flight. For multipart * requests like subscriptions, this will be `true` while fetching the @@ -80,8 +80,8 @@ type ExecutionContextType = { fetcher: Fetcher; }; -type ExecutionContextProviderProps = Pick< - ExecutionContextType, +type ExecutionStoreProps = Pick< + ExecutionStoreType, 'getDefaultFieldNames' | 'fetcher' > & { children: ReactNode; @@ -92,8 +92,7 @@ type ExecutionContextProviderProps = Pick< }; export const executionStore = createStore< - ExecutionContextType & - Pick + ExecutionStoreType & Pick >((set, get) => ({ isFetching: false, subscription: null, @@ -268,7 +267,7 @@ export const executionStore = createStore< }, })); -export const ExecutionContextProvider: FC = ({ +export const ExecutionStore: FC = ({ fetcher, getDefaultFieldNames, children, From 8a894dabfc9a168b7f6ca1f56684489bcd4fc019 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:19:40 +0200 Subject: [PATCH 128/255] upd --- packages/graphiql-react/src/index.ts | 1 - packages/graphiql-react/src/stores/plugin.tsx | 12 ++++---- packages/graphiql-react/src/stores/schema.ts | 28 +++++++++---------- .../graphiql-react/src/stores/storage.tsx | 11 +++----- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/packages/graphiql-react/src/index.ts b/packages/graphiql-react/src/index.ts index b17d5208fde..895e89151cd 100644 --- a/packages/graphiql-react/src/index.ts +++ b/packages/graphiql-react/src/index.ts @@ -44,7 +44,6 @@ export type { WriteableEditorProps, } from './editor'; export type { GraphiQLPlugin } from './stores/plugin'; -export type { SchemaContextType } from './stores/schema'; export type { Theme } from './theme'; export { clsx as cn } from 'clsx'; export { KEY_MAP } from './constants'; diff --git a/packages/graphiql-react/src/stores/plugin.tsx b/packages/graphiql-react/src/stores/plugin.tsx index 25272beb086..c5c976208f5 100644 --- a/packages/graphiql-react/src/stores/plugin.tsx +++ b/packages/graphiql-react/src/stores/plugin.tsx @@ -20,7 +20,7 @@ export type GraphiQLPlugin = { title: string; }; -type PluginContextType = { +type PluginStoreType = { /** * A list of all current plugins, including the built-in ones (the doc * explorer and the history). @@ -50,8 +50,8 @@ type PluginContextType = { onTogglePluginVisibility?(visiblePlugin: GraphiQLPlugin | null): void; }; -type PluginContextProviderProps = Pick< - PluginContextType, +type PluginStoreProps = Pick< + PluginStoreType, 'referencePlugin' | 'onTogglePluginVisibility' > & { children: ReactNode; @@ -69,14 +69,14 @@ type PluginContextProviderProps = Pick< visiblePlugin?: GraphiQLPlugin | string; }; -export const pluginStore = createStore((set, get) => ({ +export const pluginStore = createStore((set, get) => ({ plugins: [], visiblePlugin: null, referencePlugin: undefined, setVisiblePlugin(plugin) { const { plugins, onTogglePluginVisibility } = get(); const byTitle = typeof plugin === 'string'; - const newVisiblePlugin: PluginContextType['visiblePlugin'] = + const newVisiblePlugin: PluginStoreType['visiblePlugin'] = (plugin && plugins.find(p => (byTitle ? p.title : p) === plugin)) || null; set(({ visiblePlugin }) => { if (newVisiblePlugin === visiblePlugin) { @@ -88,7 +88,7 @@ export const pluginStore = createStore((set, get) => ({ }, })); -export const PluginContextProvider: FC = ({ +export const PluginStore: FC = ({ onTogglePluginVisibility, children, visiblePlugin, diff --git a/packages/graphiql-react/src/stores/schema.ts b/packages/graphiql-react/src/stores/schema.ts index abb2ed0a850..b46dd8a28f3 100644 --- a/packages/graphiql-react/src/stores/schema.ts +++ b/packages/graphiql-react/src/stores/schema.ts @@ -23,16 +23,7 @@ import { executionStore, useExecutionStore } from './execution'; type MaybeGraphQLSchema = GraphQLSchema | null | undefined; -type SchemaStore = SchemaContextType & - Pick< - SchemaContextProviderProps, - | 'inputValueDeprecation' - | 'introspectionQueryName' - | 'schemaDescription' - | 'onSchemaChange' - >; - -export const schemaStore = createStore((set, get) => ({ +export const schemaStore = createStore((set, get) => ({ inputValueDeprecation: null!, introspectionQueryName: null!, schemaDescription: null!, @@ -186,12 +177,20 @@ export const schemaStore = createStore((set, get) => ({ }, })); -export type SchemaContextType = { +export interface SchemaStoreType + extends Pick< + SchemaStoreProps, + | 'inputValueDeprecation' + | 'introspectionQueryName' + | 'schemaDescription' + | 'onSchemaChange' + > { /** * Stores an error raised during introspecting or building the GraphQL schema * from the introspection result. */ fetchError: string | null; + /** * Trigger building the GraphQL schema. This might trigger an introspection * request if no schema is passed via props and if using a schema is not @@ -201,6 +200,7 @@ export type SchemaContextType = { * `dangerouslyAssumeSchemaIsValid` prop. */ introspect(): void; + /** * If there currently is an introspection request in-flight. */ @@ -232,9 +232,9 @@ export type SchemaContextType = { * @default '' */ currentHeaders: string; -}; +} -type SchemaContextProviderProps = { +type SchemaStoreProps = { children: ReactNode; /** * This prop can be used to skip validating the GraphQL schema. This applies @@ -275,7 +275,7 @@ type SchemaContextProviderProps = { schema?: GraphQLSchema | IntrospectionQuery | null; } & IntrospectionArgs; -export const SchemaContextProvider: FC = ({ +export const SchemaStore: FC = ({ onSchemaChange, dangerouslyAssumeSchemaIsValid = false, children, diff --git a/packages/graphiql-react/src/stores/storage.tsx b/packages/graphiql-react/src/stores/storage.tsx index f3b58edd7b3..583cb09e57e 100644 --- a/packages/graphiql-react/src/stores/storage.tsx +++ b/packages/graphiql-react/src/stores/storage.tsx @@ -4,11 +4,11 @@ import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore } from 'zustand'; import { createBoundedUseStore } from '../utility'; -type StorageContextType = { +type StorageStoreType = { storage: StorageAPI; }; -type StorageContextProviderProps = { +type StorageStoreProps = { children: ReactNode; /** * Provide a custom storage API. @@ -19,14 +19,11 @@ type StorageContextProviderProps = { storage?: Storage; }; -export const storageStore = createStore(() => ({ +export const storageStore = createStore(() => ({ storage: null!, })); -export const StorageContextProvider: FC = ({ - storage, - children, -}) => { +export const StorageStore: FC = ({ storage, children }) => { const isMounted = useStorageStore(store => Boolean(store.storage)); useEffect(() => { From 83825c3591b52f575c555aa1f1956d677cc9ce4d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:27:41 +0200 Subject: [PATCH 129/255] upd --- .changeset/warm-shoes-boil.md | 9 +++++++ .../graphiql-plugin-doc-explorer/README.md | 5 ++++ packages/graphiql-plugin-history/README.md | 5 ++++ packages/graphiql-react/README.md | 23 ++++++++---------- packages/graphiql-react/src/provider.tsx | 24 +++++++++---------- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/.changeset/warm-shoes-boil.md b/.changeset/warm-shoes-boil.md index b73defc07a9..1eeb3d5335a 100644 --- a/.changeset/warm-shoes-boil.md +++ b/.changeset/warm-shoes-boil.md @@ -6,3 +6,12 @@ remove `useQueryEditor`, `useVariableEditor`, `useHeaderEditor`, `useResponseEditor` hooks remove `UseHeaderEditorArgs`, `UseQueryEditorArgs`, `UseResponseEditorArgs`, `UseVariableEditorArgs` exports + +rename components + +- `StorageContext` => `StorageStore` +- `EditorContext` => `EditorStore` +- `SchemaContext` => `SchemaStore` +- `ExecutionContext` => `ExecutionStore` +- `HistoryContext` => `HistoryStore` +- `ExplorerContext` => `ExplorerStore` diff --git a/packages/graphiql-plugin-doc-explorer/README.md b/packages/graphiql-plugin-doc-explorer/README.md index 7a0dfdbbba8..34d7ec909b9 100644 --- a/packages/graphiql-plugin-doc-explorer/README.md +++ b/packages/graphiql-plugin-doc-explorer/README.md @@ -1 +1,6 @@ # `@graphiql/plugin-doc-explorer` + +## API + +- `useDocExplorer`: Handles the state for the doc explorer +- `useDocExplorerActions`: Actions related to the doc explorer diff --git a/packages/graphiql-plugin-history/README.md b/packages/graphiql-plugin-history/README.md index 7a5f86745f6..7b0bfc28a01 100644 --- a/packages/graphiql-plugin-history/README.md +++ b/packages/graphiql-plugin-history/README.md @@ -1 +1,6 @@ # `@graphiql/plugin-history` + +## API + +- `useHistory`: Persists executed requests in storage +- `useHistoryActions`: Actions related to the history diff --git a/packages/graphiql-react/README.md b/packages/graphiql-react/README.md index 0f3d461c6ff..5248b1d7b96 100644 --- a/packages/graphiql-react/README.md +++ b/packages/graphiql-react/README.md @@ -81,24 +81,21 @@ Further details on how to use `@graphiql/react` can be found in the reference implementation of a GraphQL IDE - Graph*i*QL - in the [`graphiql` package](https://github.com/graphql/graphiql/blob/main/packages/graphiql/src/components/GraphiQL.tsx). -## Available contexts +## Available stores -There are multiple contexts that own different parts of the state that make up a -complete GraphQL IDE. For each context there is a provider component -(`ContextProvider`) that makes sure the context is initialized and managed +There are multiple stores that own different parts of the state that make up a +complete GraphQL IDE. For each context there is a component +(`Store`) that makes sure the store is initialized and managed properly. These components contains all the logic related to state management. -In addition, for each context there is also a hook (`useContext`) that -allows you to consume its current value. -Here is a list of all contexts that come with `@graphiql/react` +In addition, for each store, there is also a hook that +allows you to consume its current value: -- `StorageContext`: Provides a storage API that can be used to persist state in +- `useStorage`: Provides a storage API that can be used to persist state in the browser (by default using `localStorage`) -- `EditorContext`: Manages all the editors and tabs -- `SchemaContext`: Fetches, validates and stores the GraphQL schema -- `ExecutionContext`: Executes GraphQL requests -- `HistoryContext`: Persists executed requests in storage -- `ExplorerContext`: Handles the state for the docs explorer +- `useEditorStore`: Manages all the editors and tabs +- `useSchemaStore`: Fetches, validates and stores the GraphQL schema +- `useExecutionStore`: Executes GraphQL requests All context properties are documented using JSDoc comments. If you're using an IDE like VSCode for development these descriptions will show up in auto-complete diff --git a/packages/graphiql-react/src/provider.tsx b/packages/graphiql-react/src/provider.tsx index 92dc1d5fd90..2ce69274e15 100644 --- a/packages/graphiql-react/src/provider.tsx +++ b/packages/graphiql-react/src/provider.tsx @@ -2,17 +2,17 @@ import type { ComponentPropsWithoutRef, FC } from 'react'; import { EditorStore } from './stores/editor'; import { ExecutionStore } from './stores/execution'; -import { PluginContextProvider } from './stores/plugin'; -import { SchemaContextProvider } from './stores/schema'; -import { StorageContextProvider } from './stores/storage'; +import { PluginStore } from './stores/plugin'; +import { SchemaStore } from './stores/schema'; +import { StorageStore } from './stores/storage'; type GraphiQLProviderProps = // ComponentPropsWithoutRef & ComponentPropsWithoutRef & - ComponentPropsWithoutRef & - ComponentPropsWithoutRef & - ComponentPropsWithoutRef; + ComponentPropsWithoutRef & + ComponentPropsWithoutRef & + ComponentPropsWithoutRef; export const GraphiQLProvider: FC = ({ defaultHeaders, @@ -92,16 +92,14 @@ export const GraphiQLProvider: FC = ({ visiblePlugin, }; return ( - + - + - - {children} - + {children} - + - + ); }; From e78fa9a91bf2c8ae42eed3ef7beefc45ae607826 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:32:19 +0200 Subject: [PATCH 130/255] upd --- .changeset/warm-shoes-boil.md | 4 ++- .../src/__tests__/components.spec.tsx | 8 +++--- .../graphiql-plugin-history/src/context.tsx | 25 +++++++++++-------- packages/graphiql-plugin-history/src/index.ts | 6 +---- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.changeset/warm-shoes-boil.md b/.changeset/warm-shoes-boil.md index 1eeb3d5335a..3fad1202747 100644 --- a/.changeset/warm-shoes-boil.md +++ b/.changeset/warm-shoes-boil.md @@ -1,5 +1,7 @@ --- -'@graphiql/react': patch +'@graphiql/react': minor +'@graphiql/plugin-history': minor +'@graphiql/plugin-doc-explorer': minor 'graphiql': patch --- diff --git a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx index 909e6854f09..f024d369560 100644 --- a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx +++ b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx @@ -1,9 +1,9 @@ import { fireEvent, render } from '@testing-library/react'; import type { ComponentProps } from 'react'; import { formatQuery, HistoryItem } from '../components'; -import { HistoryContextProvider } from '../context'; +import { HistoryStore } from '../context'; import { Tooltip, GraphiQLProvider } from '@graphiql/react'; -import { editorStore } from '../../../graphiql-react/dist/editor/context'; +import { editorStore } from '../../../graphiql-react/dist/stores'; const mockQuery = /* GraphQL */ ` query Test($string: String) { @@ -25,9 +25,9 @@ const QueryHistoryItemWithContext: typeof HistoryItem = props => { return ( - + - + ); diff --git a/packages/graphiql-plugin-history/src/context.tsx b/packages/graphiql-plugin-history/src/context.tsx index a7cc0d39466..62aed7455c7 100644 --- a/packages/graphiql-plugin-history/src/context.tsx +++ b/packages/graphiql-plugin-history/src/context.tsx @@ -1,7 +1,10 @@ // eslint-disable-next-line react/jsx-filename-extension -- TODO import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore } from 'zustand'; -import { HistoryStore, QueryStoreItem } from '@graphiql/toolkit'; +import { + HistoryStore as ToolkitHistoryStore, + QueryStoreItem, +} from '@graphiql/toolkit'; import { useExecutionStore, useEditorStore, @@ -9,7 +12,7 @@ import { createBoundedUseStore, } from '@graphiql/react'; -const historyStore = createStore((set, get) => ({ +const historyStore = createStore((set, get) => ({ historyStorage: null!, actions: { addToHistory(operation) { @@ -36,8 +39,8 @@ const historyStore = createStore((set, get) => ({ }, })); -type HistoryContextType = { - historyStorage: HistoryStore; +type HistoryStoreType = { + historyStorage: ToolkitHistoryStore; actions: { /** * Add an operation to the history. @@ -100,7 +103,7 @@ type HistoryContextType = { }; }; -type HistoryContextProviderProps = { +type HistoryStoreProps = { children: ReactNode; /** * The maximum number of executed operations to store. @@ -110,12 +113,12 @@ type HistoryContextProviderProps = { }; /** - * The functions send the entire operation so users can customize their own application with - * and get access to the operation plus - * any additional props they added for their needs (i.e., build their own functions that may save - * to a backend instead of localStorage and might need an id property added to the QueryStoreItem) + * The functions send the entire operation so users can customize their own application and get + * access to the operation plus any additional props they added for their needs (i.e., build their + * own functions that may save to a backend instead of localStorage and might need an id property + * added to the `QueryStoreItem`) */ -export const HistoryContextProvider: FC = ({ +export const HistoryStore: FC = ({ maxHistoryLength = 20, children, }) => { @@ -125,7 +128,7 @@ export const HistoryContextProvider: FC = ({ const storage = useStorage(); const historyStorage = // eslint-disable-line react-hooks/exhaustive-deps -- false positive, code is optimized by React Compiler - new HistoryStore(storage, maxHistoryLength); + new ToolkitHistoryStore(storage, maxHistoryLength); useEffect(() => { historyStore.setState({ historyStorage }); diff --git a/packages/graphiql-plugin-history/src/index.ts b/packages/graphiql-plugin-history/src/index.ts index 7f16625bd91..1ea1e91b7b3 100644 --- a/packages/graphiql-plugin-history/src/index.ts +++ b/packages/graphiql-plugin-history/src/index.ts @@ -11,8 +11,4 @@ export const HISTORY_PLUGIN: GraphiQLPlugin = { export { History }; -export { - HistoryContextProvider, - useHistory, - useHistoryActions, -} from './context'; +export { HistoryStore, useHistory, useHistoryActions } from './context'; From a7852976a4f5424285bb88bebdb4a9b7544fe419 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:33:30 +0200 Subject: [PATCH 131/255] upd --- .../__tests__/doc-explorer.spec.tsx | 30 +++++++++---------- .../src/context.ts | 6 ++-- .../src/index.tsx | 3 +- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx b/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx index de6a7d600ca..72e8b1ec8da 100644 --- a/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx +++ b/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx @@ -3,7 +3,7 @@ import { GraphQLInt, GraphQLObjectType, GraphQLSchema } from 'graphql'; import { FC, useEffect } from 'react'; import type { SchemaContextType } from '@graphiql/react'; import { - DocExplorerContextProvider, + DocExplorerStore, useDocExplorer, useDocExplorerActions, } from '../../context'; @@ -49,9 +49,9 @@ const withErrorSchemaContext: SchemaContextType = { const DocExplorerWithContext: FC = () => { return ( - + - + ); }; @@ -115,9 +115,9 @@ describe('DocExplorer', () => { schema: initialSchema, }); const { container, rerender } = render( - + - , + , ); // First proper render of doc explorer @@ -128,9 +128,9 @@ describe('DocExplorer', () => { }); }); rerender( - + - , + , ); const [title] = container.querySelectorAll('.graphiql-doc-explorer-title'); @@ -144,9 +144,9 @@ describe('DocExplorer', () => { }); }); rerender( - + - , + , ); const [title2] = container.querySelectorAll('.graphiql-doc-explorer-title'); // Because `Query.field` still exists in the new schema, we can still render it @@ -177,9 +177,9 @@ describe('DocExplorer', () => { schema: initialSchema, }); const { container, rerender } = render( - + - , + , ); // First proper render of doc explorer @@ -190,9 +190,9 @@ describe('DocExplorer', () => { }); }); rerender( - + - , + , ); const title = container.querySelector('.graphiql-doc-explorer-title')!; @@ -206,9 +206,9 @@ describe('DocExplorer', () => { }); }); rerender( - + - , + , ); const title2 = container.querySelector('.graphiql-doc-explorer-title')!; // Because `Query.field` doesn't exist anymore, the top-most item we can render is `Query` diff --git a/packages/graphiql-plugin-doc-explorer/src/context.ts b/packages/graphiql-plugin-doc-explorer/src/context.ts index 03e333b8551..2d0d0f06dcd 100644 --- a/packages/graphiql-plugin-doc-explorer/src/context.ts +++ b/packages/graphiql-plugin-doc-explorer/src/context.ts @@ -45,7 +45,7 @@ export type DocExplorerNavStack = [ ...DocExplorerNavStackItem[], ]; -export type DocExplorerContextType = { +export type DocExplorerStoreType = { /** * A stack of navigation items. The last item in the list is the current one. * This list always contains at least one item. @@ -78,7 +78,7 @@ export type DocExplorerContextType = { const INITIAL_NAV_STACK: DocExplorerNavStack = [{ name: 'Docs' }]; -export const docExplorerStore = createStore( +export const docExplorerStore = createStore( (set, get) => ({ explorerNavStack: INITIAL_NAV_STACK, actions: { @@ -235,7 +235,7 @@ export const docExplorerStore = createStore( }), ); -export const DocExplorerContextProvider: FC<{ +export const DocExplorerStore: FC<{ children: ReactNode; }> = ({ children }) => { const { schema, validationErrors, schemaReference } = useSchemaStore(); diff --git a/packages/graphiql-plugin-doc-explorer/src/index.tsx b/packages/graphiql-plugin-doc-explorer/src/index.tsx index 2b34c809eeb..37e5e47b849 100644 --- a/packages/graphiql-plugin-doc-explorer/src/index.tsx +++ b/packages/graphiql-plugin-doc-explorer/src/index.tsx @@ -9,13 +9,12 @@ import { DocExplorer } from './components'; export * from './components'; export { - DocExplorerContextProvider, + DocExplorerStore, useDocExplorer, useDocExplorerActions, } from './context'; export type { - DocExplorerContextType, DocExplorerFieldDef, DocExplorerNavStack, DocExplorerNavStackItem, From d2f0d36602c03f01ab93c49270164dd9c8ce2ca8 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:36:27 +0200 Subject: [PATCH 132/255] upd --- packages/graphiql-react/src/editor/hooks.ts | 3 +-- packages/graphiql-react/src/editor/types.ts | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/graphiql-react/src/editor/hooks.ts b/packages/graphiql-react/src/editor/hooks.ts index 1e8a0f4db97..2b21b39b199 100644 --- a/packages/graphiql-react/src/editor/hooks.ts +++ b/packages/graphiql-react/src/editor/hooks.ts @@ -1,6 +1,5 @@ import { fillLeafs, mergeAst } from '@graphiql/toolkit'; import type { EditorChange, EditorConfiguration } from 'codemirror'; -import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; import copyToClipboard from 'copy-to-clipboard'; import { print } from 'graphql'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports -- TODO: check why query builder update only 1st field https://github.com/graphql/graphiql/issues/3836 @@ -14,7 +13,7 @@ import { } from '../stores'; import { debounce } from '../utility'; import { onHasCompletion } from './completion'; -import { CodeMirrorEditor } from './types'; +import { CodeMirrorEditor, SchemaReference } from './types'; export function useSynchronizeValue( editor: CodeMirrorEditor | null, diff --git a/packages/graphiql-react/src/editor/types.ts b/packages/graphiql-react/src/editor/types.ts index 3fca9f0d936..05b678aa504 100644 --- a/packages/graphiql-react/src/editor/types.ts +++ b/packages/graphiql-react/src/editor/types.ts @@ -27,3 +27,5 @@ export type WriteableEditorProps = CommonEditorProps & { */ readOnly?: boolean; }; + +export type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; From e9c0567e4381a379457fb5a485fa1f48c57ea667 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:36:46 +0200 Subject: [PATCH 133/255] upd --- packages/graphiql-react/src/editor/query-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/editor/query-editor.tsx b/packages/graphiql-react/src/editor/query-editor.tsx index ab358b7df08..715c12285d6 100644 --- a/packages/graphiql-react/src/editor/query-editor.tsx +++ b/packages/graphiql-react/src/editor/query-editor.tsx @@ -1,5 +1,4 @@ import { getSelectedOperationName } from '@graphiql/toolkit'; -import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; import type { DocumentNode, FragmentDefinitionNode, @@ -40,6 +39,7 @@ import { CodeMirrorEditor, CodeMirrorType, WriteableEditorProps, + SchemaReference, } from './types'; import { normalizeWhitespace } from '../utility/whitespace'; import { KEY_MAP } from '../constants'; From aa701c558d2d517b950a33eebba4cfd70675e34a Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:36:58 +0200 Subject: [PATCH 134/255] upd --- packages/graphiql-react/src/editor/variable-editor.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/graphiql-react/src/editor/variable-editor.tsx b/packages/graphiql-react/src/editor/variable-editor.tsx index c84105d773d..d93e300a5db 100644 --- a/packages/graphiql-react/src/editor/variable-editor.tsx +++ b/packages/graphiql-react/src/editor/variable-editor.tsx @@ -1,6 +1,4 @@ -import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; import { useEffect, useRef } from 'react'; - import { useExecutionStore, useEditorStore } from '../stores'; import { commonKeys, @@ -16,7 +14,7 @@ import { prettifyEditors, useSynchronizeOption, } from './hooks'; -import { WriteableEditorProps } from './types'; +import { WriteableEditorProps, SchemaReference } from './types'; import { KEY_MAP } from '../constants'; import { clsx } from 'clsx'; From 639bc4c3f0620a03e627e63f0df95346493ae961 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:37:23 +0200 Subject: [PATCH 135/255] upd --- packages/graphiql-react/src/stores/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/stores/schema.ts b/packages/graphiql-react/src/stores/schema.ts index b46dd8a28f3..40afd81ce03 100644 --- a/packages/graphiql-react/src/stores/schema.ts +++ b/packages/graphiql-react/src/stores/schema.ts @@ -17,7 +17,7 @@ import { import { Dispatch, FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore } from 'zustand'; import { useEditorStore } from './editor'; -import type { SchemaReference } from 'codemirror-graphql/utils/SchemaReference'; +import { SchemaReference } from '../editor/types'; import { createBoundedUseStore } from '../utility'; import { executionStore, useExecutionStore } from './execution'; From 6f44cd84651197a4a1c1284574c76aab3f17325b Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:46:01 +0200 Subject: [PATCH 136/255] upd --- .../components/__tests__/doc-explorer.spec.tsx | 16 +++++----------- .../__tests__/type-documentation.spec.tsx | 2 +- .../src/__tests__/components.spec.tsx | 2 +- packages/graphiql-react/src/editor/index.ts | 2 +- packages/graphiql-react/src/stores/index.ts | 2 +- packages/graphiql/src/GraphiQL.tsx | 17 +++++++---------- 6 files changed, 16 insertions(+), 25 deletions(-) diff --git a/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx b/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx index 72e8b1ec8da..278139920a7 100644 --- a/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx +++ b/packages/graphiql-plugin-doc-explorer/src/components/__tests__/doc-explorer.spec.tsx @@ -1,14 +1,13 @@ import { act, render } from '@testing-library/react'; import { GraphQLInt, GraphQLObjectType, GraphQLSchema } from 'graphql'; import { FC, useEffect } from 'react'; -import type { SchemaContextType } from '@graphiql/react'; import { DocExplorerStore, useDocExplorer, useDocExplorerActions, } from '../../context'; import { DocExplorer } from '../doc-explorer'; -import { schemaStore } from '../../../../graphiql-react/dist/schema'; +import { schemaStore } from '../../../../graphiql-react/dist/stores/schema'; function makeSchema(fieldName = 'field') { return new GraphQLSchema({ @@ -29,22 +28,17 @@ function makeSchema(fieldName = 'field') { }); } -const defaultSchemaContext: SchemaContextType = { - fetchError: null, +const defaultSchemaContext = { + ...schemaStore.getInitialState(), introspect() {}, - isFetching: false, schema: makeSchema(), - validationErrors: [], }; -const withErrorSchemaContext: SchemaContextType = { +const withErrorSchemaContext = { + ...schemaStore.getInitialState(), fetchError: 'Error fetching schema', introspect() {}, - isFetching: false, schema: new GraphQLSchema({ description: 'GraphQL Schema for testing' }), - validationErrors: [], - schemaReference: null!, - setSchemaReference: null!, }; const DocExplorerWithContext: FC = () => { diff --git a/packages/graphiql-plugin-doc-explorer/src/components/__tests__/type-documentation.spec.tsx b/packages/graphiql-plugin-doc-explorer/src/components/__tests__/type-documentation.spec.tsx index 728f5d53e21..0141a621080 100644 --- a/packages/graphiql-plugin-doc-explorer/src/components/__tests__/type-documentation.spec.tsx +++ b/packages/graphiql-plugin-doc-explorer/src/components/__tests__/type-documentation.spec.tsx @@ -10,7 +10,7 @@ import { import { docExplorerStore } from '../../context'; import { TypeDocumentation } from '../type-documentation'; import { unwrapType } from './test-utils'; -import { schemaStore } from '../../../../graphiql-react/dist/schema'; +import { schemaStore } from '../../../../graphiql-react/dist/stores/schema'; const TypeDocumentationWithContext: FC<{ type: GraphQLNamedType }> = ({ type, diff --git a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx index f024d369560..6e28ca2c095 100644 --- a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx +++ b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx @@ -3,7 +3,7 @@ import type { ComponentProps } from 'react'; import { formatQuery, HistoryItem } from '../components'; import { HistoryStore } from '../context'; import { Tooltip, GraphiQLProvider } from '@graphiql/react'; -import { editorStore } from '../../../graphiql-react/dist/stores'; +import { editorStore } from '../../../graphiql-react/dist/stores/editor'; const mockQuery = /* GraphQL */ ` query Test($string: String) { diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index c5ab538b274..62dd1d10113 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -16,4 +16,4 @@ export { useHeadersEditorState, } from './hooks'; export type { TabsState } from './tabs'; -export type { CommonEditorProps, KeyMap, WriteableEditorProps } from './types'; +export type { CommonEditorProps, KeyMap, WriteableEditorProps, SchemaReference } from './types'; diff --git a/packages/graphiql-react/src/stores/index.ts b/packages/graphiql-react/src/stores/index.ts index 7103e46fc73..e27614c7a2c 100644 --- a/packages/graphiql-react/src/stores/index.ts +++ b/packages/graphiql-react/src/stores/index.ts @@ -1,7 +1,7 @@ export { editorStore, useEditorStore, - CodeMirrorEditorWithOperationFacts, + type CodeMirrorEditorWithOperationFacts, } from './editor'; export { executionStore, useExecutionStore } from './execution'; export { pluginStore, usePluginStore } from './plugin'; diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index c2562966ca8..0219ca2f84d 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -52,12 +52,9 @@ import { cn, KEY_MAP, } from '@graphiql/react'; +import { HistoryStore, HISTORY_PLUGIN } from '@graphiql/plugin-history'; import { - HistoryContextProvider, - HISTORY_PLUGIN, -} from '@graphiql/plugin-history'; -import { - DocExplorerContextProvider, + DocExplorerStore, DOC_EXPLORER_PLUGIN, } from '@graphiql/plugin-doc-explorer'; @@ -69,7 +66,7 @@ import { export type GraphiQLProps = // Omit, 'children'> & - Omit, 'children'> & + Omit, 'children'> & // `children` prop should be optional GraphiQLInterfaceProps; @@ -139,11 +136,11 @@ const GraphiQL_: FC = ({ referencePlugin={referencePlugin} {...props} > - - + + {children} - - + + ); }; From e4eb50cbe1ebc29efb37148b5a7d8e1a508f5720 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:46:40 +0200 Subject: [PATCH 137/255] prettier --- packages/graphiql-react/src/editor/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/graphiql-react/src/editor/index.ts b/packages/graphiql-react/src/editor/index.ts index 62dd1d10113..e77ba8be4e5 100644 --- a/packages/graphiql-react/src/editor/index.ts +++ b/packages/graphiql-react/src/editor/index.ts @@ -16,4 +16,9 @@ export { useHeadersEditorState, } from './hooks'; export type { TabsState } from './tabs'; -export type { CommonEditorProps, KeyMap, WriteableEditorProps, SchemaReference } from './types'; +export type { + CommonEditorProps, + KeyMap, + WriteableEditorProps, + SchemaReference, +} from './types'; From 14be107ec36fd508657a2d5cbc7e526ef81eb024 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:52:02 +0200 Subject: [PATCH 138/255] upd --- packages/graphiql-react/src/editor/tabs.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/graphiql-react/src/editor/tabs.ts b/packages/graphiql-react/src/editor/tabs.ts index 53c5094db90..898fe1bfd21 100644 --- a/packages/graphiql-react/src/editor/tabs.ts +++ b/packages/graphiql-react/src/editor/tabs.ts @@ -66,9 +66,15 @@ export function getDefaultTabState({ defaultQuery, defaultHeaders, headers, - defaultTabs, query, variables, + defaultTabs = [ + { + query: query ?? defaultQuery, + variables, + headers: headers ?? defaultHeaders, + }, + ], shouldPersistHeaders, }: { defaultQuery: string; @@ -132,15 +138,7 @@ export function getDefaultTabState({ } catch { return { activeTabIndex: 0, - tabs: ( - defaultTabs || [ - { - query: query ?? defaultQuery, - variables, - headers: headers ?? defaultHeaders, - }, - ] - ).map(createTab), + tabs: defaultTabs.map(createTab), }; } } From 388495d8448eebcf40c201d415311699e1bebcab Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 11:55:10 +0200 Subject: [PATCH 139/255] move plugins check to store --- packages/graphiql-react/src/stores/plugin.tsx | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/graphiql-react/src/stores/plugin.tsx b/packages/graphiql-react/src/stores/plugin.tsx index c5c976208f5..5a24e11f678 100644 --- a/packages/graphiql-react/src/stores/plugin.tsx +++ b/packages/graphiql-react/src/stores/plugin.tsx @@ -48,6 +48,8 @@ type PluginStoreType = { * is visible, the function will be invoked with `null`. */ onTogglePluginVisibility?(visiblePlugin: GraphiQLPlugin | null): void; + + setPlugins(plugins: GraphiQLPlugin[]): void; }; type PluginStoreProps = Pick< @@ -86,16 +88,7 @@ export const pluginStore = createStore((set, get) => ({ return { visiblePlugin: newVisiblePlugin }; }); }, -})); - -export const PluginStore: FC = ({ - onTogglePluginVisibility, - children, - visiblePlugin, - plugins = [], - referencePlugin, -}) => { - useEffect(() => { + setPlugins(plugins) { const seenTitles = new Set(); const msg = 'All GraphiQL plugins must have a unique title'; for (const { title } of plugins) { @@ -107,6 +100,18 @@ export const PluginStore: FC = ({ } seenTitles.add(title); } + set({ plugins }); + }, +})); + +export const PluginStore: FC = ({ + onTogglePluginVisibility, + children, + visiblePlugin, + plugins = [], + referencePlugin, +}) => { + useEffect(() => { // TODO: visiblePlugin initial data // const storedValue = storage.get(STORAGE_KEY); // const pluginForStoredValue = plugins.find( @@ -118,9 +123,8 @@ export const PluginStore: FC = ({ // if (storedValue) { // storage.set(STORAGE_KEY, ''); // } - + pluginStore.getState().setPlugins(plugins); pluginStore.setState({ - plugins, onTogglePluginVisibility, referencePlugin, }); From bc2c655ca92d59ba6cc8e05dca6413cccbf7061b Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 12:31:25 +0200 Subject: [PATCH 140/255] upd --- .changeset/warm-shoes-boil.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.changeset/warm-shoes-boil.md b/.changeset/warm-shoes-boil.md index 3fad1202747..ce390e7e78b 100644 --- a/.changeset/warm-shoes-boil.md +++ b/.changeset/warm-shoes-boil.md @@ -11,9 +11,9 @@ remove `UseHeaderEditorArgs`, `UseQueryEditorArgs`, `UseResponseEditorArgs`, `Us rename components -- `StorageContext` => `StorageStore` -- `EditorContext` => `EditorStore` -- `SchemaContext` => `SchemaStore` -- `ExecutionContext` => `ExecutionStore` -- `HistoryContext` => `HistoryStore` -- `ExplorerContext` => `ExplorerStore` +- `StorageContextProvider` => `StorageStore` +- `EditorContextProvider` => `EditorStore` +- `SchemaContextProvider` => `SchemaStore` +- `ExecutionContextProvider` => `ExecutionStore` +- `HistoryContextProvider` => `HistoryStore` +- `ExplorerContextProvider` => `ExplorerStore` From 7e937c31d58bd52c1128c92ae1bca6e563d518ed Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 12:33:48 +0200 Subject: [PATCH 141/255] upd --- packages/graphiql-react/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/graphiql-react/README.md b/packages/graphiql-react/README.md index 5248b1d7b96..586a7b38a96 100644 --- a/packages/graphiql-react/README.md +++ b/packages/graphiql-react/README.md @@ -84,7 +84,7 @@ implementation of a GraphQL IDE - Graph*i*QL - in the ## Available stores There are multiple stores that own different parts of the state that make up a -complete GraphQL IDE. For each context there is a component +complete GraphQL IDE. For each store there is a component (`Store`) that makes sure the store is initialized and managed properly. These components contains all the logic related to state management. From 25ba8cc88f1ad08f51f3c76a70898370d1bac3ea Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:18:34 +0200 Subject: [PATCH 142/255] refactor --- packages/graphiql/src/GraphiQL.tsx | 793 ++++++++++-------------- packages/graphiql/src/ui/footer.tsx | 6 + packages/graphiql/src/ui/index.tsx | 4 + packages/graphiql/src/ui/logo.tsx | 21 + packages/graphiql/src/ui/short-keys.tsx | 64 ++ packages/graphiql/src/ui/toolbar.tsx | 65 ++ 6 files changed, 484 insertions(+), 469 deletions(-) create mode 100644 packages/graphiql/src/ui/footer.tsx create mode 100644 packages/graphiql/src/ui/index.tsx create mode 100644 packages/graphiql/src/ui/logo.tsx create mode 100644 packages/graphiql/src/ui/short-keys.tsx create mode 100644 packages/graphiql/src/ui/toolbar.tsx diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 0219ca2f84d..894d31ff548 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -10,21 +10,18 @@ import type { FC, ComponentPropsWithoutRef, } from 'react'; -import { Fragment, useState, useEffect, Children } from 'react'; +import { useState, useEffect, Children } from 'react'; import { Button, ButtonGroup, ChevronDownIcon, ChevronUpIcon, - CopyIcon, Dialog, ExecuteButton, GraphiQLProvider, HeaderEditor, KeyboardShortcutIcon, - MergeIcon, PlusIcon, - PrettifyIcon, QueryEditor, ReloadIcon, ResponseEditor, @@ -33,30 +30,25 @@ import { Tab, Tabs, Theme, - ToolbarButton, Tooltip, UnStyledButton, - copyQuery, useDragResize, useEditorStore, useExecutionStore, - mergeQuery, usePluginStore, - prettifyEditors, useSchemaStore, useStorage, useTheme, VariableEditor, WriteableEditorProps, - isMacOs, cn, - KEY_MAP, } from '@graphiql/react'; import { HistoryStore, HISTORY_PLUGIN } from '@graphiql/plugin-history'; import { DocExplorerStore, DOC_EXPLORER_PLUGIN, } from '@graphiql/plugin-doc-explorer'; +import { GraphiQLLogo, GraphiQLToolbar, GraphiQLFooter, ShortKeys } from './ui'; /** * API docs for this live here: @@ -138,7 +130,11 @@ const GraphiQL_: FC = ({ > - {children} + + + {children} + + @@ -427,511 +423,370 @@ export const GraphiQLInterface: FC = ({ }; return ( - -
-
- {plugins.map((plugin, index) => { - const isVisible = plugin === visiblePlugin; - const label = `${isVisible ? 'Hide' : 'Show'} ${plugin.title}`; - return ( - - - - - ); - })} - - - - - - - - - - - - +
+
+ {plugins.map((plugin, index) => { + const isVisible = plugin === visiblePlugin; + const label = `${isVisible ? 'Hide' : 'Show'} ${plugin.title}`; + return ( + + + + + ); + })} + + + + + + + + + + + + +
+
+
+ {PluginContent ? : null}
-
+ {visiblePlugin && (
- {PluginContent ? : null} + className="graphiql-horizontal-drag-bar" + ref={pluginResize.dragBarRef} + /> + )} +
+
+ + {tabs.map((tab, index, arr) => ( + + + {tab.title} + + {arr.length > 1 && } + + ))} + + + + + + {logo}
- {visiblePlugin && ( -
- )} -
-
- +
+
+ +
+ + {toolbar} +
+
+ +
- {tabs.map((tab, index, arr) => ( - - - {tab.title} - - {arr.length > 1 && } - - ))} - - - - - {logo} -
-
-
-
- -
- - {toolbar} -
-
- -
+ {isHeadersEditorEnabled && ( - Variables + Headers - {isHeadersEditorEnabled && ( - - Headers - - )} + )} - + - - {editorToolsResize.hiddenElement === 'second' ? ( - - -
+ {editorToolsResize.hiddenElement === 'second' ? ( +
-
- + + {isHeadersEditorEnabled && ( + - {isHeadersEditorEnabled && ( - - )} -
-
+ )} +
+
-
+
-
- {isExecutionFetching && } - - {footer} -
+
+ {isExecutionFetching && } + + {footer}
- -
- - Short Keys - - -
+
+ +
+ + Short Keys + + +
+
+ +
+
+ +
+ + Settings + + +
+ {showPersistHeadersSettings ? (
- -
-
- -
- - Settings - - -
- {showPersistHeadersSettings ? ( -
-
-
- Persist headers -
-
- Save headers upon reloading.{' '} - - Only enable if you trust this device. - -
+
+
+ Persist headers
- - - - -
- ) : null} - {!forcedTheme && ( -
-
-
Theme
-
- Adjust how the interface appears. -
+
+ Save headers upon reloading.{' '} + + Only enable if you trust this device. +
- - - - -
- )} + + + + +
+ ) : null} + {!forcedTheme && (
-
Clear storage
+
Theme
- Remove all locally stored data and start fresh. + Adjust how the interface appears.
- + + + + +
-
-
- - ); -}; - -function withMacOS(key: string) { - return isMacOs ? key.replace('Ctrl', '⌘') : key; -} - -const SHORT_KEYS = Object.entries({ - 'Search in editor': withMacOS(KEY_MAP.searchInEditor[0]), - 'Search in documentation': withMacOS(KEY_MAP.searchInDocs[0]), - 'Execute query': withMacOS(KEY_MAP.runQuery[0]), - 'Prettify editors': KEY_MAP.prettify[0], - 'Merge fragments definitions into operation definition': - KEY_MAP.mergeFragments[0], - 'Copy query': KEY_MAP.copyQuery[0], - 'Re-fetch schema using introspection': KEY_MAP.refetchSchema[0], -}); - -interface ShortKeysProps { - /** @default 'sublime' */ - keyMap?: string; -} - -const ShortKeys: FC = ({ keyMap = 'sublime' }) => { - return ( -
- - - - - - - - - {SHORT_KEYS.map(([title, keys]) => ( - - - - - ))} - -
Short KeyFunction
- {keys.split('-').map((key, index, array) => ( - - {key} - {index !== array.length - 1 && ' + '} - - ))} - {title}
-

- The editors use{' '} - - CodeMirror Key Maps - {' '} - that add more short keys. This instance of GraphiQL uses{' '} - {keyMap}. -

+ )} +
+
+
Clear storage
+
+ Remove all locally stored data and start fresh. +
+
+ +
+
); }; -const defaultGraphiqlLogo = ( - - Graph - i - QL - -); - -// Configure the UI by providing this Component as a child of GraphiQL. -const GraphiQLLogo: FC<{ children?: ReactNode }> = ({ - children = defaultGraphiqlLogo, -}) => { - return
{children}
; -}; - -const DefaultToolbarRenderProps: FC<{ - prettify: ReactNode; - copy: ReactNode; - merge: ReactNode; -}> = ({ prettify, copy, merge }) => ( - <> - {prettify} - {merge} - {copy} - -); - -// Configure the UI by providing this Component as a child of GraphiQL. -const GraphiQLToolbar: FC<{ - children?: typeof DefaultToolbarRenderProps; -}> = ({ children = DefaultToolbarRenderProps }) => { - if (typeof children !== 'function') { - throw new TypeError( - 'The `GraphiQL.Toolbar` component requires a render prop function as its child.', - ); - } - - const prettify = ( - - - ); - - const merge = ( - - - ); - - const copy = ( - - - ); - - return children({ prettify, copy, merge }); -}; - -// Configure the UI by providing this Component as a child of GraphiQL. -const GraphiQLFooter: FC<{ children: ReactNode }> = ({ children }) => { - return
{children}
; -}; - function getChildComponentType(child: ReactNode) { if ( child && diff --git a/packages/graphiql/src/ui/footer.tsx b/packages/graphiql/src/ui/footer.tsx new file mode 100644 index 00000000000..5ab9db90063 --- /dev/null +++ b/packages/graphiql/src/ui/footer.tsx @@ -0,0 +1,6 @@ +import { FC, ReactNode } from 'react'; + +// Configure the UI by providing this Component as a child of GraphiQL. +export const GraphiQLFooter: FC<{ children: ReactNode }> = ({ children }) => { + return
{children}
; +}; diff --git a/packages/graphiql/src/ui/index.tsx b/packages/graphiql/src/ui/index.tsx new file mode 100644 index 00000000000..10b27973b62 --- /dev/null +++ b/packages/graphiql/src/ui/index.tsx @@ -0,0 +1,4 @@ +export { GraphiQLFooter } from './footer'; +export { GraphiQLLogo } from './logo'; +export { ShortKeys } from './short-keys'; +export { GraphiQLToolbar } from './toolbar'; diff --git a/packages/graphiql/src/ui/logo.tsx b/packages/graphiql/src/ui/logo.tsx new file mode 100644 index 00000000000..dbee3f4c7e2 --- /dev/null +++ b/packages/graphiql/src/ui/logo.tsx @@ -0,0 +1,21 @@ +import type { FC, ReactNode } from 'react'; + +const defaultGraphiqlLogo = ( + + Graph + i + QL + +); + +// Configure the UI by providing this Component as a child of GraphiQL. +export const GraphiQLLogo: FC<{ children?: ReactNode }> = ({ + children = defaultGraphiqlLogo, +}) => { + return
{children}
; +}; diff --git a/packages/graphiql/src/ui/short-keys.tsx b/packages/graphiql/src/ui/short-keys.tsx new file mode 100644 index 00000000000..32f8cde7e68 --- /dev/null +++ b/packages/graphiql/src/ui/short-keys.tsx @@ -0,0 +1,64 @@ +import { FC, Fragment } from 'react'; +import { isMacOs, KEY_MAP } from '@graphiql/react'; + +function withMacOS(key: string) { + return isMacOs ? key.replace('Ctrl', '⌘') : key; +} + +const SHORT_KEYS = Object.entries({ + 'Search in editor': withMacOS(KEY_MAP.searchInEditor[0]), + 'Search in documentation': withMacOS(KEY_MAP.searchInDocs[0]), + 'Execute query': withMacOS(KEY_MAP.runQuery[0]), + 'Prettify editors': KEY_MAP.prettify[0], + 'Merge fragments definitions into operation definition': + KEY_MAP.mergeFragments[0], + 'Copy query': KEY_MAP.copyQuery[0], + 'Re-fetch schema using introspection': KEY_MAP.refetchSchema[0], +}); + +interface ShortKeysProps { + /** @default 'sublime' */ + keyMap?: string; +} + +export const ShortKeys: FC = ({ keyMap = 'sublime' }) => { + return ( +
+ + + + + + + + + {SHORT_KEYS.map(([title, keys]) => ( + + + + + ))} + +
Short KeyFunction
+ {keys.split('-').map((key, index, array) => ( + + {key} + {index !== array.length - 1 && ' + '} + + ))} + {title}
+

+ The editors use{' '} + + CodeMirror Key Maps + {' '} + that add more short keys. This instance of GraphiQL uses{' '} + {keyMap}. +

+
+ ); +}; diff --git a/packages/graphiql/src/ui/toolbar.tsx b/packages/graphiql/src/ui/toolbar.tsx new file mode 100644 index 00000000000..c35c5f4e8a0 --- /dev/null +++ b/packages/graphiql/src/ui/toolbar.tsx @@ -0,0 +1,65 @@ +import type { FC, ReactNode } from 'react'; +import { + CopyIcon, + copyQuery, + KEY_MAP, + MergeIcon, + mergeQuery, + prettifyEditors, + PrettifyIcon, + ToolbarButton, +} from '@graphiql/react'; + +const DefaultToolbarRenderProps: FC<{ + prettify: ReactNode; + copy: ReactNode; + merge: ReactNode; +}> = ({ prettify, copy, merge }) => ( + <> + {prettify} + {merge} + {copy} + +); + +/** + * Configure the UI by providing this Component as a child of GraphiQL. + */ +export const GraphiQLToolbar: FC<{ + children?: typeof DefaultToolbarRenderProps; +}> = ({ children = DefaultToolbarRenderProps }) => { + if (typeof children !== 'function') { + throw new TypeError( + 'The `GraphiQL.Toolbar` component requires a render prop function as its child.', + ); + } + + const prettify = ( + + + ); + + const merge = ( + + + ); + + const copy = ( + + + ); + + return children({ prettify, copy, merge }); +}; From 319cb3694603361c13b67be727247293ff2903ab Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:22:59 +0200 Subject: [PATCH 143/255] refactor --- packages/graphiql/src/GraphiQL.tsx | 493 +++++++++++++++-------------- 1 file changed, 247 insertions(+), 246 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 894d31ff548..e17505ec062 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -422,254 +422,59 @@ export const GraphiQLInterface: FC = ({ changeTab(index); }; - return ( -
-
- {plugins.map((plugin, index) => { - const isVisible = plugin === visiblePlugin; - const label = `${isVisible ? 'Hide' : 'Show'} ${plugin.title}`; - return ( - - - - - ); - })} - - - - - - - - - - - - -
-
-
+ {plugins.map((plugin, index) => { + const isVisible = plugin === visiblePlugin; + const label = `${isVisible ? 'Hide' : 'Show'} ${plugin.title}`; + return ( + + + + + ); + })} + + - {PluginContent ? : null} -
- {visiblePlugin && ( - ); + + const editors = ( +
+
+ +
+ + {toolbar} +
+
+ +
+ + Variables + + {isHeadersEditorEnabled && ( + + Headers + + )} + + + + {editorToolsResize.hiddenElement === 'second' ? ( + + +
+ +
+ + {isHeadersEditorEnabled && ( + + )} +
+
+ ); + + return ( +
+ {sidebar} +
+
+ {PluginContent ? : null} +
+ {visiblePlugin && ( +
+ )} +
+
+ + {tabs.map((tab, index, arr) => ( + + + {tab.title} + + {arr.length > 1 && } + + ))} + + + + + + {logo} +
+
+ {editors} +
+
+ {isExecutionFetching && } + + {footer} +
+
+
+
+
+ ); }; function getChildComponentType(child: ReactNode) { From 0623c2dfb3f77ae0869734cfb9d78c70effcd4a0 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:25:31 +0200 Subject: [PATCH 144/255] upd --- packages/graphiql/src/GraphiQL.tsx | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index e17505ec062..7f8fc65a57c 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -591,6 +591,11 @@ export const GraphiQLInterface: FC = ({
); + const editorToolsText = + editorToolsResize.hiddenElement === 'second' + ? 'Show editor tools' + : 'Hide editor tools'; + const editors = (
= ({ )} - + {editorToolsResize.hiddenElement === 'second' ? ( From f8cecc2e3d435a88d34eeb6dbb1c98a1b927f76d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:28:40 +0200 Subject: [PATCH 145/255] upd --- packages/graphiql/src/GraphiQL.tsx | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 7f8fc65a57c..dabf418f978 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -596,6 +596,11 @@ export const GraphiQLInterface: FC = ({ ? 'Show editor tools' : 'Hide editor tools'; + const EditorToolsIcon = + editorToolsResize.hiddenElement === 'second' + ? ChevronUpIcon + : ChevronDownIcon; + const editors = (
= ({ aria-label={editorToolsText} className="graphiql-toggle-editor-tools" > - {editorToolsResize.hiddenElement === 'second' ? ( -
From 7320d0dc3ee860f887417953cb41ebf7db073f1f Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:31:23 +0200 Subject: [PATCH 146/255] upd --- packages/graphiql/src/GraphiQL.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index dabf418f978..b68d7a44a68 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -591,10 +591,7 @@ export const GraphiQLInterface: FC = ({
); - const editorToolsText = - editorToolsResize.hiddenElement === 'second' - ? 'Show editor tools' - : 'Hide editor tools'; + const editorToolsText = `${editorToolsResize.hiddenElement === 'second' ? 'Show' : 'Hide'} editor tools`; const EditorToolsIcon = editorToolsResize.hiddenElement === 'second' From fe17c0b00f5969aabe2190a1474b5795e8242d46 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:33:14 +0200 Subject: [PATCH 147/255] upd --- packages/graphiql/src/GraphiQL.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index b68d7a44a68..743653e47cc 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -55,12 +55,11 @@ import { GraphiQLLogo, GraphiQLToolbar, GraphiQLFooter, ShortKeys } from './ui'; * * https://graphiql-test.netlify.app/typedoc/modules/graphiql.html#graphiqlprops */ -export type GraphiQLProps = - // - Omit, 'children'> & - Omit, 'children'> & - // `children` prop should be optional - GraphiQLInterfaceProps; +export interface GraphiQLProps + // `children` prop should be optional + extends GraphiQLInterfaceProps, + Omit, 'children'>, + Omit, 'children'> {} /** * The top-level React component for GraphiQL, intended to encompass the entire From df29d87753d6c40a95ddd16a0f6e792535a6da17 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:33:58 +0200 Subject: [PATCH 148/255] upd --- packages/graphiql/src/GraphiQL.tsx | 94 +++++++++++++++--------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 743653e47cc..a7b35a6f7b3 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -149,52 +149,54 @@ type VariableEditorProps = ComponentPropsWithoutRef; type HeaderEditorProps = ComponentPropsWithoutRef; type ResponseEditorProps = ComponentPropsWithoutRef; -export type GraphiQLInterfaceProps = WriteableEditorProps & - AddSuffix, 'Query'> & - AddSuffix, 'Variables'> & - AddSuffix, 'Headers'> & - Pick & { - children?: ReactNode; - /** - * Set the default state for the editor tools. - * - `false` hides the editor tools - * - `true` shows the editor tools - * - `'variables'` specifically shows the variables editor - * - `'headers'` specifically shows the headers editor - * By default, the editor tools are initially shown when at least one of the - * editors has contents. - */ - defaultEditorToolsVisibility?: boolean | 'variables' | 'headers'; - /** - * Toggle if the headers' editor should be shown inside the editor tools. - * @default true - */ - isHeadersEditorEnabled?: boolean; - /** - * Indicates if settings for persisting headers should appear in the - * settings modal. - */ - showPersistHeadersSettings?: boolean; - defaultTheme?: Theme; - /** - * `forcedTheme` allows enforcement of a specific theme for GraphiQL. - * This is useful when you want to make sure that GraphiQL is always - * rendered with a specific theme. - */ - forcedTheme?: (typeof THEMES)[number]; - /** - * Additional class names which will be appended to the container element. - */ - className?: string; - /** - * When the user clicks a close tab button, this function is invoked with - * the index of the tab that is about to be closed. It can return a promise - * that should resolve to `true` (meaning the tab may be closed) or `false` - * (meaning the tab may not be closed). - * @param index The index of the tab that should be closed. - */ - confirmCloseTab?(index: number): Promise | boolean; - }; +export interface GraphiQLInterfaceProps + extends WriteableEditorProps, + AddSuffix, 'Query'>, + AddSuffix, 'Variables'>, + AddSuffix, 'Headers'>, + Pick { + children?: ReactNode; + /** + * Set the default state for the editor tools. + * - `false` hides the editor tools + * - `true` shows the editor tools + * - `'variables'` specifically shows the variables editor + * - `'headers'` specifically shows the headers editor + * By default, the editor tools are initially shown when at least one of the + * editors has contents. + */ + defaultEditorToolsVisibility?: boolean | 'variables' | 'headers'; + /** + * Toggle if the headers' editor should be shown inside the editor tools. + * @default true + */ + isHeadersEditorEnabled?: boolean; + /** + * Indicates if settings for persisting headers should appear in the + * settings modal. + */ + showPersistHeadersSettings?: boolean; + defaultTheme?: Theme; + /** + * `forcedTheme` allows enforcement of a specific theme for GraphiQL. + * This is useful when you want to make sure that GraphiQL is always + * rendered with a specific theme. + */ + forcedTheme?: (typeof THEMES)[number]; + /** + * Additional class names which will be appended to the container element. + */ + className?: string; + + /** + * When the user clicks a close tab button, this function is invoked with + * the index of the tab that is about to be closed. It can return a promise + * that should resolve to `true` (meaning the tab may be closed) or `false` + * (meaning the tab may not be closed). + * @param index The index of the tab that should be closed. + */ + confirmCloseTab?(index: number): Promise | boolean; +} const THEMES = ['light', 'dark', 'system'] as const; From e63113b7f272d2f43d4cd3584c8021e74ccc4afa Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 14:49:42 +0200 Subject: [PATCH 149/255] upd --- packages/graphiql/src/GraphiQL.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index a7b35a6f7b3..78fc0185cea 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -268,20 +268,15 @@ export const GraphiQLInterface: FC = ({ const editorToolsResize = useDragResize({ defaultSizeRelation: 3, direction: 'vertical', - initiallyHidden: (() => { - if ( - defaultEditorToolsVisibility === 'variables' || - defaultEditorToolsVisibility === 'headers' - ) { + initiallyHidden: ((d: typeof defaultEditorToolsVisibility) => { + if (d === 'variables' || d === 'headers') { return; } - - if (typeof defaultEditorToolsVisibility === 'boolean') { - return defaultEditorToolsVisibility ? undefined : 'second'; + if (typeof d === 'boolean') { + return d ? undefined : 'second'; } - return initialVariables || initialHeaders ? undefined : 'second'; - })(), + })(defaultEditorToolsVisibility), sizeThresholdSecond: 60, storageKey: 'secondaryEditorFlex', }); From 537a8f9926035afcbafd345af9e4a6c659fa235b Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 15 May 2025 17:01:58 +0200 Subject: [PATCH 150/255] lint fix --- packages/graphiql/src/ui/{index.tsx => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/graphiql/src/ui/{index.tsx => index.ts} (100%) diff --git a/packages/graphiql/src/ui/index.tsx b/packages/graphiql/src/ui/index.ts similarity index 100% rename from packages/graphiql/src/ui/index.tsx rename to packages/graphiql/src/ui/index.ts From 3f701bae26953d84f14a6a0dad63c202f4223455 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Mon, 26 May 2025 13:06:32 +0200 Subject: [PATCH 151/255] all in one --- .changeset/soft-cars-notice.md | 7 + .eslintrc.js | 10 +- cspell.json | 1 + package.json | 9 +- .../src/utils/SchemaReference.ts | 1 - .../package.json | 2 +- .../graphiql-plugin-doc-explorer/package.json | 2 +- .../setup-files.ts | 32 + .../src/context.ts | 29 +- .../src/schema-reference.ts | 147 +++++ .../vitest.config.mts | 10 + .../graphiql-plugin-explorer/package.json | 2 +- packages/graphiql-plugin-history/package.json | 2 +- .../graphiql-plugin-history/setup-files.ts | 32 + .../src/__tests__/components.spec.tsx | 7 +- .../graphiql-plugin-history/vitest.config.mts | 10 + packages/graphiql-react/package.json | 26 +- packages/graphiql-react/setup-files.ts | 33 + .../button-group/index.css} | 0 .../button-group/index.tsx} | 7 +- .../button/index.css} | 0 .../src/components/button/index.tsx | 39 ++ .../dialog/index.css} | 0 .../dialog/index.tsx} | 12 +- .../dropdown-menu/index.css} | 0 .../dropdown-menu/index.tsx} | 2 +- .../execute-button/index.css} | 0 .../execute-button/index.tsx} | 32 +- .../src/components/header-editor.tsx | 65 ++ .../{editor => components}/image-preview.tsx | 46 +- .../graphiql-react/src/components/index.ts | 20 + .../markdown-content/index.css} | 0 .../src/components/markdown-content/index.tsx | 27 + .../src/{ => components}/provider.tsx | 33 +- .../src/components/query-editor.tsx | 342 ++++++++++ .../src/components/response-editor.tsx | 151 +++++ .../spinner/index.css} | 0 .../src/components/spinner/index.tsx | 15 + .../tabs.css => components/tabs/index.css} | 2 +- .../tabs.tsx => components/tabs/index.tsx} | 46 +- .../toolbar-button/index.css} | 0 .../src/components/toolbar-button/index.tsx | 54 ++ .../toolbar-menu/index.css} | 0 .../toolbar-menu/index.tsx} | 23 +- .../tooltip/index.css} | 0 .../tooltip/index.tsx} | 2 +- .../src/components/variable-editor.tsx | 75 +++ packages/graphiql-react/src/constants.ts | 116 +++- .../src/editor/__tests__/common.spec.ts | 12 - packages/graphiql-react/src/editor/common.ts | 54 -- .../graphiql-react/src/editor/completion.ts | 241 ------- .../src/editor/header-editor.tsx | 136 ---- packages/graphiql-react/src/editor/index.ts | 24 - .../src/editor/query-editor.tsx | 463 -------------- .../src/editor/response-editor.tsx | 145 ----- packages/graphiql-react/src/editor/types.ts | 31 - .../src/editor/variable-editor.tsx | 145 ----- packages/graphiql-react/src/env.d.ts | 6 + packages/graphiql-react/src/index.ts | 39 +- packages/graphiql-react/src/monaco-editor.ts | 1 + .../graphiql-react/src/setup-workers/vite.ts | 27 + .../src/setup-workers/webpack.ts | 25 + .../src/stores/{editor.tsx => editor.ts} | 110 ++-- .../stores/{execution.tsx => execution.ts} | 108 ++-- packages/graphiql-react/src/stores/index.ts | 7 +- .../src/stores/{plugin.tsx => plugin.ts} | 7 +- packages/graphiql-react/src/stores/schema.ts | 3 +- .../src/stores/{storage.tsx => storage.ts} | 1 - packages/graphiql-react/src/stores/theme.ts | 100 +++ .../src/style/auto-insertion.css | 4 +- .../graphiql-react/src/style/codemirror.css | 14 - packages/graphiql-react/src/style/editor.css | 38 +- packages/graphiql-react/src/style/fold.css | 2 - packages/graphiql-react/src/style/hint.css | 2 - packages/graphiql-react/src/style/lint.css | 2 - packages/graphiql-react/src/style/root.css | 7 +- packages/graphiql-react/src/theme.ts | 44 -- .../graphiql-react/src/toolbar/button.tsx | 50 -- packages/graphiql-react/src/toolbar/index.ts | 3 - packages/graphiql-react/src/types.ts | 18 + packages/graphiql-react/src/ui/button.tsx | 37 -- packages/graphiql-react/src/ui/index.ts | 8 - packages/graphiql-react/src/ui/markdown.tsx | 28 - packages/graphiql-react/src/ui/spinner.tsx | 15 - .../src/utility/create-editor.ts | 271 ++++++++ .../src/{editor => utility}/hooks.ts | 285 ++++----- packages/graphiql-react/src/utility/index.ts | 20 + packages/graphiql-react/src/utility/jsonc.ts | 42 ++ packages/graphiql-react/src/utility/resize.ts | 132 ++-- .../__tests__ => utility}/tabs.spec.ts | 4 +- .../src/{editor => utility}/tabs.ts | 14 +- packages/graphiql-react/vite.config.mts | 48 +- packages/graphiql-react/vitest.config.mts | 11 + packages/graphiql/__mocks__/codemirror.ts | 70 --- packages/graphiql/cypress/e2e/errors.cy.ts | 12 +- .../graphiql/cypress/e2e/graphql-ws.cy.ts | 12 +- packages/graphiql/cypress/e2e/init.cy.ts | 4 +- packages/graphiql/cypress/e2e/keyboard.cy.ts | 4 +- packages/graphiql/cypress/e2e/lint.cy.ts | 22 +- packages/graphiql/cypress/e2e/prettify.cy.ts | 20 +- packages/graphiql/cypress/env.d.ts | 8 + packages/graphiql/cypress/support/commands.ts | 110 ++-- packages/graphiql/index.html | 25 +- packages/graphiql/package.json | 16 +- packages/graphiql/setup-files.ts | 128 +++- packages/graphiql/src/GraphiQL.spec.tsx | 30 +- packages/graphiql/src/GraphiQL.tsx | 68 +- packages/graphiql/src/cdn.ts | 5 +- packages/graphiql/src/e2e.ts | 65 +- packages/graphiql/src/env.d.ts | 3 - packages/graphiql/src/index.ts | 17 +- packages/graphiql/src/setup-workers/vite.ts | 1 + .../graphiql/src/setup-workers/webpack.ts | 1 + packages/graphiql/src/style.css | 3 + packages/graphiql/src/ui/short-keys.tsx | 117 ++-- packages/graphiql/src/ui/toolbar.tsx | 6 +- packages/graphiql/tsconfig.json | 21 +- packages/graphiql/vite.config.mts | 80 ++- packages/graphiql/vitest.config.mts | 10 + .../src/__tests__/MessageProcessor.test.ts | 4 +- .../src/interface/getDiagnostics.ts | 6 +- .../src/interface/getHoverInformation.ts | 28 +- .../src/utils/getVariablesJSONSchema.ts | 1 + packages/monaco-graphql/package.json | 14 +- packages/monaco-graphql/src/GraphQLWorker.ts | 23 +- .../monaco-graphql/src/languageFeatures.ts | 3 +- packages/monaco-graphql/src/utils.ts | 43 +- resources/custom-words.txt | 2 + turbo.json | 3 + yarn.lock | 595 +++++++++++++----- 130 files changed, 3224 insertions(+), 2611 deletions(-) create mode 100644 .changeset/soft-cars-notice.md create mode 100644 packages/graphiql-plugin-doc-explorer/src/schema-reference.ts create mode 100644 packages/graphiql-react/setup-files.ts rename packages/graphiql-react/src/{ui/button-group.css => components/button-group/index.css} (100%) rename packages/graphiql-react/src/{ui/button-group.tsx => components/button-group/index.tsx} (68%) rename packages/graphiql-react/src/{ui/button.css => components/button/index.css} (100%) create mode 100644 packages/graphiql-react/src/components/button/index.tsx rename packages/graphiql-react/src/{ui/dialog.css => components/dialog/index.css} (100%) rename packages/graphiql-react/src/{ui/dialog.tsx => components/dialog/index.tsx} (76%) rename packages/graphiql-react/src/{ui/dropdown.css => components/dropdown-menu/index.css} (100%) rename packages/graphiql-react/src/{ui/dropdown.tsx => components/dropdown-menu/index.tsx} (98%) rename packages/graphiql-react/src/{toolbar/execute.css => components/execute-button/index.css} (100%) rename packages/graphiql-react/src/{toolbar/execute.tsx => components/execute-button/index.tsx} (69%) create mode 100644 packages/graphiql-react/src/components/header-editor.tsx rename packages/graphiql-react/src/{editor => components}/image-preview.tsx (55%) create mode 100644 packages/graphiql-react/src/components/index.ts rename packages/graphiql-react/src/{ui/markdown.css => components/markdown-content/index.css} (100%) create mode 100644 packages/graphiql-react/src/components/markdown-content/index.tsx rename packages/graphiql-react/src/{ => components}/provider.tsx (69%) create mode 100644 packages/graphiql-react/src/components/query-editor.tsx create mode 100644 packages/graphiql-react/src/components/response-editor.tsx rename packages/graphiql-react/src/{ui/spinner.css => components/spinner/index.css} (100%) create mode 100644 packages/graphiql-react/src/components/spinner/index.tsx rename packages/graphiql-react/src/{ui/tabs.css => components/tabs/index.css} (96%) rename packages/graphiql-react/src/{ui/tabs.tsx => components/tabs/index.tsx} (72%) rename packages/graphiql-react/src/{toolbar/button.css => components/toolbar-button/index.css} (100%) create mode 100644 packages/graphiql-react/src/components/toolbar-button/index.tsx rename packages/graphiql-react/src/{toolbar/menu.css => components/toolbar-menu/index.css} (100%) rename packages/graphiql-react/src/{toolbar/menu.tsx => components/toolbar-menu/index.tsx} (67%) rename packages/graphiql-react/src/{ui/tooltip.css => components/tooltip/index.css} (100%) rename packages/graphiql-react/src/{ui/tooltip.tsx => components/tooltip/index.tsx} (96%) create mode 100644 packages/graphiql-react/src/components/variable-editor.tsx delete mode 100644 packages/graphiql-react/src/editor/__tests__/common.spec.ts delete mode 100644 packages/graphiql-react/src/editor/common.ts delete mode 100644 packages/graphiql-react/src/editor/completion.ts delete mode 100644 packages/graphiql-react/src/editor/header-editor.tsx delete mode 100644 packages/graphiql-react/src/editor/index.ts delete mode 100644 packages/graphiql-react/src/editor/query-editor.tsx delete mode 100644 packages/graphiql-react/src/editor/response-editor.tsx delete mode 100644 packages/graphiql-react/src/editor/types.ts delete mode 100644 packages/graphiql-react/src/editor/variable-editor.tsx create mode 100644 packages/graphiql-react/src/env.d.ts create mode 100644 packages/graphiql-react/src/monaco-editor.ts create mode 100644 packages/graphiql-react/src/setup-workers/vite.ts create mode 100644 packages/graphiql-react/src/setup-workers/webpack.ts rename packages/graphiql-react/src/stores/{editor.tsx => editor.ts} (87%) rename packages/graphiql-react/src/stores/{execution.tsx => execution.ts} (84%) rename packages/graphiql-react/src/stores/{plugin.tsx => plugin.ts} (96%) rename packages/graphiql-react/src/stores/{storage.tsx => storage.ts} (94%) create mode 100644 packages/graphiql-react/src/stores/theme.ts delete mode 100644 packages/graphiql-react/src/theme.ts delete mode 100644 packages/graphiql-react/src/toolbar/button.tsx delete mode 100644 packages/graphiql-react/src/toolbar/index.ts create mode 100644 packages/graphiql-react/src/types.ts delete mode 100644 packages/graphiql-react/src/ui/button.tsx delete mode 100644 packages/graphiql-react/src/ui/index.ts delete mode 100644 packages/graphiql-react/src/ui/markdown.tsx delete mode 100644 packages/graphiql-react/src/ui/spinner.tsx create mode 100644 packages/graphiql-react/src/utility/create-editor.ts rename packages/graphiql-react/src/{editor => utility}/hooks.ts (51%) create mode 100644 packages/graphiql-react/src/utility/jsonc.ts rename packages/graphiql-react/src/{editor/__tests__ => utility}/tabs.spec.ts (98%) rename packages/graphiql-react/src/{editor => utility}/tabs.ts (97%) delete mode 100644 packages/graphiql/__mocks__/codemirror.ts create mode 100644 packages/graphiql/cypress/env.d.ts delete mode 100644 packages/graphiql/src/env.d.ts create mode 100644 packages/graphiql/src/setup-workers/vite.ts create mode 100644 packages/graphiql/src/setup-workers/webpack.ts diff --git a/.changeset/soft-cars-notice.md b/.changeset/soft-cars-notice.md new file mode 100644 index 00000000000..eebddd05220 --- /dev/null +++ b/.changeset/soft-cars-notice.md @@ -0,0 +1,7 @@ +--- +'graphiql': major +'@graphiql/react': minor +'monaco-graphql': minor +--- + +replace Codemirror editor with Monaco editor and Monaco GraphQL diff --git a/.eslintrc.js b/.eslintrc.js index 9d097d4f6d7..490ba4abb5b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -393,6 +393,7 @@ module.exports = { '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-unnecessary-type-conversion': 'error', + // '@typescript-eslint/await-thenable': 'error', // TODO // TODO: Fix all errors for the following rules included in recommended config '@typescript-eslint/no-deprecated': 'off', '@typescript-eslint/no-unsafe-function-type': 'off', @@ -409,11 +410,10 @@ module.exports = { projectService: { allowDefaultProject: [ 'examples/monaco-graphql-react-vite/vite.config.ts', - 'packages/graphiql/vite.config.mts', - 'packages/{codemirror-graphql,graphiql-toolkit,graphql-language-service-cli,graphql-language-service,monaco-graphql,vscode-graphql-syntax,graphiql}/vitest.config.mts', + 'packages/{codemirror-graphql,graphiql-toolkit,graphql-language-service-cli,graphql-language-service,monaco-graphql,vscode-graphql-syntax}/vitest.config.mts', 'packages/cm6-graphql/__tests__/test.spec.ts', - 'packages/graphiql/src/GraphiQL.spec.tsx', + 'packages/graphiql/cypress.config.ts', 'packages/vscode-graphql-syntax/tests/*.spec.ts', 'packages/graphql-language-service-cli/src/__tests__/*.test.ts', 'packages/monaco-graphql/test/monaco-editor.test.ts', @@ -515,7 +515,9 @@ module.exports = { rules: { '@typescript-eslint/no-restricted-imports': [ 'error', - ...RESTRICTED_IMPORTS, + ...RESTRICTED_IMPORTS + // TODO: enable when monaco-editor will be migrated over codemirror + .filter(({ name }) => name !== 'monaco-editor'), { name: 'react', importNames: ['memo', 'useCallback', 'useMemo'], diff --git a/cspell.json b/cspell.json index e08e52185cc..5eb97c0b4b2 100644 --- a/cspell.json +++ b/cspell.json @@ -19,6 +19,7 @@ "**/esbuild.js", ".eslintrc.js", ".vscode/extensions.json", + "packages/monaco-graphql/test/monaco-editor.test.ts", "working-group" ], "files": ["**/*.{js,cjs,mjs,ts,jsx,tsx,md,mdx,html,json,css,toml,yaml,yml}"] diff --git a/package.json b/package.json index 17408ba4ea0..6f446eb8c22 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "examples/monaco-graphql-webpack", "examples/monaco-graphql-nextjs", "examples/monaco-graphql-react-vite", + "examples/graphiql-vite", + "examples/graphiql-nextjs", "examples/graphiql-webpack" ] }, @@ -16,6 +18,8 @@ "scripts": { "types:check": "turbo run types:check", "dev:graphiql": "turbo run dev --filter=graphiql...", + "dev:example-nextjs": "turbo run dev --filter=example-graphiql-nextjs...", + "dev:example-vite": "turbo run dev --filter=example-graphiql-vite...", "build:graphiql": "turbo run build --filter=graphiql...", "build": "yarn build-clean && yarn tsc && yarn build:nontsc", "build-bundles": "yarn prebuild-bundles && yarn wsrun:noexamples --stages build-bundles", @@ -30,7 +34,7 @@ "watch-vscode": "yarn tsc && yarn workspace vscode-graphql compile", "watch-vscode-exec": "yarn workspace vscode-graphql-execution compile", "check": "yarn tsc --noEmit", - "cypress-open": "yarn workspace graphiql cypress-open", + "cypress-open": "concurrently 'yarn dev:graphiql' 'yarn workspace graphiql cypress-open'", "e2e": "yarn workspace graphiql e2e", "eslint": "NODE_OPTIONS=--max-old-space-size=4096 ESLINT_USE_FLAT_CONFIG=false eslint --max-warnings=0 --ignore-path .gitignore --cache .", "format": "yarn eslint --fix && yarn pretty", @@ -57,7 +61,7 @@ "test:coverage": "yarn jest --coverage", "test:watch": "yarn jest --watch", "tsc": "tsc --build", - "vitest": "yarn wsrun -p -m test", + "vitest": "turbo run test", "wsrun:noexamples": "wsrun --exclude-missing --exclude example-monaco-graphql-react-vite --exclude example-monaco-graphql-nextjs --exclude example-monaco-graphql-webpack --exclude example-graphiql-webpack", "gen-agenda": "wgutils agenda gen" }, @@ -125,6 +129,7 @@ "turbo": "^2.5.2" }, "resolutions": { + "monaco-editor": "0.47.0", "@babel/traverse": "^7.23.2", "vscode-languageserver-types": "3.17.3", "markdown-it": "14.1.0", diff --git a/packages/codemirror-graphql/src/utils/SchemaReference.ts b/packages/codemirror-graphql/src/utils/SchemaReference.ts index 60afc203b5c..be5c552da51 100644 --- a/packages/codemirror-graphql/src/utils/SchemaReference.ts +++ b/packages/codemirror-graphql/src/utils/SchemaReference.ts @@ -100,7 +100,6 @@ export function getEnumValueReference(typeInfo: TypeInfo): EnumValueReference { return { kind: 'EnumValue', value: typeInfo.enumValue || undefined, - // $FlowFixMe type: typeInfo.inputType ? (getNamedType(typeInfo.inputType) as GraphQLEnumType) : undefined, diff --git a/packages/graphiql-plugin-code-exporter/package.json b/packages/graphiql-plugin-code-exporter/package.json index 5ef1825444f..2fed491fb5f 100644 --- a/packages/graphiql-plugin-code-exporter/package.json +++ b/packages/graphiql-plugin-code-exporter/package.json @@ -28,7 +28,7 @@ }, "scripts": { "types:check": "tsc --noEmit", - "dev": "vite build --watch", + "dev": "vite build --watch --emptyOutDir=false", "build": "vite build && UMD=true vite build", "postbuild": "cp src/graphiql-code-exporter.d.ts dist/graphiql-code-exporter.d.ts" }, diff --git a/packages/graphiql-plugin-doc-explorer/package.json b/packages/graphiql-plugin-doc-explorer/package.json index 08eb2948cfb..f39a6b0b69b 100644 --- a/packages/graphiql-plugin-doc-explorer/package.json +++ b/packages/graphiql-plugin-doc-explorer/package.json @@ -31,7 +31,7 @@ ], "scripts": { "types:check": "tsc --noEmit", - "dev": "vite build --watch", + "dev": "vite build --watch --emptyOutDir=false", "build": "vite build", "test": "vitest" }, diff --git a/packages/graphiql-plugin-doc-explorer/setup-files.ts b/packages/graphiql-plugin-doc-explorer/setup-files.ts index 276c7c4f7a3..3079d90df15 100644 --- a/packages/graphiql-plugin-doc-explorer/setup-files.ts +++ b/packages/graphiql-plugin-doc-explorer/setup-files.ts @@ -3,3 +3,35 @@ import '@testing-library/jest-dom'; vi.mock('zustand'); // to make it works like Jest (auto-mocking) + +/** + * Fixes TypeError: document.queryCommandSupported is not a function + */ +if (!navigator.clipboard) { + Object.defineProperty(navigator, 'clipboard', { + writable: false, + value: { + write: async () => null, + }, + }); +} + +/** + * Fixes TypeError: mainWindow.matchMedia is not a function + * @see https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + */ +if (!window.matchMedia) { + Object.defineProperty(window, 'matchMedia', { + writable: false, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); +} diff --git a/packages/graphiql-plugin-doc-explorer/src/context.ts b/packages/graphiql-plugin-doc-explorer/src/context.ts index 2483e1fe5d5..444e615bfee 100644 --- a/packages/graphiql-plugin-doc-explorer/src/context.ts +++ b/packages/graphiql-plugin-doc-explorer/src/context.ts @@ -21,6 +21,7 @@ import { createBoundedUseStore, } from '@graphiql/react'; import { createStore } from 'zustand'; +import { getSchemaReference } from './schema-reference'; export type DocExplorerFieldDef = | GraphQLField @@ -114,36 +115,42 @@ export const docExplorerStore = createStore( if (!schemaReference) { return; } + const { kind, typeInfo } = schemaReference; + const ref = getSchemaReference(kind, typeInfo); + if (!ref) { + return; + } + const { push } = get().actions; - switch (schemaReference.kind) { + switch (ref.kind) { case 'Type': { push({ - name: schemaReference.type.name, - def: schemaReference.type, + name: ref.type.name, + def: ref.type, }); break; } case 'Field': { push({ - name: schemaReference.field.name, - def: schemaReference.field, + name: ref.field.name, + def: ref.field, }); break; } case 'Argument': { - if (schemaReference.field) { + if (ref.field) { push({ - name: schemaReference.field.name, - def: schemaReference.field, + name: ref.field.name, + def: ref.field, }); } break; } case 'EnumValue': { - if (schemaReference.type) { + if (ref.type) { push({ - name: schemaReference.type.name, - def: schemaReference.type, + name: ref.type.name, + def: ref.type, }); } break; diff --git a/packages/graphiql-plugin-doc-explorer/src/schema-reference.ts b/packages/graphiql-plugin-doc-explorer/src/schema-reference.ts new file mode 100644 index 00000000000..7bac0a639c8 --- /dev/null +++ b/packages/graphiql-plugin-doc-explorer/src/schema-reference.ts @@ -0,0 +1,147 @@ +import { getNamedType } from 'graphql'; +import type { + GraphQLEnumType, + GraphQLNamedType, + GraphQLField, + GraphQLArgument, + GraphQLDirective, + GraphQLSchema, + GraphQLEnumValue, + GraphQLInputFieldMap, + GraphQLInputType, + GraphQLType, +} from 'graphql'; +import { Maybe } from 'graphql/jsutils/Maybe'; + +/** + * Copied from packages/codemirror-graphql/src/jump.ts + */ +export function getSchemaReference(kind: string, typeInfo: any) { + if ( + (kind === 'Field' && typeInfo.fieldDef) || + (kind === 'AliasedField' && typeInfo.fieldDef) + ) { + return getFieldReference(typeInfo); + } + if (kind === 'Directive' && typeInfo.directiveDef) { + return getDirectiveReference(typeInfo); + } + if (kind === 'Argument' && typeInfo.argDef) { + return getArgumentReference(typeInfo); + } + if (kind === 'EnumValue' && typeInfo.enumValue) { + return getEnumValueReference(typeInfo); + } + if (kind === 'NamedType' && typeInfo.type) { + return getTypeReference(typeInfo); + } +} + +function getArgumentReference(typeInfo: any): ArgumentReference { + return typeInfo.directiveDef + ? { + kind: 'Argument', + schema: typeInfo.schema, + argument: typeInfo.argDef, + directive: typeInfo.directiveDef, + } + : { + kind: 'Argument', + schema: typeInfo.schema, + argument: typeInfo.argDef, + field: typeInfo.fieldDef, + type: isMetaField(typeInfo.fieldDef) ? null : typeInfo.parentType, + }; +} + +function getDirectiveReference(typeInfo: any): DirectiveReference { + return { + kind: 'Directive', + schema: typeInfo.schema, + directive: typeInfo.directiveDef, + }; +} + +function getFieldReference(typeInfo: any): FieldReference { + return { + kind: 'Field', + schema: typeInfo.schema, + field: typeInfo.fieldDef, + type: isMetaField(typeInfo.fieldDef) ? null : typeInfo.parentType, + }; +} + +// Note: for reusability, getTypeReference can produce a reference to any type, +// though it defaults to the current type. +function getTypeReference( + typeInfo: any, + type?: Maybe, +): TypeReference { + return { + kind: 'Type', + schema: typeInfo.schema, + type: type || typeInfo.type, + }; +} + +function getEnumValueReference(typeInfo: TypeInfo): EnumValueReference { + return { + kind: 'EnumValue', + value: typeInfo.enumValue || undefined, + type: typeInfo.inputType + ? (getNamedType(typeInfo.inputType) as GraphQLEnumType) + : undefined, + }; +} + +function isMetaField(fieldDef: GraphQLField) { + return fieldDef.name.slice(0, 2) === '__'; +} + +type ArgumentReference = { + kind: 'Argument'; + argument: GraphQLArgument; + field?: GraphQLField; + type?: GraphQLNamedType; + directive?: GraphQLDirective; + schema?: GraphQLSchema; +}; + +type DirectiveReference = { + kind: 'Directive'; + directive: GraphQLDirective; + schema?: GraphQLSchema; +}; + +type EnumValueReference = { + kind: 'EnumValue'; + value?: GraphQLEnumValue; + type?: GraphQLEnumType; + schema?: GraphQLSchema; +}; + +type FieldReference = { + kind: 'Field'; + field: GraphQLField; + type: Maybe; + schema?: GraphQLSchema; +}; + +type TypeReference = { + kind: 'Type'; + type: GraphQLNamedType; + schema?: GraphQLSchema; +}; + +interface TypeInfo { + schema: GraphQLSchema; + type?: Maybe; + parentType?: Maybe; + inputType?: Maybe; + directiveDef?: Maybe; + fieldDef?: Maybe>; + argDef?: Maybe; + argDefs?: Maybe; + enumValue?: Maybe; + objectFieldDefs?: Maybe; +} diff --git a/packages/graphiql-plugin-doc-explorer/vitest.config.mts b/packages/graphiql-plugin-doc-explorer/vitest.config.mts index 2a31c6fda84..310795fe31f 100644 --- a/packages/graphiql-plugin-doc-explorer/vitest.config.mts +++ b/packages/graphiql-plugin-doc-explorer/vitest.config.mts @@ -1,3 +1,4 @@ +import path from 'node:path'; import { defineConfig } from 'vitest/config'; import { plugins } from './vite.config.mjs'; @@ -7,5 +8,14 @@ export default defineConfig({ globals: true, environment: 'jsdom', setupFiles: ['./setup-files.ts'], + alias: [ + { + // Fixes Error: Failed to resolve entry for package "monaco-editor". The package may have incorrect main/module/exports specified in its package.json. + find: /^monaco-editor$/, + replacement: path.resolve( + '../../node_modules/monaco-editor/esm/vs/editor/editor.api', + ), + }, + ], }, }); diff --git a/packages/graphiql-plugin-explorer/package.json b/packages/graphiql-plugin-explorer/package.json index 124ac934e72..7c2dd94d83e 100644 --- a/packages/graphiql-plugin-explorer/package.json +++ b/packages/graphiql-plugin-explorer/package.json @@ -27,7 +27,7 @@ }, "scripts": { "types:check": "tsc --noEmit", - "dev": "vite build --watch", + "dev": "vite build --watch --emptyOutDir=false", "build": "vite build && UMD=true vite build", "postbuild": "cp src/graphiql-explorer.d.ts dist/graphiql-explorer.d.ts" }, diff --git a/packages/graphiql-plugin-history/package.json b/packages/graphiql-plugin-history/package.json index 27f28ce59be..70ac12480b6 100644 --- a/packages/graphiql-plugin-history/package.json +++ b/packages/graphiql-plugin-history/package.json @@ -30,7 +30,7 @@ ], "scripts": { "types:check": "tsc --noEmit", - "dev": "vite build --watch", + "dev": "vite build --watch --emptyOutDir=false", "build": "vite build", "test": "vitest" }, diff --git a/packages/graphiql-plugin-history/setup-files.ts b/packages/graphiql-plugin-history/setup-files.ts index 276c7c4f7a3..3079d90df15 100644 --- a/packages/graphiql-plugin-history/setup-files.ts +++ b/packages/graphiql-plugin-history/setup-files.ts @@ -3,3 +3,35 @@ import '@testing-library/jest-dom'; vi.mock('zustand'); // to make it works like Jest (auto-mocking) + +/** + * Fixes TypeError: document.queryCommandSupported is not a function + */ +if (!navigator.clipboard) { + Object.defineProperty(navigator, 'clipboard', { + writable: false, + value: { + write: async () => null, + }, + }); +} + +/** + * Fixes TypeError: mainWindow.matchMedia is not a function + * @see https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + */ +if (!window.matchMedia) { + Object.defineProperty(window, 'matchMedia', { + writable: false, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); +} diff --git a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx index 004000607ee..89154e147da 100644 --- a/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx +++ b/packages/graphiql-plugin-history/src/__tests__/components.spec.tsx @@ -77,17 +77,14 @@ describe('QueryHistoryItem', () => { const mockedSetQueryEditor = vi.fn(); const mockedSetVariableEditor = vi.fn(); const mockedSetHeaderEditor = vi.fn(); - type MonacoEditorWithOperationFacts = NonNullable< - ReturnType['queryEditor'] - >; type MonacoEditor = NonNullable< - ReturnType['variableEditor'] + ReturnType['queryEditor'] >; editorStore.setState({ queryEditor: { setValue: mockedSetQueryEditor, - } as unknown as MonacoEditorWithOperationFacts, + } as unknown as MonacoEditor, variableEditor: { setValue: mockedSetVariableEditor, } as unknown as MonacoEditor, diff --git a/packages/graphiql-plugin-history/vitest.config.mts b/packages/graphiql-plugin-history/vitest.config.mts index 2a31c6fda84..310795fe31f 100644 --- a/packages/graphiql-plugin-history/vitest.config.mts +++ b/packages/graphiql-plugin-history/vitest.config.mts @@ -1,3 +1,4 @@ +import path from 'node:path'; import { defineConfig } from 'vitest/config'; import { plugins } from './vite.config.mjs'; @@ -7,5 +8,14 @@ export default defineConfig({ globals: true, environment: 'jsdom', setupFiles: ['./setup-files.ts'], + alias: [ + { + // Fixes Error: Failed to resolve entry for package "monaco-editor". The package may have incorrect main/module/exports specified in its package.json. + find: /^monaco-editor$/, + replacement: path.resolve( + '../../node_modules/monaco-editor/esm/vs/editor/editor.api', + ), + }, + ], }, }); diff --git a/packages/graphiql-react/package.json b/packages/graphiql-react/package.json index 18bdfbab369..eccfa1905c7 100644 --- a/packages/graphiql-react/package.json +++ b/packages/graphiql-react/package.json @@ -1,7 +1,10 @@ { "name": "@graphiql/react", "version": "0.34.0", - "sideEffects": false, + "sideEffects": [ + "dist/setup-workers/webpack.js", + "dist/setup-workers/vite.js" + ], "repository": { "type": "git", "url": "https://github.com/graphql/graphiql", @@ -16,14 +19,20 @@ "./package.json": "./package.json", "./style.css": "./dist/style.css", "./font/*": "./font/*", - ".": "./dist/index.js" + ".": "./dist/index.js", + "./setup-workers/*": { + "types": "./dist/setup-workers/*.d.ts", + "import": "./dist/setup-workers/*.js" + } }, "types": "dist/index.d.ts", "keywords": [ "react", "graphql", "sdk", - "codemirror" + "monaco-editor", + "monaco-graphql", + "monaco" ], "files": [ "dist", @@ -31,7 +40,7 @@ ], "scripts": { "types:check": "tsc --noEmit", - "dev": "vite build --watch", + "dev": "vite build --watch --emptyOutDir=false", "build": "vite build", "test": "vitest" }, @@ -41,18 +50,19 @@ "react-dom": "^18 || ^19" }, "dependencies": { + "monaco-editor": "^0.47", + "monaco-graphql": "^1.6.1", + "jsonc-parser": "^3.3.1", + "prettier": "^3.5.3", "react-compiler-runtime": "19.1.0-rc.1", "@graphiql/toolkit": "^0.11.2", "@radix-ui/react-dialog": "^1.1", "@radix-ui/react-dropdown-menu": "^2.1", "@radix-ui/react-tooltip": "^1.2", "@radix-ui/react-visually-hidden": "^1.2", - "@types/codemirror": "^5.60.8", "clsx": "^1.2.1", - "codemirror": "^5.65.3", - "codemirror-graphql": "^2.2.1", "copy-to-clipboard": "^3.2.0", - "framer-motion": "^12", + "framer-motion": "^12.12", "get-value": "^3.0.1", "graphql-language-service": "^5.3.1", "markdown-it": "^14.1.0", diff --git a/packages/graphiql-react/setup-files.ts b/packages/graphiql-react/setup-files.ts new file mode 100644 index 00000000000..28dc3015b58 --- /dev/null +++ b/packages/graphiql-react/setup-files.ts @@ -0,0 +1,33 @@ +/** + * Fixes TypeError: document.queryCommandSupported is not a function + */ +if (!navigator.clipboard) { + Object.defineProperty(navigator, 'clipboard', { + writable: false, + value: { + write: async () => null, + }, + }); +} + +/** + * Fixes TypeError: mainWindow.matchMedia is not a function + * @see https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom + */ +if (!window.matchMedia) { + Object.defineProperty(window, 'matchMedia', { + writable: false, + value: vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), + }); +} + +export {}; diff --git a/packages/graphiql-react/src/ui/button-group.css b/packages/graphiql-react/src/components/button-group/index.css similarity index 100% rename from packages/graphiql-react/src/ui/button-group.css rename to packages/graphiql-react/src/components/button-group/index.css diff --git a/packages/graphiql-react/src/ui/button-group.tsx b/packages/graphiql-react/src/components/button-group/index.tsx similarity index 68% rename from packages/graphiql-react/src/ui/button-group.tsx rename to packages/graphiql-react/src/components/button-group/index.tsx index 8779d463dd3..97d5e0a5952 100644 --- a/packages/graphiql-react/src/ui/button-group.tsx +++ b/packages/graphiql-react/src/components/button-group/index.tsx @@ -1,11 +1,10 @@ -import { forwardRef, JSX } from 'react'; +import { ComponentPropsWithoutRef, forwardRef } from 'react'; import { clsx } from 'clsx'; - -import './button-group.css'; +import './index.css'; export const ButtonGroup = forwardRef< HTMLDivElement, - JSX.IntrinsicElements['div'] + ComponentPropsWithoutRef<'div'> >((props, ref) => (
; + +export const UnStyledButton = forwardRef< + HTMLButtonElement, + UnStyledButtonProps +>((props, ref) => ( + - ) - ) : null} + ))} ); }; @@ -216,7 +215,7 @@ const EnumValue: FC<{ value: GraphQLEnumValue }> = ({ value }) => { }; const PossibleTypes: FC<{ type: GraphQLNamedType }> = ({ type }) => { - const schema = useSchemaStore(store => store.schema); + const schema = useGraphiQL(state => state.schema); if (!schema || !isAbstractType(type)) { return null; } diff --git a/packages/graphiql-plugin-doc-explorer/src/context.ts b/packages/graphiql-plugin-doc-explorer/src/context.ts index 444e615bfee..fef982be199 100644 --- a/packages/graphiql-plugin-doc-explorer/src/context.ts +++ b/packages/graphiql-plugin-doc-explorer/src/context.ts @@ -17,7 +17,8 @@ import { import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { SchemaReference, - useSchemaStore, + useGraphiQL, + pick, createBoundedUseStore, } from '@graphiql/react'; import { createStore } from 'zustand'; @@ -245,7 +246,9 @@ export const docExplorerStore = createStore( export const DocExplorerStore: FC<{ children: ReactNode; }> = ({ children }) => { - const { schema, validationErrors, schemaReference } = useSchemaStore(); + const { schema, validationErrors, schemaReference } = useGraphiQL( + pick('schema', 'validationErrors', 'schemaReference'), + ); useEffect(() => { const { resolveSchemaReferenceToNavItem } = diff --git a/packages/graphiql-plugin-doc-explorer/src/index.tsx b/packages/graphiql-plugin-doc-explorer/src/index.tsx index 37e5e47b849..253e3c48969 100644 --- a/packages/graphiql-plugin-doc-explorer/src/index.tsx +++ b/packages/graphiql-plugin-doc-explorer/src/index.tsx @@ -2,7 +2,7 @@ import { DocsFilledIcon, DocsIcon, GraphiQLPlugin, - usePluginStore, + useGraphiQL, } from '@graphiql/react'; import { DocExplorer } from './components'; @@ -23,7 +23,7 @@ export type { export const DOC_EXPLORER_PLUGIN: GraphiQLPlugin = { title: 'Documentation Explorer', icon: function Icon() { - const visiblePlugin = usePluginStore(store => store.visiblePlugin); + const visiblePlugin = useGraphiQL(state => state.visiblePlugin); return visiblePlugin === DOC_EXPLORER_PLUGIN ? ( ) : ( diff --git a/packages/graphiql-plugin-explorer/src/index.tsx b/packages/graphiql-plugin-explorer/src/index.tsx index 15e70f990ce..b4db982b8b3 100644 --- a/packages/graphiql-plugin-explorer/src/index.tsx +++ b/packages/graphiql-plugin-explorer/src/index.tsx @@ -1,9 +1,8 @@ import React, { CSSProperties, FC, useCallback } from 'react'; import { GraphiQLPlugin, - useEditorStore, - useExecutionStore, - useSchemaStore, + useGraphiQL, + pick, useOperationsEditorState, useOptimisticState, } from '@graphiql/react'; @@ -62,9 +61,9 @@ export type GraphiQLExplorerPluginProps = Omit< >; const ExplorerPlugin: FC = props => { - const setOperationName = useEditorStore(store => store.setOperationName); - const schema = useSchemaStore(store => store.schema); - const run = useExecutionStore(store => store.run); + const { setOperationName, schema, run } = useGraphiQL( + pick('setOperationName', 'schema', 'run'), + ); // handle running the current operation from the plugin const handleRunOperation = useCallback( diff --git a/packages/graphiql-plugin-history/src/components.tsx b/packages/graphiql-plugin-history/src/components.tsx index bd379ca62ee..1710eaf6ee7 100644 --- a/packages/graphiql-plugin-history/src/components.tsx +++ b/packages/graphiql-plugin-history/src/components.tsx @@ -7,7 +7,8 @@ import { StarFilledIcon, StarIcon, TrashIcon, - useEditorStore, + useGraphiQL, + pick, Button, Tooltip, UnStyledButton, @@ -112,7 +113,9 @@ type QueryHistoryItemProps = { export const HistoryItem: FC = props => { const { editLabel, toggleFavorite, deleteFromHistory, setActive } = useHistoryActions(); - const { headerEditor, queryEditor, variableEditor } = useEditorStore(); + const { headerEditor, queryEditor, variableEditor } = useGraphiQL( + pick('headerEditor', 'queryEditor', 'variableEditor'), + ); const inputRef = useRef(null); const buttonRef = useRef(null); const [isEditable, setIsEditable] = useState(false); diff --git a/packages/graphiql-plugin-history/src/context.tsx b/packages/graphiql-plugin-history/src/context.tsx index 2619fc2248e..664599a14f0 100644 --- a/packages/graphiql-plugin-history/src/context.tsx +++ b/packages/graphiql-plugin-history/src/context.tsx @@ -6,8 +6,8 @@ import { QueryStoreItem, } from '@graphiql/toolkit'; import { - useExecutionStore, - useEditorStore, + useGraphiQL, + pick, useStorage, createBoundedUseStore, } from '@graphiql/react'; @@ -122,8 +122,9 @@ export const HistoryStore: FC = ({ maxHistoryLength = 20, children, }) => { - const isFetching = useExecutionStore(store => store.isFetching); - const { tabs, activeTabIndex } = useEditorStore(); + const { isFetching, tabs, activeTabIndex } = useGraphiQL( + pick('isFetching', 'tabs', 'activeTabIndex'), + ); const activeTab = tabs[activeTabIndex]!; const storage = useStorage(); diff --git a/packages/graphiql-react/src/components/provider.tsx b/packages/graphiql-react/src/components/provider.tsx index 0144b11e255..30d1a50d2aa 100644 --- a/packages/graphiql-react/src/components/provider.tsx +++ b/packages/graphiql-react/src/components/provider.tsx @@ -4,9 +4,12 @@ import type { FC, ReactElement, ReactNode, + RefObject, } from 'react'; import { createContext, useContext, useRef, useEffect } from 'react'; -import { useStore, create, UseBoundStore, StoreApi } from 'zustand'; +import { useStore, UseBoundStore, StoreApi } from 'zustand'; +import { createWithEqualityFn as create } from 'zustand/traditional'; +import { shallow } from 'zustand/vanilla/shallow'; import { EditorProps, createEditorSlice, @@ -48,7 +51,7 @@ type GraphiQLProviderProps = type GraphiQLStore = UseBoundStore>; -const GraphiQLContext = createContext(null!); +const GraphiQLContext = createContext>(null!); export const GraphiQLProvider: FC = ({ storage, @@ -111,46 +114,54 @@ const InnerGraphiQLProvider: FC = ({ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- false positive if (storeRef.current === null) { - // We only need to compute it lazily during the initial render. - const query = props.query ?? storage.get(STORAGE_KEY_QUERY) ?? null; - const variables = - props.variables ?? storage.get(STORAGE_KEY_VARIABLES) ?? null; - const headers = props.headers ?? storage.get(STORAGE_KEY_HEADERS) ?? null; - const response = props.response ?? ''; + function getInitialState() { + // We only need to compute it lazily during the initial render. + const query = props.query ?? storage.get(STORAGE_KEY_QUERY) ?? null; + const variables = + props.variables ?? storage.get(STORAGE_KEY_VARIABLES) ?? null; + const headers = props.headers ?? storage.get(STORAGE_KEY_HEADERS) ?? null; + const response = props.response ?? ''; - const { tabs, activeTabIndex } = getDefaultTabState({ - defaultHeaders, - defaultQuery: defaultQuery || DEFAULT_QUERY, - defaultTabs, - headers, - query, - shouldPersistHeaders, - variables, - }); + const { tabs, activeTabIndex } = getDefaultTabState({ + defaultHeaders, + defaultQuery: defaultQuery || DEFAULT_QUERY, + defaultTabs, + headers, + query, + shouldPersistHeaders, + variables, + }); - const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; + const isStored = storage.get(PERSIST_HEADERS_STORAGE_KEY) !== null; - const $shouldPersistHeaders = - shouldPersistHeaders !== false && isStored - ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' - : shouldPersistHeaders; + const $shouldPersistHeaders = + shouldPersistHeaders !== false && isStored + ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' + : shouldPersistHeaders; - storeRef.current = create((...args) => ({ - ...createEditorSlice({ - activeTabIndex, - initialHeaders: headers ?? defaultHeaders ?? '', - initialQuery: - query ?? (activeTabIndex === 0 ? tabs[0]!.query : null) ?? '', - initialResponse: response, - initialVariables: variables ?? '', - shouldPersistHeaders: $shouldPersistHeaders, - tabs, - })(...args), - ...createExecutionSlice(...args), - ...createPluginSlice(...args), - ...createSchemaSlice(...args), - })); - storeRef.current.getState().storeTabs({ activeTabIndex, tabs }); + const store = create( + (...args) => ({ + ...createEditorSlice({ + activeTabIndex, + initialHeaders: headers ?? defaultHeaders ?? '', + initialQuery: + query ?? (activeTabIndex === 0 ? tabs[0]!.query : null) ?? '', + initialResponse: response, + initialVariables: variables ?? '', + shouldPersistHeaders: $shouldPersistHeaders, + tabs, + })(...args), + ...createExecutionSlice(...args), + ...createPluginSlice(...args), + ...createSchemaSlice(...args), + }), + shallow, + ); + store.getState().storeTabs({ activeTabIndex, tabs }); + return store; + } + + storeRef.current = getInitialState(); } // TODO: // const lastShouldPersistHeadersProp = useRef(undefined); @@ -290,7 +301,7 @@ const InnerGraphiQLProvider: FC = ({ }, []); return ( - + {children} ); @@ -323,5 +334,5 @@ export function useGraphiQL(selector: (state: AllSlices) => T): T { if (!store) { throw new Error('Missing `GraphiQLContext.Provider` in the tree'); } - return useStore(store, selector); + return useStore(store.current, selector); } diff --git a/packages/graphiql-react/vite.config.mts b/packages/graphiql-react/vite.config.mts index 335d3833c5d..f490dc618a8 100644 --- a/packages/graphiql-react/vite.config.mts +++ b/packages/graphiql-react/vite.config.mts @@ -121,6 +121,7 @@ export default defineConfig({ /monaco-editor\//, /prettier\//, /graphql-language-service\//, + /zustand\// ], output: { preserveModules: true, diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 607d18b6dec..e21d24bc5f7 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -32,10 +32,8 @@ import { Tooltip, UnStyledButton, useDragResize, - useEditorStore, - useExecutionStore, - usePluginStore, - useSchemaStore, + useGraphiQL, + pick, useStorage, useThemeStore, VariableEditor, @@ -227,11 +225,33 @@ const GraphiQLInterface: FC = ({ shouldPersistHeaders, tabs, activeTabIndex, - } = useEditorStore(); - const isExecutionFetching = useExecutionStore(store => store.isFetching); - const { isFetching: isSchemaFetching, introspect } = useSchemaStore(); + isIntrospecting, + isFetching, + introspect, + visiblePlugin, + setVisiblePlugin, + plugins, + } = useGraphiQL( + pick( + 'initialVariables', + 'initialHeaders', + 'setShouldPersistHeaders', + 'addTab', + 'moveTab', + 'closeTab', + 'changeTab', + 'shouldPersistHeaders', + 'tabs', + 'activeTabIndex', + 'isIntrospecting', + 'isFetching', + 'introspect', + 'visiblePlugin', + 'setVisiblePlugin', + 'plugins', + ), + ); const storageContext = useStorage(); - const { visiblePlugin, setVisiblePlugin, plugins } = usePluginStore(); const { theme, setTheme } = useThemeStore(); useEffect(() => { @@ -434,13 +454,13 @@ const GraphiQLInterface: FC = ({ @@ -766,7 +786,7 @@ const GraphiQLInterface: FC = ({ ref={editorResize.dragBarRef} />
- {isExecutionFetching && } + {isFetching && } {footer}
From b1d5eb0b15af2ad5222aeacf81e098443508229a Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 1 Jun 2025 01:15:35 +0200 Subject: [PATCH 203/255] upd --- packages/graphiql-react/src/components/query-editor.tsx | 2 +- packages/graphiql/src/ui/toolbar.tsx | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/components/query-editor.tsx b/packages/graphiql-react/src/components/query-editor.tsx index 42445c2f886..96d44a118de 100644 --- a/packages/graphiql-react/src/components/query-editor.tsx +++ b/packages/graphiql-react/src/components/query-editor.tsx @@ -334,8 +334,8 @@ export const QueryEditor: FC = ({ }; }, [ onClickReference, - referencePlugin, schema, + referencePlugin, setSchemaReference, setVisiblePlugin, ]); diff --git a/packages/graphiql/src/ui/toolbar.tsx b/packages/graphiql/src/ui/toolbar.tsx index 732571fd3d5..c8973f698d2 100644 --- a/packages/graphiql/src/ui/toolbar.tsx +++ b/packages/graphiql/src/ui/toolbar.tsx @@ -1,11 +1,11 @@ import type { FC, ReactNode } from 'react'; import { CopyIcon, - copyQuery, + useCopyQuery, KEY_MAP, MergeIcon, - mergeQuery, - prettifyEditors, + useMergeQuery, + usePrettifyEditors, PrettifyIcon, ToolbarButton, } from '@graphiql/react'; @@ -33,6 +33,9 @@ export const GraphiQLToolbar: FC<{ 'The `GraphiQL.Toolbar` component requires a render prop function as its child.', ); } + const prettifyEditors = usePrettifyEditors(); + const mergeQuery = useMergeQuery(); + const copyQuery = useCopyQuery(); const prettify = ( Date: Sun, 1 Jun 2025 01:15:53 +0200 Subject: [PATCH 204/255] upd --- .../src/components/provider.tsx | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/graphiql-react/src/components/provider.tsx b/packages/graphiql-react/src/components/provider.tsx index 30d1a50d2aa..04b7c3f17d3 100644 --- a/packages/graphiql-react/src/components/provider.tsx +++ b/packages/graphiql-react/src/components/provider.tsx @@ -7,9 +7,8 @@ import type { RefObject, } from 'react'; import { createContext, useContext, useRef, useEffect } from 'react'; -import { useStore, UseBoundStore, StoreApi } from 'zustand'; -import { createWithEqualityFn as create } from 'zustand/traditional'; -import { shallow } from 'zustand/vanilla/shallow'; +import { create, useStore, UseBoundStore, StoreApi } from 'zustand'; +import { useShallow } from 'zustand/shallow'; import { EditorProps, createEditorSlice, @@ -139,24 +138,21 @@ const InnerGraphiQLProvider: FC = ({ ? storage.get(PERSIST_HEADERS_STORAGE_KEY) === 'true' : shouldPersistHeaders; - const store = create( - (...args) => ({ - ...createEditorSlice({ - activeTabIndex, - initialHeaders: headers ?? defaultHeaders ?? '', - initialQuery: - query ?? (activeTabIndex === 0 ? tabs[0]!.query : null) ?? '', - initialResponse: response, - initialVariables: variables ?? '', - shouldPersistHeaders: $shouldPersistHeaders, - tabs, - })(...args), - ...createExecutionSlice(...args), - ...createPluginSlice(...args), - ...createSchemaSlice(...args), - }), - shallow, - ); + const store = create((...args) => ({ + ...createEditorSlice({ + activeTabIndex, + initialHeaders: headers ?? defaultHeaders ?? '', + initialQuery: + query ?? (activeTabIndex === 0 ? tabs[0]!.query : null) ?? '', + initialResponse: response, + initialVariables: variables ?? '', + shouldPersistHeaders: $shouldPersistHeaders, + tabs, + })(...args), + ...createExecutionSlice(...args), + ...createPluginSlice(...args), + ...createSchemaSlice(...args), + })); store.getState().storeTabs({ activeTabIndex, tabs }); return store; } @@ -334,5 +330,5 @@ export function useGraphiQL(selector: (state: AllSlices) => T): T { if (!store) { throw new Error('Missing `GraphiQLContext.Provider` in the tree'); } - return useStore(store.current, selector); + return useStore(store.current, useShallow(selector)); } From 1343e96abcca4023296df3a86a98ccd4af11d457 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 1 Jun 2025 01:24:12 +0200 Subject: [PATCH 205/255] swap isIntrospecting and isFetching --- .../graphiql-react/src/stores/execution.ts | 20 +++++++++---------- packages/graphiql-react/src/stores/schema.ts | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/graphiql-react/src/stores/execution.ts b/packages/graphiql-react/src/stores/execution.ts index c0b1e2487f9..8b9c06c5ce4 100644 --- a/packages/graphiql-react/src/stores/execution.ts +++ b/packages/graphiql-react/src/stores/execution.ts @@ -30,7 +30,7 @@ export interface ExecutionSlice { * first partial response and `false` while fetching subsequent batches. * @default false */ - isIntrospecting: boolean; + isFetching: boolean; /** * Represents an active GraphQL subscription. * @@ -96,7 +96,7 @@ export const createExecutionSlice: StateCreator< [], ExecutionSlice > = (set, get) => ({ - isIntrospecting: false, + isFetching: false, subscription: null, overrideOperationName: null, getDefaultFieldNames: undefined, @@ -105,7 +105,7 @@ export const createExecutionSlice: StateCreator< stop() { const { subscription } = get(); subscription?.unsubscribe(); - set({ isIntrospecting: false, subscription: null }); + set({ isFetching: false, subscription: null }); }, async run() { const { @@ -185,7 +185,7 @@ export const createExecutionSlice: StateCreator< } setResponse(''); - set({ isIntrospecting: true }); + set({ isFetching: true }); try { const fullResponse: ExecutionResult = {}; const handleResponse = (result: ExecutionResult) => { @@ -209,10 +209,10 @@ export const createExecutionSlice: StateCreator< mergeIncrementalResult(fullResponse, part); } - set({ isIntrospecting: false }); + set({ isFetching: false }); setResponse(formatResult(fullResponse)); } else { - set({ isIntrospecting: false }); + set({ isFetching: false }); setResponse(formatResult(result)); } }; @@ -238,12 +238,12 @@ export const createExecutionSlice: StateCreator< handleResponse(result); }, error(error: Error) { - set({ isIntrospecting: false }); + set({ isFetching: false }); setResponse(formatError(error)); set({ subscription: null }); }, complete() { - set({ isIntrospecting: false, subscription: null }); + set({ isFetching: false, subscription: null }); }, }); set({ subscription: newSubscription }); @@ -255,12 +255,12 @@ export const createExecutionSlice: StateCreator< for await (const result of value) { handleResponse(result); } - set({ isIntrospecting: false, subscription: null }); + set({ isFetching: false, subscription: null }); } else { handleResponse(value); } } catch (error) { - set({ isIntrospecting: false }); + set({ isFetching: false }); setResponse(formatError(error)); set({ subscription: null }); } diff --git a/packages/graphiql-react/src/stores/schema.ts b/packages/graphiql-react/src/stores/schema.ts index 98c21d0bc28..185dd261326 100644 --- a/packages/graphiql-react/src/stores/schema.ts +++ b/packages/graphiql-react/src/stores/schema.ts @@ -28,7 +28,7 @@ export const createSchemaSlice: StateCreator = ( onSchemaChange: undefined, fetchError: null, - isFetching: false, + isIntrospecting: false, schema: null, /** * Derive validation errors from the schema @@ -100,7 +100,7 @@ export const createSchemaSlice: StateCreator = ( }); return; } - set({ isFetching: true, fetchError: null }); + set({ isIntrospecting: true, fetchError: null }); let result = await fetch; if (typeof result !== 'object' || !('data' in result)) { @@ -123,7 +123,7 @@ export const createSchemaSlice: StateCreator = ( result = await fetch2; } - set({ isFetching: false }); + set({ isIntrospecting: false }); let introspectionData: IntrospectionQuery | undefined; if (result.data && '__schema' in result.data) { introspectionData = result.data as IntrospectionQuery; @@ -153,7 +153,7 @@ export const createSchemaSlice: StateCreator = ( } set({ fetchError: formatError(error), - isFetching: false, + isIntrospecting: false, }); } }, @@ -186,7 +186,7 @@ export interface SchemaSlice /** * If there currently is an introspection request in-flight. */ - isFetching: boolean; + isIntrospecting: boolean; /** * The current GraphQL schema. */ From fe33c88cf4bbae39de64d1dd52f72221326a482f Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 1 Jun 2025 01:33:19 +0200 Subject: [PATCH 206/255] cleanupDisposables --- .../src/components/header-editor.tsx | 8 ++------ .../src/components/query-editor.tsx | 15 +++------------ .../src/components/response-editor.tsx | 9 ++------- .../src/components/variable-editor.tsx | 9 ++------- packages/graphiql-react/src/stores/plugin.ts | 1 - .../src/utility/cleanup-disposables.ts | 9 +++++++++ packages/graphiql-react/src/utility/index.ts | 1 + packages/graphiql-react/vite.config.mts | 2 +- 8 files changed, 20 insertions(+), 34 deletions(-) create mode 100644 packages/graphiql-react/src/utility/cleanup-disposables.ts diff --git a/packages/graphiql-react/src/components/header-editor.tsx b/packages/graphiql-react/src/components/header-editor.tsx index 68302b99edc..c191b77b3e3 100644 --- a/packages/graphiql-react/src/components/header-editor.tsx +++ b/packages/graphiql-react/src/components/header-editor.tsx @@ -11,6 +11,7 @@ import { pick, usePrettifyEditors, useMergeQuery, + cleanupDisposables, } from '../utility'; interface HeaderEditorProps extends EditorProps { @@ -49,12 +50,7 @@ export const HeaderEditor: FC = ({ editor, model, ]; - // 3️⃣ Clean‑up on unmount - return () => { - for (const disposable of disposables) { - disposable.dispose(); // remove the listener - } - }; + return cleanupDisposables(disposables); }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount return ( diff --git a/packages/graphiql-react/src/components/query-editor.tsx b/packages/graphiql-react/src/components/query-editor.tsx index 96d44a118de..01a1ff16de1 100644 --- a/packages/graphiql-react/src/components/query-editor.tsx +++ b/packages/graphiql-react/src/components/query-editor.tsx @@ -13,6 +13,7 @@ import { useCopyQuery, usePrettifyEditors, useMergeQuery, + cleanupDisposables, } from '../utility'; import { MonacoEditor, EditorProps, SchemaReference } from '../types'; import { KEY_BINDINGS, MONACO_GRAPHQL_API, QUERY_URI } from '../constants'; @@ -239,13 +240,7 @@ export const QueryEditor: FC = ({ editor, model, ]; - - // 3️⃣ Clean‑up on unmount - return () => { - for (const disposable of disposables) { - disposable.dispose(); // remove the listener - } - }; + return cleanupDisposables(disposables); }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount useEffect(() => { @@ -327,11 +322,7 @@ export const QueryEditor: FC = ({ }, }), ]; - return () => { - for (const disposable of disposables) { - disposable.dispose(); - } - }; + return cleanupDisposables(disposables); }, [ onClickReference, schema, diff --git a/packages/graphiql-react/src/components/response-editor.tsx b/packages/graphiql-react/src/components/response-editor.tsx index 2a5bd7bc949..a3607fb81ff 100644 --- a/packages/graphiql-react/src/components/response-editor.tsx +++ b/packages/graphiql-react/src/components/response-editor.tsx @@ -8,6 +8,7 @@ import { createEditor, onEditorContainerKeyDown, pick, + cleanupDisposables, } from '../utility'; import { RESPONSE_URI } from '../constants'; import { clsx } from 'clsx'; @@ -140,13 +141,7 @@ export const ResponseEditor: FC = ({ editor, model, ]; - - // Clean‑up on unmount - return () => { - for (const disposable of disposables) { - disposable.dispose(); // remove the listener - } - }; + return cleanupDisposables(disposables); }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount return ( diff --git a/packages/graphiql-react/src/components/variable-editor.tsx b/packages/graphiql-react/src/components/variable-editor.tsx index 7743b0708b0..09df217e248 100644 --- a/packages/graphiql-react/src/components/variable-editor.tsx +++ b/packages/graphiql-react/src/components/variable-editor.tsx @@ -11,6 +11,7 @@ import { pick, usePrettifyEditors, useMergeQuery, + cleanupDisposables, } from '../utility'; interface VariableEditorProps extends EditorProps { @@ -58,13 +59,7 @@ export const VariableEditor: FC = ({ editor, model, ]; - - // 3️⃣ Clean‑up on unmount - return () => { - for (const disposable of disposables) { - disposable.dispose(); // remove the listener - } - }; + return cleanupDisposables(disposables); }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount return ( diff --git a/packages/graphiql-react/src/stores/plugin.ts b/packages/graphiql-react/src/stores/plugin.ts index 8ab82116dd5..b72ca972d34 100644 --- a/packages/graphiql-react/src/stores/plugin.ts +++ b/packages/graphiql-react/src/stores/plugin.ts @@ -105,4 +105,3 @@ export const createPluginSlice: StateCreator = ( set({ plugins }); }, }); - diff --git a/packages/graphiql-react/src/utility/cleanup-disposables.ts b/packages/graphiql-react/src/utility/cleanup-disposables.ts new file mode 100644 index 00000000000..3ffafbf4a2b --- /dev/null +++ b/packages/graphiql-react/src/utility/cleanup-disposables.ts @@ -0,0 +1,9 @@ +import type { IDisposable } from '../monaco-editor'; + +export function cleanupDisposables(disposables: IDisposable[]) { + return () => { + for (const disposable of disposables) { + disposable.dispose(); // remove the listener + } + }; +} diff --git a/packages/graphiql-react/src/utility/index.ts b/packages/graphiql-react/src/utility/index.ts index af4beeda00b..6862417d785 100644 --- a/packages/graphiql-react/src/utility/index.ts +++ b/packages/graphiql-react/src/utility/index.ts @@ -1,3 +1,4 @@ +export { cleanupDisposables } from './cleanup-disposables'; export { createBoundedUseStore } from './create-bounded-use-store'; export { getOrCreateModel, diff --git a/packages/graphiql-react/vite.config.mts b/packages/graphiql-react/vite.config.mts index f490dc618a8..fa137ffe037 100644 --- a/packages/graphiql-react/vite.config.mts +++ b/packages/graphiql-react/vite.config.mts @@ -121,7 +121,7 @@ export default defineConfig({ /monaco-editor\//, /prettier\//, /graphql-language-service\//, - /zustand\// + /zustand\//, ], output: { preserveModules: true, From 11d6ea923c06fd22768a1dfcb919e9966aa03b20 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 1 Jun 2025 01:34:31 +0200 Subject: [PATCH 207/255] lint --- packages/graphiql-react/src/stores/execution.ts | 1 - packages/graphiql-react/src/stores/plugin.ts | 2 +- packages/graphiql-react/src/utility/tabs.ts | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/graphiql-react/src/stores/execution.ts b/packages/graphiql-react/src/stores/execution.ts index 8b9c06c5ce4..f5cd4f8f865 100644 --- a/packages/graphiql-react/src/stores/execution.ts +++ b/packages/graphiql-react/src/stores/execution.ts @@ -14,7 +14,6 @@ import { print, } from 'graphql'; import { getFragmentDependenciesForAST } from 'graphql-language-service'; -import { FC, useEffect } from 'react'; import setValue from 'set-value'; import getValue from 'get-value'; diff --git a/packages/graphiql-react/src/stores/plugin.ts b/packages/graphiql-react/src/stores/plugin.ts index b72ca972d34..d375804a4cb 100644 --- a/packages/graphiql-react/src/stores/plugin.ts +++ b/packages/graphiql-react/src/stores/plugin.ts @@ -1,4 +1,4 @@ -import { ComponentType, FC, useEffect } from 'react'; +import { ComponentType } from 'react'; import type { StateCreator } from 'zustand'; import type { AllSlices } from '../types'; diff --git a/packages/graphiql-react/src/utility/tabs.ts b/packages/graphiql-react/src/utility/tabs.ts index 5fd4904afb9..e8cd9c82140 100644 --- a/packages/graphiql-react/src/utility/tabs.ts +++ b/packages/graphiql-react/src/utility/tabs.ts @@ -1,8 +1,6 @@ 'use no memo'; // can't figure why it isn't optimized import { storageStore } from '../stores'; -import { debounce } from './debounce'; -import { AllSlices } from '../types'; export type TabDefinition = { /** From df85f9aab316fd626dcafdaf02e233705c924026 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 1 Jun 2025 01:50:12 +0200 Subject: [PATCH 208/255] add test-d typecheck test --- packages/graphiql-react/package.json | 2 +- packages/graphiql-react/src/types.test-d.ts | 37 +++++++++++++++++++++ packages/graphiql-react/src/types.ts | 33 +++--------------- 3 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 packages/graphiql-react/src/types.test-d.ts diff --git a/packages/graphiql-react/package.json b/packages/graphiql-react/package.json index 92677edaf48..03883a23626 100644 --- a/packages/graphiql-react/package.json +++ b/packages/graphiql-react/package.json @@ -42,7 +42,7 @@ "types:check": "tsc --noEmit", "dev": "vite build --watch --emptyOutDir=false", "build": "vite build", - "test": "vitest" + "test": "vitest --typecheck" }, "peerDependencies": { "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0", diff --git a/packages/graphiql-react/src/types.test-d.ts b/packages/graphiql-react/src/types.test-d.ts new file mode 100644 index 00000000000..1b24e7e4582 --- /dev/null +++ b/packages/graphiql-react/src/types.test-d.ts @@ -0,0 +1,37 @@ +import type { + EditorSlice, + ExecutionSlice, + PluginSlice, + SchemaSlice, +} from './stores'; + +describe('Types', () => { + it('should not have conflicting types', () => { + type OverlapError = { + ERROR: 'Conflicting keys found'; + CONFLICT_KEYS: K; + }; + + type MergeWithoutOverlap = keyof A & keyof B extends never + ? A & B + : OverlapError; + + type MergeMany = T extends [ + infer Head, + ...infer Tail, + ] + ? MergeWithoutOverlap extends infer Merged + ? Merged extends OverlapError + ? Merged + : MergeMany + : never + : Acc; + + type Actual = MergeMany< + [EditorSlice, ExecutionSlice, PluginSlice, SchemaSlice] + >; + type Expected = EditorSlice & ExecutionSlice & PluginSlice & SchemaSlice; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expectTypeOf().toEqualTypeOf; + }); +}); diff --git a/packages/graphiql-react/src/types.ts b/packages/graphiql-react/src/types.ts index 3b7d7ade822..bc960b08340 100644 --- a/packages/graphiql-react/src/types.ts +++ b/packages/graphiql-react/src/types.ts @@ -23,32 +23,7 @@ export interface SchemaReference { export type MonacoEditor = monacoEditor.IStandaloneCodeEditor; -type OverlapError = { - ERROR: 'Conflicting keys found'; - CONFLICT_KEYS: K; -}; - -type MergeWithoutOverlap = keyof A & keyof B extends never - ? A & B - : OverlapError; - -type MergeMany = T extends [ - infer Head, - ...infer Tail, -] - ? MergeWithoutOverlap extends infer Merged - ? Merged extends OverlapError - ? Merged - : MergeMany - : never - : Acc; - -export type AllSlices = MergeMany< - [ - // - EditorSlice, - ExecutionSlice, - PluginSlice, - SchemaSlice, - ] ->; +export type AllSlices = EditorSlice & + ExecutionSlice & + PluginSlice & + SchemaSlice; From d90e3a471127bd672ad820af6697570e2dfff56d Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Sun, 1 Jun 2025 01:52:19 +0200 Subject: [PATCH 209/255] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 85ffd50e4c4..76d9d0a30b3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ package-lock.json vite.config.d.ts vite.config.js +tsconfig.vitest-temp.json .next/ .turbo/ From e27c076c9c02bd003325497e61bb56d768a2764b Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Thu, 5 Jun 2025 23:49:07 +0200 Subject: [PATCH 210/255] run yarn --- examples/graphiql-webpack/package.json | 2 +- yarn.lock | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/graphiql-webpack/package.json b/examples/graphiql-webpack/package.json index dbac12fe513..e1c15cadd6a 100644 --- a/examples/graphiql-webpack/package.json +++ b/examples/graphiql-webpack/package.json @@ -21,9 +21,9 @@ "regenerator-runtime": "^0.13.9" }, "devDependencies": { - "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", "ajv-formats": "^3.0.1", diff --git a/yarn.lock b/yarn.lock index a93793b1431..fcfd64d98b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3313,7 +3313,7 @@ __metadata: graphql-language-service: "npm:^5.3.1" jsonc-parser: "npm:^3.3.1" markdown-it: "npm:^14.1.0" - monaco-editor: "npm:^0.47" + monaco-editor: "npm:^0.52.2" monaco-graphql: "npm:^1.6.1" prettier: "npm:^3.5.3" react: "npm:^19.1.0" @@ -12536,6 +12536,7 @@ __metadata: dependencies: "@babel/plugin-proposal-class-properties": "npm:^7.18.6" "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" + "@babel/plugin-transform-class-static-block": "npm:^7.27.1" "@babel/preset-env": "npm:^7.20.2" "@babel/preset-react": "npm:^7.18.6" "@graphiql/plugin-code-exporter": "npm:^4.0.6-rc.0" @@ -12623,7 +12624,7 @@ __metadata: html-webpack-plugin: "npm:^5.5.0" json-schema: "npm:^0.4.0" jsonc-parser: "npm:^3.2.0" - monaco-editor: "npm:^0.47.0" + monaco-editor: "npm:^0.52.2" monaco-editor-webpack-plugin: "npm:^7.0.1" monaco-graphql: "npm:^1.6.2" prettier: "npm:3.3.2" @@ -18613,10 +18614,10 @@ __metadata: languageName: node linkType: hard -"monaco-editor@npm:0.47.0": - version: 0.47.0 - resolution: "monaco-editor@npm:0.47.0" - checksum: 10c0/bbe4fc284a7803e4ed0fa69b6e31c41ef6f5d259722e8a876ce2850b7139e68eff3a6435306e11eff848db8662b818e40f9cafc6f7a53d4a4551fed362d5dc02 +"monaco-editor@npm:0.52.2": + version: 0.52.2 + resolution: "monaco-editor@npm:0.52.2" + checksum: 10c0/5a92da64f1e2ab375c0ce99364137f794d057c97bed10ecc65a08d6e6846804b8ecbd377eacf01e498f7dfbe1b21e8be64f728256681448f0484df90e767b435 languageName: node linkType: hard @@ -18627,7 +18628,7 @@ __metadata: execa: "npm:^7.1.1" graphql: "npm:^16.9.0" graphql-language-service: "npm:^5.4.0" - monaco-editor: "npm:^0.39.0" + monaco-editor: "npm:^0.52.2" picomatch-browser: "npm:^2.2.6" prettier: "npm:3.3.2" vscode-languageserver-types: "npm:^3.17.1" From bca83ff39123d95da9fe2f8a4a9ee9ca9d51204f Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 6 Jun 2025 00:08:03 +0200 Subject: [PATCH 211/255] fix graphiql build --- packages/graphiql-react/src/components/query-editor.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/graphiql-react/src/components/query-editor.tsx b/packages/graphiql-react/src/components/query-editor.tsx index 80f616730db..a5a9804970b 100644 --- a/packages/graphiql-react/src/components/query-editor.tsx +++ b/packages/graphiql-react/src/components/query-editor.tsx @@ -4,15 +4,6 @@ import { getOperationFacts } from 'graphql-language-service'; import { FC, useEffect, useRef } from 'react'; import { useStorage } from '../stores'; import { useGraphiQL } from './provider'; -import { - schemaStore, - useSchemaStore, - useEditorStore, - useStorage, - editorStore, - executionStore, - pluginStore, -} from '../stores'; import { debounce, getOrCreateModel, From b97ac72cd65879d296f658edbf190b468c5fa6b7 Mon Sep 17 00:00:00 2001 From: Dimitri POSTOLOV Date: Fri, 6 Jun 2025 00:16:39 +0200 Subject: [PATCH 212/255] use cn --- .../src/components/button-group/index.tsx | 4 ++-- .../graphiql-react/src/components/button/index.tsx | 6 +++--- .../graphiql-react/src/components/dialog/index.tsx | 4 ++-- .../src/components/dropdown-menu/index.tsx | 8 ++++---- .../graphiql-react/src/components/header-editor.tsx | 4 ++-- .../src/components/markdown-content/index.tsx | 5 ++--- .../graphiql-react/src/components/query-editor.tsx | 10 ++-------- .../graphiql-react/src/components/response-editor.tsx | 4 ++-- .../graphiql-react/src/components/spinner/index.tsx | 4 ++-- packages/graphiql-react/src/components/tabs/index.tsx | 10 +++++----- .../src/components/toolbar-button/index.tsx | 4 ++-- .../src/components/toolbar-menu/index.tsx | 4 ++-- .../graphiql-react/src/components/variable-editor.tsx | 4 ++-- packages/graphiql-react/src/index.ts | 1 - packages/graphiql-react/src/utility/hooks.ts | 10 ++++++++-- packages/graphiql-react/src/utility/index.ts | 2 +- 16 files changed, 41 insertions(+), 43 deletions(-) diff --git a/packages/graphiql-react/src/components/button-group/index.tsx b/packages/graphiql-react/src/components/button-group/index.tsx index 97d5e0a5952..3534425da4c 100644 --- a/packages/graphiql-react/src/components/button-group/index.tsx +++ b/packages/graphiql-react/src/components/button-group/index.tsx @@ -1,5 +1,5 @@ import { ComponentPropsWithoutRef, forwardRef } from 'react'; -import { clsx } from 'clsx'; +import { cn } from '../../utility'; import './index.css'; export const ButtonGroup = forwardRef< @@ -9,7 +9,7 @@ export const ButtonGroup = forwardRef<
)); ButtonGroup.displayName = 'ButtonGroup'; diff --git a/packages/graphiql-react/src/components/button/index.tsx b/packages/graphiql-react/src/components/button/index.tsx index 98160c4a39b..b5d94fab7f8 100644 --- a/packages/graphiql-react/src/components/button/index.tsx +++ b/packages/graphiql-react/src/components/button/index.tsx @@ -1,5 +1,5 @@ import { ComponentPropsWithoutRef, forwardRef } from 'react'; -import { clsx } from 'clsx'; +import { cn } from '../../utility'; import './index.css'; type UnStyledButtonProps = ComponentPropsWithoutRef<'button'>; @@ -11,7 +11,7 @@ export const UnStyledButton = forwardRef<