-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
index.js
155 lines (142 loc) · 4.67 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
* WordPress dependencies
*/
import {
__experimentalListView as ListView,
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
import { useFocusOnMount, useMergeRefs } from '@wordpress/compose';
import { useDispatch, useSelect } from '@wordpress/data';
import { focus } from '@wordpress/dom';
import { useCallback, useRef, useState } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import { useShortcut } from '@wordpress/keyboard-shortcuts';
import { ESCAPE } from '@wordpress/keycodes';
/**
* Internal dependencies
*/
import ListViewOutline from './list-view-outline';
import { unlock } from '../../lock-unlock';
import { store as editorStore } from '../../store';
const { TabbedSidebar } = unlock( blockEditorPrivateApis );
export default function ListViewSidebar() {
const { setIsListViewOpened } = useDispatch( editorStore );
const { getListViewToggleRef } = unlock( useSelect( editorStore ) );
// This hook handles focus when the sidebar first renders.
const focusOnMountRef = useFocusOnMount( 'firstElement' );
// When closing the list view, focus should return to the toggle button.
const closeListView = useCallback( () => {
setIsListViewOpened( false );
getListViewToggleRef().current?.focus();
}, [ getListViewToggleRef, setIsListViewOpened ] );
const closeOnEscape = useCallback(
( event ) => {
if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) {
event.preventDefault();
closeListView();
}
},
[ closeListView ]
);
// Use internal state instead of a ref to make sure that the component
// re-renders when the dropZoneElement updates.
const [ dropZoneElement, setDropZoneElement ] = useState( null );
// Tracks our current tab.
const [ tab, setTab ] = useState( 'list-view' );
// This ref refers to the sidebar as a whole.
const sidebarRef = useRef();
// This ref refers to the tab panel.
const tabsRef = useRef();
// This ref refers to the list view application area.
const listViewRef = useRef();
// Must merge the refs together so focus can be handled properly in the next function.
const listViewContainerRef = useMergeRefs( [
focusOnMountRef,
listViewRef,
setDropZoneElement,
] );
/*
* Callback function to handle list view or outline focus.
*
* @param {string} currentTab The current tab. Either list view or outline.
*
* @return void
*/
function handleSidebarFocus( currentTab ) {
// Tab panel focus.
const tabPanelFocus = focus.tabbable.find( tabsRef.current )[ 0 ];
// List view tab is selected.
if ( currentTab === 'list-view' ) {
// Either focus the list view or the tab panel. Must have a fallback because the list view does not render when there are no blocks.
const listViewApplicationFocus = focus.tabbable.find(
listViewRef.current
)[ 0 ];
const listViewFocusArea = sidebarRef.current.contains(
listViewApplicationFocus
)
? listViewApplicationFocus
: tabPanelFocus;
listViewFocusArea.focus();
// Outline tab is selected.
} else {
tabPanelFocus.focus();
}
}
const handleToggleListViewShortcut = useCallback( () => {
// If the sidebar has focus, it is safe to close.
if (
sidebarRef.current.contains(
sidebarRef.current.ownerDocument.activeElement
)
) {
closeListView();
} else {
// If the list view or outline does not have focus, focus should be moved to it.
handleSidebarFocus( tab );
}
}, [ closeListView, tab ] );
// This only fires when the sidebar is open because of the conditional rendering.
// It is the same shortcut to open but that is defined as a global shortcut and only fires when the sidebar is closed.
useShortcut( 'core/editor/toggle-list-view', handleToggleListViewShortcut );
return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className="editor-list-view-sidebar"
onKeyDown={ closeOnEscape }
ref={ sidebarRef }
>
<TabbedSidebar
tabs={ [
{
name: 'list-view',
title: _x( 'List View', 'Post overview' ),
panel: (
<div className="editor-list-view-sidebar__list-view-container">
<div className="editor-list-view-sidebar__list-view-panel-content">
<ListView
dropZoneElement={ dropZoneElement }
/>
</div>
</div>
),
panelRef: listViewContainerRef,
},
{
name: 'outline',
title: _x( 'Outline', 'Post overview' ),
panel: (
<div className="editor-list-view-sidebar__list-view-container">
<ListViewOutline />
</div>
),
},
] }
onClose={ closeListView }
onSelect={ ( tabName ) => setTab( tabName ) }
defaultTabId="list-view"
ref={ tabsRef }
closeButtonLabel={ __( 'Close' ) }
/>
</div>
);
}