@@ -101,9 +101,9 @@ export type GraphiQLProps = Omit<GraphiQLProviderProps, 'children'> &
101
101
*
102
102
* @see https://github.com/graphql/graphiql#usage
103
103
*/
104
-
105
104
export function GraphiQL ( {
106
105
dangerouslyAssumeSchemaIsValid,
106
+ confirmCloseTab,
107
107
defaultQuery,
108
108
defaultTabs,
109
109
externalFragments,
@@ -168,6 +168,7 @@ export function GraphiQL({
168
168
variables = { variables }
169
169
>
170
170
< GraphiQLInterface
171
+ confirmCloseTab = { confirmCloseTab }
171
172
showPersistHeadersSettings = { shouldPersistHeaders !== false }
172
173
disableTabs = { props . disableTabs ?? false }
173
174
forcedTheme = { props . forcedTheme }
@@ -220,19 +221,29 @@ export type GraphiQLInterfaceProps = WriteableEditorProps &
220
221
showPersistHeadersSettings ?: boolean ;
221
222
disableTabs ?: boolean ;
222
223
/**
223
- * forcedTheme allows enforcement of a specific theme for GraphiQL.
224
+ * ` forcedTheme` allows enforcement of a specific theme for GraphiQL.
224
225
* This is useful when you want to make sure that GraphiQL is always
225
- * rendered with a specific theme
226
+ * rendered with a specific theme.
226
227
*/
227
228
forcedTheme ?: ( typeof THEMES ) [ number ] ;
228
229
/**
229
230
* Additional class names which will be appended to the container element.
230
231
*/
231
232
className ?: string ;
233
+ /**
234
+ * When the user clicks a close tab button, this function is invoked with
235
+ * the index of the tab that is about to be closed. It can return a promise
236
+ * that should resolve to `true` (meaning the tab may be closed) or `false`
237
+ * (meaning the tab may not be closed).
238
+ * @param index The index of the tab that should be closed.
239
+ */
240
+ confirmCloseTab ?( index : number ) : Promise < boolean > | boolean ;
232
241
} ;
233
242
234
243
const THEMES = [ 'light' , 'dark' , 'system' ] as const ;
235
244
245
+ const TAB_CLASS_PREFIX = 'graphiql-session-tab-' ;
246
+
236
247
export function GraphiQLInterface ( props : GraphiQLInterfaceProps ) {
237
248
const isHeadersEditorEnabled = props . isHeadersEditorEnabled ?? true ;
238
249
const editorContext = useEditorContext ( { nonNull : true } ) ;
@@ -408,9 +419,9 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
408
419
) ;
409
420
410
421
const handlePluginClick : MouseEventHandler < HTMLButtonElement > = useCallback (
411
- e => {
422
+ event => {
412
423
const context = pluginContext ! ;
413
- const pluginIndex = Number ( e . currentTarget . dataset . index ! ) ;
424
+ const pluginIndex = Number ( event . currentTarget . dataset . index ! ) ;
414
425
const plugin = context . plugins . find ( ( _ , index ) => pluginIndex === index ) ! ;
415
426
const isVisible = plugin === context . visiblePlugin ;
416
427
if ( isVisible ) {
@@ -470,6 +481,48 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
470
481
) ;
471
482
472
483
const className = props . className ? ` ${ props . className } ` : '' ;
484
+ const confirmClose = props . confirmCloseTab ;
485
+
486
+ const handleTabClose : MouseEventHandler < HTMLButtonElement > = useCallback (
487
+ async event => {
488
+ const tabButton = event . currentTarget
489
+ . previousSibling as HTMLButtonElement ;
490
+ const index = Number ( tabButton . id . replace ( TAB_CLASS_PREFIX , '' ) ) ;
491
+
492
+ /** TODO:
493
+ * Move everything after into `editorContext.closeTab` once zustand will be used instead of
494
+ * React context, since now we can't use execution context inside editor context, since editor
495
+ * context is used in execution context.
496
+ */
497
+ const shouldCloseTab = confirmClose ? await confirmClose ( index ) : true ;
498
+
499
+ if ( ! shouldCloseTab ) {
500
+ return ;
501
+ }
502
+
503
+ if ( editorContext . activeTabIndex === index ) {
504
+ executionContext . stop ( ) ;
505
+ }
506
+ editorContext . closeTab ( index ) ;
507
+ } ,
508
+ [ confirmClose , editorContext , executionContext ] ,
509
+ ) ;
510
+
511
+ const handleTabClick : MouseEventHandler < HTMLButtonElement > = useCallback (
512
+ event => {
513
+ const index = Number (
514
+ event . currentTarget . id . replace ( TAB_CLASS_PREFIX , '' ) ,
515
+ ) ;
516
+ /** TODO:
517
+ * Move everything after into `editorContext.changeTab` once zustand will be used instead of
518
+ * React context, since now we can't use execution context inside editor context, since editor
519
+ * context is used in execution context.
520
+ */
521
+ executionContext . stop ( ) ;
522
+ editorContext . changeTab ( index ) ;
523
+ } ,
524
+ [ editorContext , executionContext ] ,
525
+ ) ;
473
526
474
527
return (
475
528
< Tooltip . Provider >
@@ -482,7 +535,6 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
482
535
{ pluginContext ?. plugins . map ( ( plugin , index ) => {
483
536
const isVisible = plugin === pluginContext . visiblePlugin ;
484
537
const label = `${ isVisible ? 'Hide' : 'Show' } ${ plugin . title } ` ;
485
- const Icon = plugin . icon ;
486
538
return (
487
539
< Tooltip key = { plugin . title } label = { label } >
488
540
< UnStyledButton
@@ -492,7 +544,7 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
492
544
data-index = { index }
493
545
aria-label = { label }
494
546
>
495
- < Icon aria-hidden = "true" />
547
+ < plugin . icon aria-hidden = "true" />
496
548
</ UnStyledButton >
497
549
</ Tooltip >
498
550
) ;
@@ -571,22 +623,12 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
571
623
>
572
624
< Tab . Button
573
625
aria-controls = "graphiql-session"
574
- id = { `graphiql-session-tab-${ index } ` }
575
- onClick = { ( ) => {
576
- executionContext . stop ( ) ;
577
- editorContext . changeTab ( index ) ;
578
- } }
626
+ id = { `${ TAB_CLASS_PREFIX } ${ index } ` }
627
+ onClick = { handleTabClick }
579
628
>
580
629
{ tab . title }
581
630
</ Tab . Button >
582
- < Tab . Close
583
- onClick = { ( ) => {
584
- if ( editorContext . activeTabIndex === index ) {
585
- executionContext . stop ( ) ;
586
- }
587
- editorContext . closeTab ( index ) ;
588
- } }
589
- />
631
+ < Tab . Close onClick = { handleTabClose } />
590
632
</ Tab >
591
633
) ) }
592
634
{ addTab }
@@ -601,9 +643,9 @@ export function GraphiQLInterface(props: GraphiQLInterfaceProps) {
601
643
</ div >
602
644
< div
603
645
role = "tabpanel"
604
- id = "graphiql-session"
646
+ id = "graphiql-session" // used by aria-controls="graphiql-session"
605
647
className = "graphiql-session"
606
- aria-labelledby = { `graphiql-session-tab- ${ editorContext . activeTabIndex } ` }
648
+ aria-labelledby = { `${ TAB_CLASS_PREFIX } ${ editorContext . activeTabIndex } ` }
607
649
>
608
650
< div ref = { editorResize . firstRef } >
609
651
< div
0 commit comments