@@ -291,6 +291,29 @@ var changeSearchShortcutKey = () => {
291
291
}
292
292
} ;
293
293
294
+ const closeDialogOnBackdropClick = ( {
295
+ currentTarget : dialog ,
296
+ clientX,
297
+ clientY,
298
+ } ) => {
299
+ if ( ! dialog . open ) {
300
+ return ;
301
+ }
302
+
303
+ // Dialog.getBoundingClientRect() does not include ::backdrop. (This is the
304
+ // trick that allows us to determine if click was inside or outside of the
305
+ // dialog: click handler includes backdrop, getBoundingClientRect does not.)
306
+ const { left, right, top, bottom } = dialog . getBoundingClientRect ( ) ;
307
+
308
+ // 0, 0 means top left
309
+ const clickWasOutsideDialog =
310
+ clientX < left || right < clientX || clientY < top || bottom < clientY ;
311
+
312
+ if ( clickWasOutsideDialog ) {
313
+ dialog . close ( ) ;
314
+ }
315
+ } ;
316
+
294
317
/**
295
318
* Activate callbacks for search button popup
296
319
*/
@@ -306,27 +329,7 @@ var setupSearchButtons = () => {
306
329
// If user clicks outside the search modal dialog, then close it.
307
330
const searchDialog = document . getElementById ( "pst-search-dialog" ) ;
308
331
// Dialog click handler includes clicks on dialog ::backdrop.
309
- searchDialog . addEventListener ( "click" , ( event ) => {
310
- if ( ! searchDialog . open ) {
311
- return ;
312
- }
313
-
314
- // Dialog.getBoundingClientRect() does not include ::backdrop. (This is the
315
- // trick that allows us to determine if click was inside or outside of the
316
- // dialog: click handler includes backdrop, getBoundingClientRect does not.)
317
- const { left, right, top, bottom } = searchDialog . getBoundingClientRect ( ) ;
318
-
319
- // 0, 0 means top left
320
- const clickWasOutsideDialog =
321
- event . clientX < left ||
322
- right < event . clientX ||
323
- event . clientY < top ||
324
- bottom < event . clientY ;
325
-
326
- if ( clickWasOutsideDialog ) {
327
- searchDialog . close ( ) ;
328
- }
329
- } ) ;
332
+ searchDialog . addEventListener ( "click" , closeDialogOnBackdropClick ) ;
330
333
} ;
331
334
332
335
/*******************************************************************************
@@ -535,7 +538,7 @@ function showVersionWarningBanner(data) {
535
538
const versionsAreComparable = validate ( version ) && validate ( preferredVersion ) ;
536
539
if ( versionsAreComparable && compare ( version , preferredVersion , "=" ) ) {
537
540
console . log (
538
- "This is the prefered version of the docs, not showing the warning banner." ,
541
+ "[PST]: This is the preferred version of the docs, not showing the warning banner." ,
539
542
) ;
540
543
return ;
541
544
}
@@ -665,84 +668,76 @@ async function fetchAndUseVersions() {
665
668
}
666
669
667
670
/*******************************************************************************
668
- * Add keyboard functionality to mobile sidebars.
669
- *
670
- * Wire up the hamburger-style buttons using the click event which (on buttons)
671
- * handles both mouse clicks and the space and enter keys.
671
+ * Sidebar modals (for mobile / narrow screens)
672
672
*/
673
673
function setupMobileSidebarKeyboardHandlers ( ) {
674
- // These are hidden checkboxes at the top of the page whose :checked property
675
- // allows the mobile sidebars to be hidden or revealed via CSS.
676
- const primaryToggle = document . getElementById ( "pst-primary-sidebar-checkbox" ) ;
677
- const secondaryToggle = document . getElementById (
678
- "pst-secondary-sidebar-checkbox" ,
674
+ // These are the left and right sidebars for wider screens. We cut and paste
675
+ // the content from these widescreen sidebars into the mobile dialogs, when
676
+ // the user clicks the hamburger icon button
677
+ const primarySidebar = document . getElementById ( "pst-primary-sidebar" ) ;
678
+ const secondarySidebar = document . getElementById ( "pst-secondary-sidebar" ) ;
679
+
680
+ // These are the corresponding left/right <dialog> elements, which are empty
681
+ // until the user clicks the hamburger icon
682
+ const primaryDialog = document . getElementById ( "pst-primary-sidebar-modal" ) ;
683
+ const secondaryDialog = document . getElementById (
684
+ "pst-secondary-sidebar-modal" ,
679
685
) ;
680
- const primarySidebar = document . querySelector ( ".bd-sidebar-primary" ) ;
681
- const secondarySidebar = document . querySelector ( ".bd-sidebar-secondary" ) ;
682
-
683
- // Toggle buttons -
684
- //
685
- // These are the hamburger-style buttons in the header nav bar. When the user
686
- // clicks, the button transmits the click to the hidden checkboxes used by the
687
- // CSS to control whether the sidebar is open or closed.
688
- const primaryClickTransmitter = document . querySelector ( ".primary-toggle" ) ;
689
- const secondaryClickTransmitter = document . querySelector ( ".secondary-toggle" ) ;
686
+
687
+ // These are the hamburger-style buttons in the header nav bar. They only
688
+ // appear at narrow screen width.
689
+ const primaryToggle = document . querySelector ( ".primary-toggle" ) ;
690
+ const secondaryToggle = document . querySelector ( ".secondary-toggle" ) ;
691
+
692
+ // Cut nodes and classes from `from`, paste into/onto `to`
693
+ const cutAndPasteNodesAndClasses = ( from , to ) => {
694
+ Array . from ( from . childNodes ) . forEach ( ( node ) => to . appendChild ( node ) ) ;
695
+ Array . from ( from . classList ) . forEach ( ( cls ) => {
696
+ from . classList . remove ( cls ) ;
697
+ to . classList . add ( cls ) ;
698
+ } ) ;
699
+ } ;
700
+
701
+ // Hook up the ways to open and close the dialog
690
702
[
691
- [ primaryClickTransmitter , primaryToggle , primarySidebar ] ,
692
- [ secondaryClickTransmitter , secondaryToggle , secondarySidebar ] ,
693
- ] . forEach ( ( [ clickTransmitter , toggle , sidebar ] ) => {
694
- if ( ! clickTransmitter ) {
703
+ [ primaryToggle , primaryDialog , primarySidebar ] ,
704
+ [ secondaryToggle , secondaryDialog , secondarySidebar ] ,
705
+ ] . forEach ( ( [ toggleButton , dialog , sidebar ] ) => {
706
+ if ( ! toggleButton || ! dialog || ! sidebar ) {
695
707
return ;
696
708
}
697
- clickTransmitter . addEventListener ( "click" , ( event ) => {
709
+
710
+ // Clicking the button can only open the sidebar, not close it.
711
+ // Clicking the button is also the *only* way to open the sidebar.
712
+ toggleButton . addEventListener ( "click" , ( event ) => {
698
713
event . preventDefault ( ) ;
699
714
event . stopPropagation ( ) ;
700
- toggle . checked = ! toggle . checked ;
701
-
702
- // If we are opening the sidebar, move focus to the first focusable item
703
- // in the sidebar
704
- if ( toggle . checked ) {
705
- // Note: this selector is not exhaustive, and we may need to update it
706
- // in the future
707
- const tabStop = sidebar . querySelector ( "a, button" ) ;
708
- // use setTimeout because you cannot move focus synchronously during a
709
- // click in the handler for the click event
710
- setTimeout ( ( ) => tabStop . focus ( ) , 100 ) ;
711
- }
715
+
716
+ // When we open the dialog, we cut and paste the nodes and classes from
717
+ // the widescreen sidebar into the dialog
718
+ cutAndPasteNodesAndClasses ( sidebar , dialog ) ;
719
+
720
+ dialog . showModal ( ) ;
712
721
} ) ;
713
- } ) ;
714
722
715
- // Escape key -
716
- //
717
- // When sidebar is open, user should be able to press escape key to close the
718
- // sidebar.
719
- [
720
- [ primarySidebar , primaryToggle , primaryClickTransmitter ] ,
721
- [ secondarySidebar , secondaryToggle , secondaryClickTransmitter ] ,
722
- ] . forEach ( ( [ sidebar , toggle , transmitter ] ) => {
723
- if ( ! sidebar ) {
724
- return ;
725
- }
726
- sidebar . addEventListener ( "keydown" , ( event ) => {
723
+ // Listen for clicks on the backdrop in order to close the dialog
724
+ dialog . addEventListener ( "click" , closeDialogOnBackdropClick ) ;
725
+
726
+ // We have to manually attach the escape key because there's some code in
727
+ // Sphinx's Sphinx_highlight.js that prevents the default behavior of the
728
+ // escape key
729
+ dialog . addEventListener ( "keydown" , ( event ) => {
727
730
if ( event . key === "Escape" ) {
728
731
event . preventDefault ( ) ;
729
732
event . stopPropagation ( ) ;
730
- toggle . checked = false ;
731
- transmitter . focus ( ) ;
733
+ dialog . close ( ) ;
732
734
}
733
735
} ) ;
734
- } ) ;
735
736
736
- // When the <label> overlay is clicked to close the sidebar, return focus to
737
- // the opener button in the nav bar.
738
- [
739
- [ primaryToggle , primaryClickTransmitter ] ,
740
- [ secondaryToggle , secondaryClickTransmitter ] ,
741
- ] . forEach ( ( [ toggle , transmitter ] ) => {
742
- toggle . addEventListener ( "change" , ( event ) => {
743
- if ( ! event . currentTarget . checked ) {
744
- transmitter . focus ( ) ;
745
- }
737
+ // When the dialog is closed, move the nodes (and classes) back to their
738
+ // original place
739
+ dialog . addEventListener ( "close" , ( ) => {
740
+ cutAndPasteNodesAndClasses ( dialog , sidebar ) ;
746
741
} ) ;
747
742
} ) ;
748
743
}
0 commit comments