From 12b2c4d3e2af6112010605b62c67460ae2140039 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 14 Dec 2021 13:29:07 -0500 Subject: [PATCH 001/286] dummy menu button component --- src/App.jsx | 3 +++ src/Menu/MenuButton.jsx | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/Menu/MenuButton.jsx diff --git a/src/App.jsx b/src/App.jsx index 98d47e47..2c1ad3e9 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,6 +2,7 @@ import React from 'react'; //Components and Styles import Accordion from 'src/Accordion'; +import MenuButton from 'src/Menu/MenuButton'; function onDummySubmit(event) { event.preventDefault(); @@ -38,6 +39,8 @@ function App() {

Accordion

+

Menu, Menubar, Menu Button

+
); } diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx new file mode 100644 index 00000000..68c501b7 --- /dev/null +++ b/src/Menu/MenuButton.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +function MenuButton(props) { + return ( + + ); +} + +MenuButton.propTypes = { +}; + +MenuButton.defaultProps = { +}; + +export default MenuButton; From 17828f52da3deefae91e205dbd448445d4bee2dc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 14 Dec 2021 13:58:50 -0500 Subject: [PATCH 002/286] fill out some more attributes/props for MenuButton --- src/App.jsx | 4 +++- src/Menu/MenuButton.jsx | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 2c1ad3e9..e1a9cd27 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -40,7 +40,9 @@ function App() {

Accordion

Menu, Menubar, Menu Button

- + + Menu Button + ); } diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 68c501b7..9e816e57 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -2,17 +2,28 @@ import React from 'react'; import PropTypes from 'prop-types'; function MenuButton(props) { + const { children, isExpanded } = props; + + //TODO: point aria-controls to the menu (optional) + //TODO: keyboard interaction return ( - ); } MenuButton.propTypes = { + children: PropTypes.node.isRequired, + isExpanded: PropTypes.bool, }; MenuButton.defaultProps = { + isExpanded: false, }; export default MenuButton; From ae6e8f6162f30a4530cf3c5d76e00fceafeed545 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 14 Dec 2021 13:58:58 -0500 Subject: [PATCH 003/286] dummy Menu component --- src/Menu/Menu.jsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/Menu/Menu.jsx diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx new file mode 100644 index 00000000..8cce6fac --- /dev/null +++ b/src/Menu/Menu.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +function Menu(props) { + return ( +
+ Hello world! +
+ ); +} + +Menu.propTypes = { +}; + +Menu.defaultProps = { +}; + +export default Menu; From 056e9aa95c65300c9621949a04e4c732f6a763a0 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 14 Dec 2021 14:41:38 -0500 Subject: [PATCH 004/286] fill out some props and attributes for Menu --- src/Menu/Menu.jsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 8cce6fac..21df6e64 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -2,17 +2,26 @@ import React from 'react'; import PropTypes from 'prop-types'; function Menu(props) { + const { children, orientation } = props; + + //TODO keyboard support if orientation is horizontal? return ( -
- Hello world! -
+
    + { children } +
); } Menu.propTypes = { + children: PropTypes.node.isRequired, + orientation: PropTypes.oneOf(['vertical', 'horizontal']), }; Menu.defaultProps = { + orientation: 'vertical', }; export default Menu; From 4aaf3d262db85fd9d4490bea21f85c7fcec46ba6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 14 Dec 2021 14:48:08 -0500 Subject: [PATCH 005/286] dummy menubar component --- src/App.jsx | 5 +++++ src/Menu/Menu.jsx | 5 ++--- src/Menu/MenuButton.jsx | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index e1a9cd27..dbb5251f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,6 +3,7 @@ import React from 'react'; //Components and Styles import Accordion from 'src/Accordion'; import MenuButton from 'src/Menu/MenuButton'; +import MenuBar from 'src/Menu/MenuBar'; function onDummySubmit(event) { event.preventDefault(); @@ -43,6 +44,10 @@ function App() { Menu Button + +
  • Hello
  • +
  • World!
  • +
    ); } diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 21df6e64..e493e84b 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -3,8 +3,7 @@ import PropTypes from 'prop-types'; function Menu(props) { const { children, orientation } = props; - - //TODO keyboard support if orientation is horizontal? + return (
      Date: Tue, 14 Dec 2021 14:50:06 -0500 Subject: [PATCH 006/286] forgot to git add --- src/Menu/MenuBar.jsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/Menu/MenuBar.jsx diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx new file mode 100644 index 00000000..8d393e69 --- /dev/null +++ b/src/Menu/MenuBar.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +function MenuBar(props) { + const { children, orientation } = props; + + return ( +
        + { children } +
      + ); +} + +MenuBar.propTypes = { + children: PropTypes.node.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), +}; + +MenuBar.defaultProps = { + orientation: 'horizontal', +}; + +export default MenuBar; From 4d4c2ee4459742a70491ee6668a3b42261c92952 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 14 Dec 2021 19:07:08 -0500 Subject: [PATCH 007/286] add a label and labelId prop to MenuBar --- src/Menu/MenuBar.jsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 8d393e69..51af77cb 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -2,21 +2,32 @@ import React from 'react'; import PropTypes from 'prop-types'; function MenuBar(props) { - const { children, orientation } = props; + const { children, orientation, label, labelId } = props; return (
        { children }
      ); } +/** + * Some notes on props: + * + * - If the menubar has a visible label, a labelId prop that points towards + * the labeling element should be provided. Otherwise, one should pass in + * a label via the label prop. In other words, one XOR the other must be provided. + */ MenuBar.propTypes = { children: PropTypes.node.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, //eslint-disable-line react/require-default-props + labelId: PropTypes.string, //eslint-disable-line react/require-default-props }; MenuBar.defaultProps = { From 8d1bc676a84d7c35cb1f25848442f439357609df Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 14 Dec 2021 19:08:22 -0500 Subject: [PATCH 008/286] make MenuBar a class component for now --- src/Menu/MenuBar.jsx | 53 +++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 51af77cb..d4203bdd 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -1,37 +1,40 @@ import React from 'react'; import PropTypes from 'prop-types'; -function MenuBar(props) { - const { children, orientation, label, labelId } = props; - - return ( -
        - { children } -
      - ); -} - -/** +/* * Some notes on props: * * - If the menubar has a visible label, a labelId prop that points towards * the labeling element should be provided. Otherwise, one should pass in * a label via the label prop. In other words, one XOR the other must be provided. */ -MenuBar.propTypes = { - children: PropTypes.node.isRequired, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - label: PropTypes.string, //eslint-disable-line react/require-default-props - labelId: PropTypes.string, //eslint-disable-line react/require-default-props -}; +class MenuBar extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, //eslint-disable-line react/require-default-props + labelId: PropTypes.string, //eslint-disable-line react/require-default-props + }; -MenuBar.defaultProps = { - orientation: 'horizontal', -}; + static defaultProps = { + orientation: 'horizontal', + }; + + //---- Rendering ---- + render() { + const { children, orientation, label, labelId } = this.props; + + return ( +
        + { children } +
      + ); + } +} export default MenuBar; From 8e6b1f3ca0e34ad2e7474a9897a868de23d31ff1 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 15 Dec 2021 12:48:22 -0500 Subject: [PATCH 009/286] hardcode a menubar for now --- src/App.jsx | 5 +--- src/Menu/MenuBar.jsx | 64 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index dbb5251f..33c0c52b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -44,10 +44,7 @@ function App() { Menu Button - -
    • Hello
    • -
    • World!
    • -
      + ); } diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d4203bdd..74aa9eb7 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; */ class MenuBar extends React.Component { static propTypes = { - children: PropTypes.node.isRequired, +// children: PropTypes.node.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, //eslint-disable-line react/require-default-props labelId: PropTypes.string, //eslint-disable-line react/require-default-props @@ -31,7 +31,67 @@ class MenuBar extends React.Component { aria-labelledby={ labelId } aria-label={ label } > - { children } +
    • + + Parent Menuitem 1 + +
        +
      • + Hello world! +
      • +
      • + Hello world! +
      • +
      • + Hello world! +
      • +
      +
    • +
    • + + Parent Menuitem 2 + +
        +
      • + Hello world! +
      • +
      • + Hello world! +
      • +
      • + Hello world! +
      • +
      +
    • +
    • + Hello world! +
    • +
    • + + Parent Menuitem 3 + + +
    ); } From 4290fa57c283bef9b5fa29a3335fff219a86b34f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 15 Dec 2021 12:51:23 -0500 Subject: [PATCH 010/286] add "aria-haspopup" attribute --- src/Menu/MenuBar.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 74aa9eb7..547da688 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -10,7 +10,7 @@ import PropTypes from 'prop-types'; */ class MenuBar extends React.Component { static propTypes = { -// children: PropTypes.node.isRequired, + children: PropTypes.node.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, //eslint-disable-line react/require-default-props labelId: PropTypes.string, //eslint-disable-line react/require-default-props @@ -32,7 +32,7 @@ class MenuBar extends React.Component { aria-label={ label } >
  • - + Parent Menuitem 1
      @@ -48,7 +48,7 @@ class MenuBar extends React.Component {
  • - + Parent Menuitem 2
      @@ -67,7 +67,7 @@ class MenuBar extends React.Component { Hello world!
    • - + Parent Menuitem 3
        @@ -78,7 +78,7 @@ class MenuBar extends React.Component { Hello world!
      • - + Nested Parent Menuitem
          From 96459f5fbb575581069c71c3c96e73894e0da94c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 15 Dec 2021 12:52:18 -0500 Subject: [PATCH 011/286] hardcode aria-expanded attribute --- src/Menu/MenuBar.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 547da688..5846f2cc 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -32,7 +32,7 @@ class MenuBar extends React.Component { aria-label={ label } >
        • - + Parent Menuitem 1
            @@ -48,7 +48,7 @@ class MenuBar extends React.Component {
        • - + Parent Menuitem 2
            @@ -67,7 +67,7 @@ class MenuBar extends React.Component { Hello world!
          • - + Parent Menuitem 3
              @@ -78,7 +78,7 @@ class MenuBar extends React.Component { Hello world!
            • - + Nested Parent Menuitem
                From b582abd926627a70a623fc41fb171324bd2b9a16 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 15 Dec 2021 13:10:54 -0500 Subject: [PATCH 012/286] hardcode aria-disabled attribute --- src/Menu/MenuBar.jsx | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 5846f2cc..4bca9116 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -32,60 +32,60 @@ class MenuBar extends React.Component { aria-label={ label } >
              • - + Parent Menuitem 1
                  -
                • +
                • Hello world!
                • -
                • +
                • Hello world!
                • -
                • +
                • Hello world!
              • - + Parent Menuitem 2
                  -
                • +
                • Hello world!
                • -
                • +
                • Hello world!
                • -
                • +
                • Hello world!
              • -
              • +
              • Hello world!
              • - + Parent Menuitem 3
                  -
                • +
                • Hello world!
                • -
                • +
                • Hello world!
                • - + Nested Parent Menuitem
                    -
                  • +
                  • Hello world!
                  • -
                  • +
                  • Hello world!
                  From 5f11cbc05591f7f0cc43e35e9c2da01551c705d7 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 15 Dec 2021 14:52:36 -0500 Subject: [PATCH 013/286] add a css visual hide technique from webaim --- src/Menu/MenuBar.jsx | 6 ++++++ src/styles.scss | 14 +++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 4bca9116..46a6e0a7 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -20,6 +20,12 @@ class MenuBar extends React.Component { orientation: 'horizontal', }; + constructor(props) { + super(props); + } + + //---- Events ---- + //---- Rendering ---- render() { const { children, orientation, label, labelId } = this.props; diff --git a/src/styles.scss b/src/styles.scss index 6821de35..dfe54165 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,2 +1,14 @@ -#reactRoot { +//A technique for visually-hiding elements +//but still keeping them readable to machines. +//See: +//https://webaim.org/techniques/css/invisiblecontent/ +.vhide { + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + width: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute } From 82e9ddf6c29b9fe7f472993f4a9e8f6ca0a26b33 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 21 Dec 2021 14:43:59 -0500 Subject: [PATCH 014/286] add a class for display: none; --- src/styles.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles.scss b/src/styles.scss index dfe54165..6e96ebaf 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,3 +1,7 @@ +.hidden { + display: none; +} + //A technique for visually-hiding elements //but still keeping them readable to machines. //See: From aba63798da100ab39841f12407fa3c25da8ccb36 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 21 Dec 2021 14:52:18 -0500 Subject: [PATCH 015/286] disable tabindex for all but the first menu item --- src/Menu/MenuBar.jsx | 58 ++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 46a6e0a7..f2a87c23 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -38,60 +38,88 @@ class MenuBar extends React.Component { aria-label={ label } >
                • - + Parent Menuitem 1
                    -
                  • +
                  • Hello world!
                  • -
                  • +
                  • Hello world!
                  • -
                  • +
                  • Hello world!
                • - + Parent Menuitem 2
                    -
                  • +
                  • Hello world!
                  • -
                  • +
                  • Hello world!
                  • -
                  • +
                  • Hello world!
                • -
                • +
                • Hello world!
                • - + Parent Menuitem 3
                    -
                  • +
                  • Hello world!
                  • -
                  • +
                  • Hello world!
                  • - + Nested Parent Menuitem
                      -
                    • +
                    • Hello world!
                    • -
                    • +
                    • Hello world!
                    From c92720101b73ce8db749878ec882d2f9c456dbc2 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 14:17:42 -0500 Subject: [PATCH 016/286] hardcoded ParentMenuItem --- src/Menu/MenuBar.jsx | 32 +++++++++---------------------- src/Menu/ParentMenuItem.jsx | 38 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 src/Menu/ParentMenuItem.jsx diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f2a87c23..cf1c3186 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -1,6 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; +//Components and Styles +import ParentMenuItem from 'src/Menu/ParentMenuItem'; + /* * Some notes on props: * @@ -12,6 +15,11 @@ class MenuBar extends React.Component { static propTypes = { children: PropTypes.node.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + menu: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.oneOf(['menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemradio', 'separator']), + node: PropTypes.node.isRequired, + menuItems: PropTypes.array, //only relevant for "parentmenuitem" + })).isRequired, label: PropTypes.string, //eslint-disable-line react/require-default-props labelId: PropTypes.string, //eslint-disable-line react/require-default-props }; @@ -37,29 +45,7 @@ class MenuBar extends React.Component { aria-labelledby={ labelId } aria-label={ label } > -
                  • - - Parent Menuitem 1 - -
                      -
                    • - Hello world! -
                    • -
                    • - Hello world! -
                    • -
                    • - Hello world! -
                    • -
                    -
                  • +
                  • + + Parent Menuitem 1 + +
                      +
                    • + Hello world! +
                    • +
                    • + Hello world! +
                    • +
                    • + Hello world! +
                    • +
                    +
                  • + ); +} + +ParentMenuItem.propTypes = { +}; + +ParentMenuItem.defaultProps = { +}; + +export default ParentMenuItem; From 1ef03c340b6dffcca1d458e0f3eb47d2471093f8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 14:29:29 -0500 Subject: [PATCH 017/286] begin filling out proptypes for ParentMenuItem --- src/Menu/MenuBar.jsx | 11 ++++++++++- src/Menu/ParentMenuItem.jsx | 18 +++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index cf1c3186..d1e7c6ad 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -4,6 +4,12 @@ import PropTypes from 'prop-types'; //Components and Styles import ParentMenuItem from 'src/Menu/ParentMenuItem'; +const MENU_ITEMS_1 = [ + { type: 'menuitem', node: 'Hello world!' }, + { type: 'menuitem', node: 'Hello world!' }, + { type: 'menuitem', node: 'Hello world!' }, +]; + /* * Some notes on props: * @@ -45,7 +51,10 @@ class MenuBar extends React.Component { aria-labelledby={ labelId } aria-label={ label } > - +
                  • - Parent Menuitem 1 + { node }
                    • @@ -30,9 +32,19 @@ function ParentMenuItem(props) { } ParentMenuItem.propTypes = { + node: PropTypes.node.isRequired, + menuItems: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.oneOf(['menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator']), + node: PropTypes.node.isRequired, + menuItems: PropTypes.array, //only relevant for "parentmenuitem" + })).isRequired, + isExpanded: PropTypes.bool, + isDisabled: PropTypes.bool, }; ParentMenuItem.defaultProps = { + isExpanded: false, + isDisabled: false, }; export default ParentMenuItem; From 464ea2c46f73d741a4c79ed40d76a89105998c41 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 15:48:30 -0500 Subject: [PATCH 018/286] add a menuitem component --- src/Menu/MenuBar.jsx | 15 ++++++++++++--- src/Menu/MenuItem.jsx | 23 +++++++++++++++++++++++ src/Menu/ParentMenuItem.jsx | 36 +++++++++++++++++++++++++----------- 3 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 src/Menu/MenuItem.jsx diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d1e7c6ad..1ef7a2dd 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -5,9 +5,18 @@ import PropTypes from 'prop-types'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; const MENU_ITEMS_1 = [ - { type: 'menuitem', node: 'Hello world!' }, - { type: 'menuitem', node: 'Hello world!' }, - { type: 'menuitem', node: 'Hello world!' }, + { + type: 'menuitem', + node: 'Hello world!' + }, + { + type: 'menuitem', + node: 'Hello world!' + }, + { + type: 'menuitem', + node: 'Hello world!' + }, ]; /* diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx new file mode 100644 index 00000000..3f0067cc --- /dev/null +++ b/src/Menu/MenuItem.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +function MenuItem(props) { + const { children, isDisabled } = props; + + return ( +
                    • + { children } +
                    • + ); +} + +MenuItem.propTypes = { + children: PropTypes.node.isRequired, + isDisabled: PropTypes.bool, +}; + +MenuItem.defaultProps = { + isDisabled: false, +}; + +export default MenuItem; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8e3f5c43..b4e5cb8e 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -1,8 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; +//Components and Styles +import MenuItem from 'src/Menu/MenuItem'; + function ParentMenuItem(props) { - const { node, menuItems, isExpanded, isDisabled } = props; + const { node, menuItems, isExpanded, isDisabled, renderMenuItem } = props; + const menuItemNodes = menuItems.map((mi, index, array) => { + return renderMenuItem(mi, index, array, props); + }); return (
                    • @@ -17,15 +23,7 @@ function ParentMenuItem(props) { { node }
                        -
                      • - Hello world! -
                      • -
                      • - Hello world! -
                      • -
                      • - Hello world! -
                      • + { menuItemNodes }
                    • ); @@ -36,15 +34,31 @@ ParentMenuItem.propTypes = { menuItems: PropTypes.arrayOf(PropTypes.shape({ type: PropTypes.oneOf(['menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator']), node: PropTypes.node.isRequired, - menuItems: PropTypes.array, //only relevant for "parentmenuitem" + menuItems: PropTypes.array, //only required for "parentmenuitem" })).isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, + renderMenuItem: PropTypes.func, }; ParentMenuItem.defaultProps = { isExpanded: false, isDisabled: false, + renderMenuItem: function renderMenuItem(menuItem, index, array, props) { + const { type, node, menuItems } = menuItem; + let Element; + + if(type === 'menuitem') + Element = MenuItem; + else + Element = MenuItem; + + return ( + + { node } + + ); + }, }; export default ParentMenuItem; From 62bcf1da5c21b42ce522adfc93b2ae75a8826cdc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 16:09:05 -0500 Subject: [PATCH 019/286] maybe it"d be good to allow render props for individual menu items --- src/Menu/MenuBar.jsx | 34 +++++++++++++++++----------------- src/Menu/MenuItem.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 35 ++++++++++++++++++++++------------- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 1ef7a2dd..40df0fea 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -7,15 +7,15 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; const MENU_ITEMS_1 = [ { type: 'menuitem', - node: 'Hello world!' + node: 'Hello world!', }, { type: 'menuitem', - node: 'Hello world!' + node: 'Hello world!', }, { type: 'menuitem', - node: 'Hello world!' + node: 'Hello world!', }, ]; @@ -30,10 +30,10 @@ class MenuBar extends React.Component { static propTypes = { children: PropTypes.node.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - menu: PropTypes.arrayOf(PropTypes.shape({ - type: PropTypes.oneOf(['menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemradio', 'separator']), + menuItems: PropTypes.arrayOf(PropTypes.shape({ + type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemradio', 'separator' ]), node: PropTypes.node.isRequired, - menuItems: PropTypes.array, //only relevant for "parentmenuitem" + menuItems: PropTypes.array, //Only relevant for "parentmenuitem" })).isRequired, label: PropTypes.string, //eslint-disable-line react/require-default-props labelId: PropTypes.string, //eslint-disable-line react/require-default-props @@ -71,23 +71,23 @@ class MenuBar extends React.Component { aria-haspopup="menu" aria-expanded={ false } aria-disabled={ false } - tabindex="-1" + tabIndex="-1" > Parent Menuitem 2
                        -
                      • +
                      • Hello world!
                      • -
                      • +
                      • Hello world!
                      • -
                      • +
                      • Hello world!
                      -
                    • +
                    • Hello world!
                    • @@ -97,15 +97,15 @@ class MenuBar extends React.Component { aria-haspopup="menu" aria-expanded={ false } aria-disabled={ false } - tabindex="-1" + tabIndex="-1" > Parent Menuitem 3
                        -
                      • +
                      • Hello world!
                      • -
                      • +
                      • Hello world!
                      • @@ -115,15 +115,15 @@ class MenuBar extends React.Component { aria-haspopup="menu" aria-expanded={ false } aria-disabled={ false } - tabindex="-1" + tabIndex="-1" > Nested Parent Menuitem
                          -
                        • +
                        • Hello world!
                        • -
                        • +
                        • Hello world!
                        diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 3f0067cc..24e151a2 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -5,7 +5,7 @@ function MenuItem(props) { const { children, isDisabled } = props; return ( -
                      • +
                      • { children }
                      • ); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index b4e5cb8e..2b034008 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -5,9 +5,9 @@ import PropTypes from 'prop-types'; import MenuItem from 'src/Menu/MenuItem'; function ParentMenuItem(props) { - const { node, menuItems, isExpanded, isDisabled, renderMenuItem } = props; + const { node, menuItems, isExpanded, isDisabled, renderItem } = props; const menuItemNodes = menuItems.map((mi, index, array) => { - return renderMenuItem(mi, index, array, props); + return renderItem(mi, index, array, props); }); return ( @@ -18,7 +18,7 @@ function ParentMenuItem(props) { aria-haspopup="menu" aria-expanded={ isExpanded } aria-disabled={ isDisabled } - tabindex="0" + tabIndex="0" > { node } @@ -32,31 +32,40 @@ function ParentMenuItem(props) { ParentMenuItem.propTypes = { node: PropTypes.node.isRequired, menuItems: PropTypes.arrayOf(PropTypes.shape({ - type: PropTypes.oneOf(['menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator']), + type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator' ]), node: PropTypes.node.isRequired, - menuItems: PropTypes.array, //only required for "parentmenuitem" + menuItems: PropTypes.array, //Only required for "parentmenuitem" + props: PropTypes.object, })).isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, - renderMenuItem: PropTypes.func, + renderItem: PropTypes.func, + renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types }; ParentMenuItem.defaultProps = { isExpanded: false, isDisabled: false, - renderMenuItem: function renderMenuItem(menuItem, index, array, props) { - const { type, node, menuItems } = menuItem; - let Element; + renderItem: function renderItem(menuItem, index, array, parentItemProps) { + const { renderMenuItem } = parentItemProps; + const { type } = menuItem; + let node; if(type === 'menuitem') - Element = MenuItem; + node = renderMenuItem(menuItem, index, array, parentItemProps); else - Element = MenuItem; + node = renderMenuItem(menuItem, index, array, parentItemProps); + + return node; + }, + renderMenuItem: function renderMenuItem(menuItem, index) { + const { node, props = {} } = menuItem; + const { isDisabled } = props; return ( - + { node } - + ); }, }; From f4bb23e13eef6fd4cf5ae88a1d7b25af82903193 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 16:14:08 -0500 Subject: [PATCH 020/286] get rid of MenuBar"s children prop --- src/Menu/MenuBar.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 40df0fea..b1ea6517 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -28,7 +28,6 @@ const MENU_ITEMS_1 = [ */ class MenuBar extends React.Component { static propTypes = { - children: PropTypes.node.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), menuItems: PropTypes.arrayOf(PropTypes.shape({ type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemradio', 'separator' ]), @@ -51,7 +50,7 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { - const { children, orientation, label, labelId } = this.props; + const { orientation, label, labelId } = this.props; return (
                          Date: Mon, 27 Dec 2021 16:36:12 -0500 Subject: [PATCH 021/286] MenuBar renders based on menuItems prop --- src/App.jsx | 73 ++++++++++++++++++- src/Menu/MenuBar.jsx | 135 +++++++++++++----------------------- src/Menu/ParentMenuItem.jsx | 26 +++++-- 3 files changed, 143 insertions(+), 91 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 33c0c52b..e3eee2a9 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -35,6 +35,77 @@ const DUMMY_ACCORDION_SECTIONS = [ }, ]; +const MENU_ITEMS = [ + { + type: 'parentmenuitem', + node: 'Parent Menuitem 1', + menuItems: [ + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + ], + }, + { + type: 'parentmenuitem', + node: 'Parent Menuitem 2', + menuItems: [ + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + ], + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'parentmenuitem', + node: 'Parent Menuitem 3', + menuItems: [ + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'parentmenuitem', + node: 'Nested Parent Menuitem', + menuItems: [ + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + ], + }, + ], + }, +]; + function App() { return (
                          @@ -44,7 +115,7 @@ function App() { Menu Button - +
                          ); } diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index b1ea6517..3a902cd8 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -2,23 +2,9 @@ import React from 'react'; import PropTypes from 'prop-types'; //Components and Styles +import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; -const MENU_ITEMS_1 = [ - { - type: 'menuitem', - node: 'Hello world!', - }, - { - type: 'menuitem', - node: 'Hello world!', - }, - { - type: 'menuitem', - node: 'Hello world!', - }, -]; - /* * Some notes on props: * @@ -33,13 +19,56 @@ class MenuBar extends React.Component { type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemradio', 'separator' ]), node: PropTypes.node.isRequired, menuItems: PropTypes.array, //Only relevant for "parentmenuitem" + props: PropTypes.object, })).isRequired, label: PropTypes.string, //eslint-disable-line react/require-default-props labelId: PropTypes.string, //eslint-disable-line react/require-default-props + renderItem: PropTypes.func, + renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types + renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types }; static defaultProps = { orientation: 'horizontal', + renderItem: function renderItem(menuItem, index, array, menuBarProps) { + const { renderMenuItem, renderParentMenuItem } = menuBarProps; + const { type } = menuItem; + let node; + + if(type === 'menuitem') + node = renderMenuItem(menuItem, index, array, menuBarProps); + else if(type === 'parentmenuitem') + node = renderParentMenuItem(menuItem, index, array, menuBarProps); + else + node = renderMenuItem(menuItem, index, array, menuBarProps); + + return node; + }, + renderMenuItem: function renderMenuItem(menuItem, index) { + const { node, props = {} } = menuItem; + const { isDisabled } = props; + + return ( + + { node } + + ); + }, + renderParentMenuItem: function renderParentMenuItem(menuItem, index) { + const { node, menuItems, props = {} } = menuItem; + const { isDisabled, isEnabled } = props; + + return ( + + { node } + + ); + }, }; constructor(props) { @@ -50,7 +79,10 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { - const { orientation, label, labelId } = this.props; + const { orientation, menuItems, label, labelId, renderItem } = this.props; + const renderedMenuItems = menuItems.map((menuItem, index, array) => { + return renderItem(menuItem, index, array, this.props); + }); return ( ); } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 2b034008..46db1966 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import MenuItem from 'src/Menu/MenuItem'; function ParentMenuItem(props) { - const { node, menuItems, isExpanded, isDisabled, renderItem } = props; + const { children, menuItems, isExpanded, isDisabled, renderItem } = props; const menuItemNodes = menuItems.map((mi, index, array) => { return renderItem(mi, index, array, props); }); @@ -20,7 +20,7 @@ function ParentMenuItem(props) { aria-disabled={ isDisabled } tabIndex="0" > - { node } + { children }
                            { menuItemNodes } @@ -30,7 +30,7 @@ function ParentMenuItem(props) { } ParentMenuItem.propTypes = { - node: PropTypes.node.isRequired, + children: PropTypes.node.isRequired, menuItems: PropTypes.arrayOf(PropTypes.shape({ type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator' ]), node: PropTypes.node.isRequired, @@ -41,18 +41,21 @@ ParentMenuItem.propTypes = { isDisabled: PropTypes.bool, renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types + renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types }; ParentMenuItem.defaultProps = { isExpanded: false, isDisabled: false, renderItem: function renderItem(menuItem, index, array, parentItemProps) { - const { renderMenuItem } = parentItemProps; + const { renderMenuItem, renderParentMenuItem } = parentItemProps; const { type } = menuItem; let node; if(type === 'menuitem') node = renderMenuItem(menuItem, index, array, parentItemProps); + else if(type === 'parentmenuitem') + node = renderParentMenuItem(menuItem, index, array, parentItemProps); else node = renderMenuItem(menuItem, index, array, parentItemProps); @@ -68,6 +71,21 @@ ParentMenuItem.defaultProps = { ); }, + renderParentMenuItem: function renderParentMenuItem(menuItem, index) { + const { node, menuItems, props = {} } = menuItem; + const { isDisabled, isEnabled } = props; + + return ( + + { node } + + ); + }, }; export default ParentMenuItem; From 6653f3c9c529920367b13970cb0f207eb67cc940 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 16:47:17 -0500 Subject: [PATCH 022/286] move proptype definitions to central location --- src/Menu/MenuBar.jsx | 10 ++++------ src/Menu/ParentMenuItem.jsx | 10 ++++------ src/utils/propTypes.js | 11 +++++++++++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 3a902cd8..5549e758 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -5,6 +5,9 @@ import PropTypes from 'prop-types'; import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; +//Misc. +import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; + /* * Some notes on props: * @@ -15,12 +18,7 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; class MenuBar extends React.Component { static propTypes = { orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - menuItems: PropTypes.arrayOf(PropTypes.shape({ - type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemradio', 'separator' ]), - node: PropTypes.node.isRequired, - menuItems: PropTypes.array, //Only relevant for "parentmenuitem" - props: PropTypes.object, - })).isRequired, + menuItems: MENU_ITEMS_PROPTYPE.isRequired, label: PropTypes.string, //eslint-disable-line react/require-default-props labelId: PropTypes.string, //eslint-disable-line react/require-default-props renderItem: PropTypes.func, diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 46db1966..c5eab542 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -4,6 +4,9 @@ import PropTypes from 'prop-types'; //Components and Styles import MenuItem from 'src/Menu/MenuItem'; +//Misc. +import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; + function ParentMenuItem(props) { const { children, menuItems, isExpanded, isDisabled, renderItem } = props; const menuItemNodes = menuItems.map((mi, index, array) => { @@ -31,12 +34,7 @@ function ParentMenuItem(props) { ParentMenuItem.propTypes = { children: PropTypes.node.isRequired, - menuItems: PropTypes.arrayOf(PropTypes.shape({ - type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator' ]), - node: PropTypes.node.isRequired, - menuItems: PropTypes.array, //Only required for "parentmenuitem" - props: PropTypes.object, - })).isRequired, + menuItems: MENU_ITEMS_PROPTYPE.isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, renderItem: PropTypes.func, diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index 404f87fe..2d73aae5 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -1,3 +1,5 @@ +import PropTypes from 'prop-types'; + /** * Creates a custom React prop type. * @@ -47,3 +49,12 @@ function _validateHeaderLevelProp(props, propName) { const validateHeaderLevelProp = createCustomPropType(_validateHeaderLevelProp); validateHeaderLevelProp.isRequired = createCustomPropType(_validateHeaderLevelProp, true); export { validateHeaderLevelProp }; + +export const MENU_ITEM_PROPTYPE = PropTypes.shape({ + type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator' ]), + node: PropTypes.node.isRequired, + menuItems: PropTypes.array, //Only required for "parentmenuitem" + props: PropTypes.object, +}); + +export const MENU_ITEMS_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_PROPTYPE); From 5f8fe061df6fb85be473dcca85baedf20791fbd0 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 16:54:04 -0500 Subject: [PATCH 023/286] common location for these functions --- src/Menu/MenuBar.jsx | 43 ++++----------------------------- src/Menu/ParentMenuItem.jsx | 43 ++++----------------------------- src/Menu/utils.js | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 78 deletions(-) create mode 100644 src/Menu/utils.js diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 5549e758..708d6687 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -7,6 +7,7 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; /* * Some notes on props: @@ -28,45 +29,9 @@ class MenuBar extends React.Component { static defaultProps = { orientation: 'horizontal', - renderItem: function renderItem(menuItem, index, array, menuBarProps) { - const { renderMenuItem, renderParentMenuItem } = menuBarProps; - const { type } = menuItem; - let node; - - if(type === 'menuitem') - node = renderMenuItem(menuItem, index, array, menuBarProps); - else if(type === 'parentmenuitem') - node = renderParentMenuItem(menuItem, index, array, menuBarProps); - else - node = renderMenuItem(menuItem, index, array, menuBarProps); - - return node; - }, - renderMenuItem: function renderMenuItem(menuItem, index) { - const { node, props = {} } = menuItem; - const { isDisabled } = props; - - return ( - - { node } - - ); - }, - renderParentMenuItem: function renderParentMenuItem(menuItem, index) { - const { node, menuItems, props = {} } = menuItem; - const { isDisabled, isEnabled } = props; - - return ( - - { node } - - ); - }, + renderItem, + renderMenuItem, + renderParentMenuItem, }; constructor(props) { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index c5eab542..e6135846 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -6,6 +6,7 @@ import MenuItem from 'src/Menu/MenuItem'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; function ParentMenuItem(props) { const { children, menuItems, isExpanded, isDisabled, renderItem } = props; @@ -45,45 +46,9 @@ ParentMenuItem.propTypes = { ParentMenuItem.defaultProps = { isExpanded: false, isDisabled: false, - renderItem: function renderItem(menuItem, index, array, parentItemProps) { - const { renderMenuItem, renderParentMenuItem } = parentItemProps; - const { type } = menuItem; - let node; - - if(type === 'menuitem') - node = renderMenuItem(menuItem, index, array, parentItemProps); - else if(type === 'parentmenuitem') - node = renderParentMenuItem(menuItem, index, array, parentItemProps); - else - node = renderMenuItem(menuItem, index, array, parentItemProps); - - return node; - }, - renderMenuItem: function renderMenuItem(menuItem, index) { - const { node, props = {} } = menuItem; - const { isDisabled } = props; - - return ( - - { node } - - ); - }, - renderParentMenuItem: function renderParentMenuItem(menuItem, index) { - const { node, menuItems, props = {} } = menuItem; - const { isDisabled, isEnabled } = props; - - return ( - - { node } - - ); - }, + renderItem, + renderMenuItem, + renderParentMenuItem, }; export default ParentMenuItem; diff --git a/src/Menu/utils.js b/src/Menu/utils.js new file mode 100644 index 00000000..68672859 --- /dev/null +++ b/src/Menu/utils.js @@ -0,0 +1,47 @@ +import React from 'react'; + +//Components and Styles +import MenuItem from 'src/Menu/MenuItem'; +import ParentMenuItem from 'src/Menu/ParentMenuItem'; + +export function renderItem(menuItem, index, array, menuBarProps) { + const { renderMenuItem, renderParentMenuItem } = menuBarProps; + const { type } = menuItem; + let node; + + if(type === 'menuitem') + node = renderMenuItem(menuItem, index, array, menuBarProps); + else if(type === 'parentmenuitem') + node = renderParentMenuItem(menuItem, index, array, menuBarProps); + else + node = renderMenuItem(menuItem, index, array, menuBarProps); + + return node; +} + +export function renderMenuItem(menuItem, index) { + const { node, props = {} } = menuItem; + const { isDisabled } = props; + + return ( + + { node } + + ); +} + +export function renderParentMenuItem(menuItem, index) { + const { node, menuItems, props = {} } = menuItem; + const { isDisabled, isEnabled } = props; + + return ( + + { node } + + ); +} From 2769a9fd90faee228d133f908d759323d643f875 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 17:11:34 -0500 Subject: [PATCH 024/286] ParentMenuItem uses Menu component now --- src/Menu/ParentMenuItem.jsx | 9 ++++++--- src/Menu/utils.js | 13 +++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index e6135846..6bb7c989 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -3,13 +3,14 @@ import PropTypes from 'prop-types'; //Components and Styles import MenuItem from 'src/Menu/MenuItem'; +import Menu from 'src/Menu/Menu'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; function ParentMenuItem(props) { - const { children, menuItems, isExpanded, isDisabled, renderItem } = props; + const { children, menuItems, isExpanded, isDisabled, orientation, renderItem } = props; const menuItemNodes = menuItems.map((mi, index, array) => { return renderItem(mi, index, array, props); }); @@ -26,9 +27,9 @@ function ParentMenuItem(props) { > { children } -
                              + { menuItemNodes } -
                            + ); } @@ -38,6 +39,7 @@ ParentMenuItem.propTypes = { menuItems: MENU_ITEMS_PROPTYPE.isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -46,6 +48,7 @@ ParentMenuItem.propTypes = { ParentMenuItem.defaultProps = { isExpanded: false, isDisabled: false, + orientation: 'horizontal', renderItem, renderMenuItem, renderParentMenuItem, diff --git a/src/Menu/utils.js b/src/Menu/utils.js index 68672859..a1f59e56 100644 --- a/src/Menu/utils.js +++ b/src/Menu/utils.js @@ -4,17 +4,17 @@ import React from 'react'; import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; -export function renderItem(menuItem, index, array, menuBarProps) { - const { renderMenuItem, renderParentMenuItem } = menuBarProps; +export function renderItem(menuItem, index, array, parentMenuProps) { + const { renderMenuItem, renderParentMenuItem } = parentMenuProps; const { type } = menuItem; let node; if(type === 'menuitem') - node = renderMenuItem(menuItem, index, array, menuBarProps); + node = renderMenuItem(menuItem, index, array, parentMenuProps); else if(type === 'parentmenuitem') - node = renderParentMenuItem(menuItem, index, array, menuBarProps); + node = renderParentMenuItem(menuItem, index, array, parentMenuProps); else - node = renderMenuItem(menuItem, index, array, menuBarProps); + node = renderMenuItem(menuItem, index, array, parentMenuProps); return node; } @@ -32,7 +32,7 @@ export function renderMenuItem(menuItem, index) { export function renderParentMenuItem(menuItem, index) { const { node, menuItems, props = {} } = menuItem; - const { isDisabled, isEnabled } = props; + const { isDisabled, isEnabled, orientation } = props; return ( { node } From a2347210234a9d9f5ae6e0e5811517b5eac6e5a6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 17:13:05 -0500 Subject: [PATCH 025/286] fix linter complaints --- src/Menu/MenuBar.jsx | 4 ---- src/Menu/ParentMenuItem.jsx | 1 - src/Menu/{utils.js => utils.jsx} | 0 3 files changed, 5 deletions(-) rename src/Menu/{utils.js => utils.jsx} (100%) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 708d6687..08d631c2 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -1,10 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -//Components and Styles -import MenuItem from 'src/Menu/MenuItem'; -import ParentMenuItem from 'src/Menu/ParentMenuItem'; - //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 6bb7c989..4e90526d 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; //Components and Styles -import MenuItem from 'src/Menu/MenuItem'; import Menu from 'src/Menu/Menu'; //Misc. diff --git a/src/Menu/utils.js b/src/Menu/utils.jsx similarity index 100% rename from src/Menu/utils.js rename to src/Menu/utils.jsx From f8a3cf9c18850d93690759cfd6cb8ad7a4be7577 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 18:51:30 -0500 Subject: [PATCH 026/286] begin recursively creating refs --- src/Menu/MenuBar.jsx | 22 ++++++++++++++++++++++ src/Menu/MenuItem.jsx | 1 + 2 files changed, 23 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 08d631c2..57270eed 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -32,6 +32,10 @@ class MenuBar extends React.Component { constructor(props) { super(props); + + const { menuItems } = props; + + this.menuItemRefs = this.createRefs(menuItems); } //---- Events ---- @@ -43,6 +47,8 @@ class MenuBar extends React.Component { return renderItem(menuItem, index, array, this.props); }); + console.log(this.menuItemRefs); + return (
                              ); } + + //---- Misc. ---- + createRefs = (menuItems) => { + const refs = []; + + menuItems.forEach((item, i) => { + const { type, menuItems: subMenuItems } = item; + + if(type === 'parentmenuitem') + refs[i] = this.createRefs(subMenuItems); + else + refs[i] = React.createRef(); + }); + + return refs; + }; } export default MenuBar; diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 24e151a2..60ec909f 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +//TODO: navigation-specific menu item? function MenuItem(props) { const { children, isDisabled } = props; From 92b583e4e55f0461f166d7d651de4fddbbe382c5 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 19:13:29 -0500 Subject: [PATCH 027/286] begin assigning refs --- src/Menu/MenuBar.jsx | 12 ++++++++---- src/Menu/MenuItem.jsx | 6 +++--- src/Menu/ParentMenuItem.jsx | 12 ++++++++---- src/Menu/utils.jsx | 17 ++++++++++------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 57270eed..d1d26801 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -25,7 +25,7 @@ class MenuBar extends React.Component { static defaultProps = { orientation: 'horizontal', - renderItem, + renderItem: renderItem, renderMenuItem, renderParentMenuItem, }; @@ -44,7 +44,7 @@ class MenuBar extends React.Component { render() { const { orientation, menuItems, label, labelId, renderItem } = this.props; const renderedMenuItems = menuItems.map((menuItem, index, array) => { - return renderItem(menuItem, index, array, this.props); + return renderItem(menuItem, index, array, this.props, this.menuItemRefs); }); console.log(this.menuItemRefs); @@ -68,8 +68,12 @@ class MenuBar extends React.Component { menuItems.forEach((item, i) => { const { type, menuItems: subMenuItems } = item; - if(type === 'parentmenuitem') - refs[i] = this.createRefs(subMenuItems); + if(type === 'parentmenuitem') { + refs[i] = { + ref: React.createRef(), + childRefs: this.createRefs(subMenuItems), + }; + } else refs[i] = React.createRef(); }); diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 60ec909f..29758bfd 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -2,15 +2,15 @@ import React from 'react'; import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? -function MenuItem(props) { +const MenuItem = React.forwardRef(function MenuItem(props, ref) { const { children, isDisabled } = props; return ( -
                            • +
                            • { children }
                            • ); -} +}); MenuItem.propTypes = { children: PropTypes.node.isRequired, diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 4e90526d..dcaf8b01 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -8,10 +8,10 @@ import Menu from 'src/Menu/Menu'; import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; -function ParentMenuItem(props) { - const { children, menuItems, isExpanded, isDisabled, orientation, renderItem } = props; +const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { + const { children, menuItems, refs, isExpanded, isDisabled, orientation, renderItem } = props; const menuItemNodes = menuItems.map((mi, index, array) => { - return renderItem(mi, index, array, props); + return renderItem(mi, index, array, props, refs); }); return ( @@ -23,6 +23,7 @@ function ParentMenuItem(props) { aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex="0" + ref={ ref } > { children } @@ -31,11 +32,14 @@ function ParentMenuItem(props) { ); -} +}); ParentMenuItem.propTypes = { children: PropTypes.node.isRequired, menuItems: MENU_ITEMS_PROPTYPE.isRequired, + refs: PropTypes.arrayOf(PropTypes.shape({ + current: PropTypes.object, + })).isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index a1f59e56..d62ba448 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -4,35 +4,36 @@ import React from 'react'; import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; -export function renderItem(menuItem, index, array, parentMenuProps) { +export function renderItem(menuItem, index, array, parentMenuProps, refs) { const { renderMenuItem, renderParentMenuItem } = parentMenuProps; const { type } = menuItem; let node; if(type === 'menuitem') - node = renderMenuItem(menuItem, index, array, parentMenuProps); + node = renderMenuItem(menuItem, index, array, parentMenuProps, refs); else if(type === 'parentmenuitem') - node = renderParentMenuItem(menuItem, index, array, parentMenuProps); + node = renderParentMenuItem(menuItem, index, array, parentMenuProps, refs); else - node = renderMenuItem(menuItem, index, array, parentMenuProps); + node = renderMenuItem(menuItem, index, array, parentMenuProps, refs); return node; } -export function renderMenuItem(menuItem, index) { +export function renderMenuItem(menuItem, index, array, parentMenuProps, refs) { const { node, props = {} } = menuItem; const { isDisabled } = props; return ( - + { node } ); } -export function renderParentMenuItem(menuItem, index) { +export function renderParentMenuItem(menuItem, index, array, parentMenuProps, refs) { const { node, menuItems, props = {} } = menuItem; const { isDisabled, isEnabled, orientation } = props; + const { ref, childRefs } = refs[index]; return ( { node } From 84736e0bb1790e6db7dd2f21e2ffcc50adb1c742 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 19:58:15 -0500 Subject: [PATCH 028/286] add some comments and rename variables to get rid of "menu-" prefix --- src/App.jsx | 10 +++--- src/Menu/MenuBar.jsx | 24 +++++++------- src/Menu/ParentMenuItem.jsx | 10 +++--- src/Menu/utils.jsx | 62 +++++++++++++++++++++++++++++-------- src/utils/propTypes.js | 2 +- 5 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index e3eee2a9..fe273b31 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -39,7 +39,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Parent Menuitem 1', - menuItems: [ + items: [ { type: 'menuitem', node: 'Hello world!', @@ -57,7 +57,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Parent Menuitem 2', - menuItems: [ + items: [ { type: 'menuitem', node: 'Hello world!', @@ -79,7 +79,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Parent Menuitem 3', - menuItems: [ + items: [ { type: 'menuitem', node: 'Hello world!', @@ -91,7 +91,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Nested Parent Menuitem', - menuItems: [ + items: [ { type: 'menuitem', node: 'Hello world!', @@ -115,7 +115,7 @@ function App() { Menu Button - + ); } diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d1d26801..8827052b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -15,7 +15,7 @@ import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils class MenuBar extends React.Component { static propTypes = { orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - menuItems: MENU_ITEMS_PROPTYPE.isRequired, + items: MENU_ITEMS_PROPTYPE.isRequired, label: PropTypes.string, //eslint-disable-line react/require-default-props labelId: PropTypes.string, //eslint-disable-line react/require-default-props renderItem: PropTypes.func, @@ -33,21 +33,21 @@ class MenuBar extends React.Component { constructor(props) { super(props); - const { menuItems } = props; + const { items } = props; - this.menuItemRefs = this.createRefs(menuItems); + this.itemRefs = this.createRefs(items); } //---- Events ---- //---- Rendering ---- render() { - const { orientation, menuItems, label, labelId, renderItem } = this.props; - const renderedMenuItems = menuItems.map((menuItem, index, array) => { - return renderItem(menuItem, index, array, this.props, this.menuItemRefs); + const { orientation, items, label, labelId, renderItem } = this.props; + const itemNodes = items.map((item, index, _items) => { + return renderItem(item, index, _items, this.props, this.itemRefs[index]); }); - console.log(this.menuItemRefs); + console.log(this.itemRefs); return (
                                - { renderedMenuItems } + { itemNodes }
                              ); } //---- Misc. ---- - createRefs = (menuItems) => { + createRefs = (items) => { const refs = []; - menuItems.forEach((item, i) => { - const { type, menuItems: subMenuItems } = item; + items.forEach((item, i) => { + const { type, items: subItems } = item; if(type === 'parentmenuitem') { refs[i] = { ref: React.createRef(), - childRefs: this.createRefs(subMenuItems), + childRefs: this.createRefs(subItems), }; } else diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index dcaf8b01..02863d2c 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -9,9 +9,9 @@ import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { - const { children, menuItems, refs, isExpanded, isDisabled, orientation, renderItem } = props; - const menuItemNodes = menuItems.map((mi, index, array) => { - return renderItem(mi, index, array, props, refs); + const { children, items, refs, isExpanded, isDisabled, orientation, renderItem } = props; + const itemNodes = items.map((item, index, _items) => { + return renderItem(item, index, _items, props, refs[index]); }); return ( @@ -28,7 +28,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { { children } - { menuItemNodes } + { itemNodes } ); @@ -36,7 +36,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { ParentMenuItem.propTypes = { children: PropTypes.node.isRequired, - menuItems: MENU_ITEMS_PROPTYPE.isRequired, + items: MENU_ITEMS_PROPTYPE.isRequired, refs: PropTypes.arrayOf(PropTypes.shape({ current: PropTypes.object, })).isRequired, diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index d62ba448..fda1c9af 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -4,41 +4,77 @@ import React from 'react'; import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; -export function renderItem(menuItem, index, array, parentMenuProps, refs) { - const { renderMenuItem, renderParentMenuItem } = parentMenuProps; - const { type } = menuItem; +/** + * Renders an item in a (sub-)menu. + * + * Note that this function only receives data for the current level of the + * overall menu, i.e. if this function is being run of a sub-menu, then + * arguments like parentMenuProps can only see data for this sub-menu and below. + * + * @param {object} item Descriptor for the item + * @param {number} index Location of the item within the current (sub-)menu + * @param {Array} items Array of descriptors representing the items in the current (sub-)menu + * @param {object} menuProps Props for the current (sub-)menu + * @param {Array} refs Refs for the item and any child items if it's a parent menuitem + */ +export function renderItem(item, index, items, menuProps, refs) { + const { renderMenuItem, renderParentMenuItem } = menuProps; + const { type } = item; let node; + console.log(menuProps, refs); + if(type === 'menuitem') - node = renderMenuItem(menuItem, index, array, parentMenuProps, refs); + node = renderMenuItem(item, index, items, menuProps, refs); else if(type === 'parentmenuitem') - node = renderParentMenuItem(menuItem, index, array, parentMenuProps, refs); - else - node = renderMenuItem(menuItem, index, array, parentMenuProps, refs); + node = renderParentMenuItem(item, index, items, menuProps, refs); return node; } -export function renderMenuItem(menuItem, index, array, parentMenuProps, refs) { +/** + * Renders a menuitem. + * + * @param {object} item Descriptor for the menuitem + * @param {number} index Location of the menuitem within the current (sub-)menu + * @param {Array} items Array of descriptors representing the items in the current (sub-)menu + * @param {object} menuProps Props for the current (sub-)menu + * @param {object} ref Ref for the menuitem + */ +export function renderMenuItem(menuItem, index, menuItems, menuProps, ref) { const { node, props = {} } = menuItem; const { isDisabled } = props; return ( - + { node } ); } -export function renderParentMenuItem(menuItem, index, array, parentMenuProps, refs) { - const { node, menuItems, props = {} } = menuItem; +/** + * Renders a parent menuitem. + * + * Note that a "parent menuitem" is a menuitem that acts as a parent + * for a sub-menu. The term "parent menuitem" is NOT being used to + * communicate "parent of the current item", though that parent would + * be a "parent menuitem". + * + * @param {object} item Descriptor for the parent menuitem + * @param {number} index Location of the parent menuitem within the current (sub-)menu + * @param {Array} items Array of descriptors representing the items in the current (sub-)menu + * @param {object} menuProps Props for the current (sub-)menu + * @param {object} refs Object containing the refs for the parent menuitem and its child items + */ +export function renderParentMenuItem(item, index, items, menuProps, refs) { + const { node, items: childItems, props = {} } = item; const { isDisabled, isEnabled, orientation } = props; - const { ref, childRefs } = refs[index]; + const { ref, childRefs } = refs; return ( Date: Mon, 27 Dec 2021 20:00:44 -0500 Subject: [PATCH 029/286] use shorthand --- src/Menu/MenuBar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 8827052b..bffee6da 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -25,7 +25,7 @@ class MenuBar extends React.Component { static defaultProps = { orientation: 'horizontal', - renderItem: renderItem, + renderItem, renderMenuItem, renderParentMenuItem, }; From 8f1255b55893751c485a8e272fb9f90f1688c426 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 27 Dec 2021 20:02:26 -0500 Subject: [PATCH 030/286] get rid of console log --- src/Menu/utils.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index fda1c9af..3f667cba 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -22,8 +22,6 @@ export function renderItem(item, index, items, menuProps, refs) { const { type } = item; let node; - console.log(menuProps, refs); - if(type === 'menuitem') node = renderMenuItem(item, index, items, menuProps, refs); else if(type === 'parentmenuitem') From dace89b6696dcee209a48bbd078505b217c28621 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 13:46:31 -0500 Subject: [PATCH 031/286] give menu items an isFocusable prop that defaults to false --- src/Menu/MenuItem.jsx | 11 +++++++++-- src/Menu/ParentMenuItem.jsx | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 29758bfd..7ef98c05 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -3,10 +3,15 @@ import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, isDisabled } = props; + const { children, isDisabled, isFocusable } = props; return ( -
                            • +
                            • { children }
                            • ); @@ -15,10 +20,12 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { MenuItem.propTypes = { children: PropTypes.node.isRequired, isDisabled: PropTypes.bool, + isFocusable: PropTypes.bool, }; MenuItem.defaultProps = { isDisabled: false, + isFocusable: false, }; export default MenuItem; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 02863d2c..348c7304 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -9,7 +9,7 @@ import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { - const { children, items, refs, isExpanded, isDisabled, orientation, renderItem } = props; + const { children, items, refs, isExpanded, isDisabled, isFocusable, orientation, renderItem } = props; const itemNodes = items.map((item, index, _items) => { return renderItem(item, index, _items, props, refs[index]); }); @@ -22,7 +22,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { aria-haspopup="menu" aria-expanded={ isExpanded } aria-disabled={ isDisabled } - tabIndex="0" + tabIndex={ isFocusable ? '0' : '-1' } ref={ ref } > { children } @@ -42,6 +42,7 @@ ParentMenuItem.propTypes = { })).isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, + isFocusable: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -51,6 +52,7 @@ ParentMenuItem.propTypes = { ParentMenuItem.defaultProps = { isExpanded: false, isDisabled: false, + isFocusable: false, orientation: 'horizontal', renderItem, renderMenuItem, From f846e331948ed3826fd7cbc0404d08f2b4edc30f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 13:52:05 -0500 Subject: [PATCH 032/286] pass down isFocusable in the render functions --- src/Menu/utils.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 3f667cba..a378e5b3 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -41,10 +41,15 @@ export function renderItem(item, index, items, menuProps, refs) { */ export function renderMenuItem(menuItem, index, menuItems, menuProps, ref) { const { node, props = {} } = menuItem; - const { isDisabled } = props; + const { isDisabled, isFocusable } = props; return ( - + { node } ); @@ -66,7 +71,7 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps, ref) { */ export function renderParentMenuItem(item, index, items, menuProps, refs) { const { node, items: childItems, props = {} } = item; - const { isDisabled, isEnabled, orientation } = props; + const { isDisabled, isEnabled, isFocusable, orientation } = props; const { ref, childRefs } = refs; return ( @@ -75,6 +80,7 @@ export function renderParentMenuItem(item, index, items, menuProps, refs) { items={ childItems } isDisabled={ isDisabled } isEnabled={ isEnabled } + isFocusable={ isFocusable } orientation={ orientation } ref={ ref } refs={ childRefs } From 063e9661487efabfa686a08396cb859b9f3e04b2 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 14:14:18 -0500 Subject: [PATCH 033/286] some proptype tweaks --- src/Menu/MenuBar.jsx | 2 +- src/utils/propTypes.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index bffee6da..f75b16b3 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -47,7 +47,7 @@ class MenuBar extends React.Component { return renderItem(item, index, _items, this.props, this.itemRefs[index]); }); - console.log(this.itemRefs); + console.log(this.props, this.itemRefs); return (
                                Date: Tue, 28 Dec 2021 14:15:29 -0500 Subject: [PATCH 034/286] fix comment grammar --- src/utils/propTypes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index 92ec8cdb..27eb04ca 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -8,8 +8,8 @@ import PropTypes from 'prop-types'; * * This also circumvents an issue with eslint-plugin-react * as the rule react/require-default-props will complain - * if it encounters a custom prop validator. - * normally cannot mark them as required. + * if it encounters a custom prop validator as they + * normally cannot be marked as required. * * See: * https://github.com/yannickcr/eslint-plugin-react/issues/1020 From 0b62e6c050a760bdd4eb846bb2083cc0b27a297d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 14:36:10 -0500 Subject: [PATCH 035/286] not actually a prop being used --- src/Menu/utils.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index a378e5b3..4bc64df1 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -71,7 +71,7 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps, ref) { */ export function renderParentMenuItem(item, index, items, menuProps, refs) { const { node, items: childItems, props = {} } = item; - const { isDisabled, isEnabled, isFocusable, orientation } = props; + const { isDisabled, isFocusable, orientation } = props; const { ref, childRefs } = refs; return ( @@ -79,7 +79,6 @@ export function renderParentMenuItem(item, index, items, menuProps, refs) { key={ index } items={ childItems } isDisabled={ isDisabled } - isEnabled={ isEnabled } isFocusable={ isFocusable } orientation={ orientation } ref={ ref } From 2a4274eabba325d13076fcd18940cf9b9fe67ac7 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 16:20:29 -0500 Subject: [PATCH 036/286] give menu items an ID prop --- src/Menu/MenuItem.jsx | 5 ++++- src/Menu/ParentMenuItem.jsx | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 7ef98c05..a69d113e 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, isDisabled, isFocusable } = props; + const { children, isDisabled, isFocusable, id } = props; return (
                              • { children }
                              • @@ -21,11 +22,13 @@ MenuItem.propTypes = { children: PropTypes.node.isRequired, isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, + id: PropTypes.string, }; MenuItem.defaultProps = { isDisabled: false, isFocusable: false, + id: undefined, }; export default MenuItem; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 348c7304..b1d8343f 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -9,7 +9,10 @@ import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { - const { children, items, refs, isExpanded, isDisabled, isFocusable, orientation, renderItem } = props; + const { + children, items, refs, isExpanded, isDisabled, isFocusable, + orientation, renderItem, id + } = props; const itemNodes = items.map((item, index, _items) => { return renderItem(item, index, _items, props, refs[index]); }); @@ -24,6 +27,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { aria-disabled={ isDisabled } tabIndex={ isFocusable ? '0' : '-1' } ref={ ref } + id={ id } > { children } @@ -44,6 +48,7 @@ ParentMenuItem.propTypes = { isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + id: PropTypes.string, renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -54,6 +59,7 @@ ParentMenuItem.defaultProps = { isDisabled: false, isFocusable: false, orientation: 'horizontal', + id: undefined, renderItem, renderMenuItem, renderParentMenuItem, From e840790fdd0e852dff66e828289061b6d96c82c8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 16:50:56 -0500 Subject: [PATCH 037/286] automatically generate IDs for menu items, use a more-generic container than "refs" --- .pnp.cjs | 9 +++++++++ package.json | 3 ++- src/Menu/MenuBar.jsx | 35 +++++++++++++++++++---------------- src/Menu/ParentMenuItem.jsx | 13 +++++++------ src/Menu/utils.jsx | 23 +++++++++++++---------- src/utils/propTypes.js | 18 +++++++++++++++++- yarn.lock | 10 ++++++++++ 7 files changed, 77 insertions(+), 34 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index 57daf54b..f6f197ba 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -50,6 +50,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["sass", "npm:1.44.0"], ["sass-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:12.3.0"], ["style-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:3.3.1"], + ["uuid", "npm:8.3.2"], ["webpack", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:5.64.4"], ["webpack-cli", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.9.1"], ["webpack-dev-server", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.6.0"] @@ -7076,6 +7077,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["sass", "npm:1.44.0"], ["sass-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:12.3.0"], ["style-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:3.3.1"], + ["uuid", "npm:8.3.2"], ["webpack", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:5.64.4"], ["webpack-cli", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.9.1"], ["webpack-dev-server", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.6.0"] @@ -8287,6 +8289,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["uuid", "npm:3.4.0"] ], "linkType": "HARD", + }], + ["npm:8.3.2", { + "packageLocation": "./.yarn/cache/uuid-npm-8.3.2-eca0baba53-5575a8a75c.zip/node_modules/uuid/", + "packageDependencies": [ + ["uuid", "npm:8.3.2"] + ], + "linkType": "HARD", }] ]], ["v8-compile-cache", [ diff --git a/package.json b/package.json index d489efc5..12f0df1c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "dependencies": { "prop-types": "^15.7.2", "react": "^17.0.2", - "react-dom": "^17.0.2" + "react-dom": "^17.0.2", + "uuid": "^8.3.2" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f75b16b3..4255d5c1 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { v4 as uuidv4 } from 'uuid'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -35,7 +36,7 @@ class MenuBar extends React.Component { const { items } = props; - this.itemRefs = this.createRefs(items); + this.itemMetaData = this.initializeMetaData(items); } //---- Events ---- @@ -44,10 +45,10 @@ class MenuBar extends React.Component { render() { const { orientation, items, label, labelId, renderItem } = this.props; const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, this.props, this.itemRefs[index]); + return renderItem(item, index, _items, this.props, this.itemMetaData[index]); }); - console.log(this.props, this.itemRefs); + console.log(this.props, this.itemMetaData); return (
                                  { - const refs = []; + initializeMetaData = (items, parentId) => { + const metaData = []; items.forEach((item, i) => { - const { type, items: subItems } = item; - - if(type === 'parentmenuitem') { - refs[i] = { - ref: React.createRef(), - childRefs: this.createRefs(subItems), - }; - } - else - refs[i] = React.createRef(); + const { type, items: subItems, props = {} } = item; + const { id } = props; + const _id = id ? id : uuidv4(); + + metaData[i] = { + id: _id, + ref: React.createRef(), + parentId, + }; + + if(type === 'parentmenuitem') + metaData[i].childMetaData = this.initializeMetaData(subItems, _id); }); - return refs; + return metaData; }; } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index b1d8343f..1997b27b 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -5,16 +5,19 @@ import PropTypes from 'prop-types'; import Menu from 'src/Menu/Menu'; //Misc. -import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { MENU_ITEMS_PROPTYPE, MENU_ITEMS_METADATA_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; +//TODO: this is straying further and further away +//from the idea of a "base" component - might be a good +//idea to separate the opinionated stuff I'm adding on? const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { - children, items, refs, isExpanded, isDisabled, isFocusable, + children, items, childMetaData, isExpanded, isDisabled, isFocusable, orientation, renderItem, id } = props; const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, props, refs[index]); + return renderItem(item, index, _items, props, childMetaData[index]); }); return ( @@ -41,9 +44,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { ParentMenuItem.propTypes = { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, - refs: PropTypes.arrayOf(PropTypes.shape({ - current: PropTypes.object, - })).isRequired, + childMetaData: MENU_ITEMS_METADATA_PROPTYPE.isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 4bc64df1..9143bc29 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -15,17 +15,17 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; * @param {number} index Location of the item within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu - * @param {Array} refs Refs for the item and any child items if it's a parent menuitem + * @param {object} metaData Extra data for the item (and any child items if it's a parent menuitem) */ -export function renderItem(item, index, items, menuProps, refs) { +export function renderItem(item, index, items, menuProps, metaData) { const { renderMenuItem, renderParentMenuItem } = menuProps; const { type } = item; let node; if(type === 'menuitem') - node = renderMenuItem(item, index, items, menuProps, refs); + node = renderMenuItem(item, index, items, menuProps, metaData); else if(type === 'parentmenuitem') - node = renderParentMenuItem(item, index, items, menuProps, refs); + node = renderParentMenuItem(item, index, items, menuProps, metaData); return node; } @@ -37,11 +37,12 @@ export function renderItem(item, index, items, menuProps, refs) { * @param {number} index Location of the menuitem within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu - * @param {object} ref Ref for the menuitem + * @param {object} metaData Extra data for the menuitem */ -export function renderMenuItem(menuItem, index, menuItems, menuProps, ref) { +export function renderMenuItem(menuItem, index, menuItems, menuProps, metaData) { const { node, props = {} } = menuItem; const { isDisabled, isFocusable } = props; + const { ref, id } = metaData; return ( { node } @@ -67,12 +69,12 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps, ref) { * @param {number} index Location of the parent menuitem within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu - * @param {object} refs Object containing the refs for the parent menuitem and its child items + * @param {object} metaData Extra data for the parent menuitem and its children */ -export function renderParentMenuItem(item, index, items, menuProps, refs) { +export function renderParentMenuItem(item, index, items, menuProps, metaData) { const { node, items: childItems, props = {} } = item; const { isDisabled, isFocusable, orientation } = props; - const { ref, childRefs } = refs; + const { ref, id, childMetaData } = metaData; return ( { node } diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index 27eb04ca..c7ad517d 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -51,10 +51,26 @@ validateHeaderLevelProp.isRequired = createCustomPropType(_validateHeaderLevelPr export { validateHeaderLevelProp }; export const MENU_ITEM_PROPTYPE = PropTypes.shape({ - type: PropTypes.oneOf([ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', 'separator' ]).isRequired, + type: PropTypes.oneOf([ + 'menuitem', + 'parentmenuitem', + 'menuitemcheckbox', + 'menuitemreadio', + 'separator' + ]).isRequired, node: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE, //Only required for "parentmenuitem" props: PropTypes.object, }); +export const MENU_ITEM_METADATA_PROPTYPE = PropTypes.shape({ + id: PropTypes.string, //TODO required? + parentId: PropTypes.string, + ref: PropTypes.shape({ + current: PropTypes.object, + }).isRequired, + childMetaData: MENU_ITEMS_METADATA_PROPTYPE, //Only required for "parentmenuitem" +}); + export const MENU_ITEMS_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_PROPTYPE); +export const MENU_ITEMS_METADATA_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_METADATA_PROPTYPE); diff --git a/yarn.lock b/yarn.lock index d9a3d4cb..bfe86181 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5265,6 +5265,7 @@ __metadata: sass: ^1.44.0 sass-loader: ^12.3.0 style-loader: ^3.3.1 + uuid: ^8.3.2 webpack: ^5.64.4 webpack-cli: ^4.9.1 webpack-dev-server: ^4.6.0 @@ -6337,6 +6338,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + "v8-compile-cache@npm:^2.0.3": version: 2.3.0 resolution: "v8-compile-cache@npm:2.3.0" From 53c4dc7297a27ad95de6a3f48b8c10d11d56991d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 17:43:32 -0500 Subject: [PATCH 038/286] try storing metadata as a map --- src/Menu/MenuBar.jsx | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 4255d5c1..a28d05e2 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -36,7 +36,11 @@ class MenuBar extends React.Component { const { items } = props; - this.itemMetaData = this.initializeMetaData(items); + this.itemMetaData = this.initializeMetaDataOld(items); + + this.state = { + metaData: this.initializeMetaData({}, items), + }; } //---- Events ---- @@ -48,7 +52,7 @@ class MenuBar extends React.Component { return renderItem(item, index, _items, this.props, this.itemMetaData[index]); }); - console.log(this.props, this.itemMetaData); + console.log(this.props, this.state, this.itemMetaData); return (
                                    { + initializeMetaData = (metaData, items, parentId) => { + items.forEach((item, i) => { + const { type, items: subItems, props = {} } = item; + const { id } = props; + const _id = id ? id : uuidv4(); + + metaData[_id] = { + id: _id, + ref: React.createRef(), + parentId, + }; + + if(type === 'parentmenuitem') + metaData = this.initializeMetaData(metaData, subItems, _id); + }); + + return metaData; + }; + + initializeMetaDataOld = (items, parentId) => { const metaData = []; items.forEach((item, i) => { @@ -78,7 +101,7 @@ class MenuBar extends React.Component { }; if(type === 'parentmenuitem') - metaData[i].childMetaData = this.initializeMetaData(subItems, _id); + metaData[i].childMetaData = this.initializeMetaDataOld(subItems, _id); }); return metaData; From 24671ab32170baa4d86b8e9f30fa3d0a0da3f4ae Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 21:34:34 -0500 Subject: [PATCH 039/286] rather than only hold metadata, maybe it should reference the actual items? --- src/Menu/MenuBar.jsx | 27 ++++++++++++++++++++------- src/Menu/ParentMenuItem.jsx | 2 +- src/utils/propTypes.js | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index a28d05e2..a5dd975a 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -39,7 +39,7 @@ class MenuBar extends React.Component { this.itemMetaData = this.initializeMetaDataOld(items); this.state = { - metaData: this.initializeMetaData({}, items), + itemMap: this.initializeItemMap({}, items), }; } @@ -67,23 +67,36 @@ class MenuBar extends React.Component { } //---- Misc. ---- - initializeMetaData = (metaData, items, parentId) => { - items.forEach((item, i) => { - const { type, items: subItems, props = {} } = item; + /** + * Recursively traverses the item tree to create a "flattened" + * hashtable that maps each item's ID to any relevant data. + * The items prop represents the structure of the menu but + * this hashtable allows for quick lookups of data. + * + * @param {object} itemMap + * @param {Array} items An array of items representing a single (sub-)menu + * @param {string} [parentId] The ID of the (sub-)menu's parent menuitem + */ + initializeItemMap = (itemMap, items, parentId) => { + items.forEach((item, index) => { + const { type, items: childItems, props = {} } = item; const { id } = props; const _id = id ? id : uuidv4(); - metaData[_id] = { + itemMap[_id] = { id: _id, ref: React.createRef(), parentId, + index, + items, + childItems, }; if(type === 'parentmenuitem') - metaData = this.initializeMetaData(metaData, subItems, _id); + itemMap = this.initializeItemMap(itemMap, childItems, _id); }); - return metaData; + return itemMap; }; initializeMetaDataOld = (items, parentId) => { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 1997b27b..8694923b 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -14,7 +14,7 @@ import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { children, items, childMetaData, isExpanded, isDisabled, isFocusable, - orientation, renderItem, id + orientation, renderItem, id, } = props; const itemNodes = items.map((item, index, _items) => { return renderItem(item, index, _items, props, childMetaData[index]); diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index c7ad517d..072d9e92 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -56,7 +56,7 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ 'parentmenuitem', 'menuitemcheckbox', 'menuitemreadio', - 'separator' + 'separator', ]).isRequired, node: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE, //Only required for "parentmenuitem" From 1408ee4aafe59caed0b5f4b5687aea0c62987a8d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 28 Dec 2021 22:05:36 -0500 Subject: [PATCH 040/286] flatten out "items" by getting rid of the props object --- src/Menu/MenuBar.jsx | 10 ++++++---- src/Menu/utils.jsx | 7 +++---- src/utils/propTypes.js | 7 +++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index a5dd975a..cb149d86 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -79,10 +79,13 @@ class MenuBar extends React.Component { */ initializeItemMap = (itemMap, items, parentId) => { items.forEach((item, index) => { - const { type, items: childItems, props = {} } = item; - const { id } = props; + const { type, items: childItems, id } = item; const _id = id ? id : uuidv4(); + //TODO: objects in itemMap cannot easily find their + //position in items, and objects in items cannot easily + //find their position in itemMap because we cannot modify + //the items prop to give them their ID itemMap[_id] = { id: _id, ref: React.createRef(), @@ -103,8 +106,7 @@ class MenuBar extends React.Component { const metaData = []; items.forEach((item, i) => { - const { type, items: subItems, props = {} } = item; - const { id } = props; + const { type, items: subItems, id } = item; const _id = id ? id : uuidv4(); metaData[i] = { diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 9143bc29..44d44758 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -40,8 +40,7 @@ export function renderItem(item, index, items, menuProps, metaData) { * @param {object} metaData Extra data for the menuitem */ export function renderMenuItem(menuItem, index, menuItems, menuProps, metaData) { - const { node, props = {} } = menuItem; - const { isDisabled, isFocusable } = props; + const { node, isDisabled, isFocusable } = menuItem; const { ref, id } = metaData; return ( @@ -72,14 +71,14 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps, metaData) * @param {object} metaData Extra data for the parent menuitem and its children */ export function renderParentMenuItem(item, index, items, menuProps, metaData) { - const { node, items: childItems, props = {} } = item; - const { isDisabled, isFocusable, orientation } = props; + const { node, items: childItems, isDisabled, isFocusable, orientation, isExpandable } = item; const { ref, id, childMetaData } = metaData; return ( Date: Wed, 29 Dec 2021 12:34:28 -0500 Subject: [PATCH 041/286] instead of storing just metadata, maybe make a full copy of items? --- src/Menu/MenuBar.jsx | 70 +++++++++++-------------------------- src/Menu/ParentMenuItem.jsx | 5 ++- src/Menu/utils.jsx | 20 ++++------- 3 files changed, 29 insertions(+), 66 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index cb149d86..f489025c 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -35,11 +35,12 @@ class MenuBar extends React.Component { super(props); const { items } = props; - - this.itemMetaData = this.initializeMetaDataOld(items); - + + //TODO: feels incredibly awkward, e.g.: + //- refs in state? + //- what if someone passes new props (e.g. change isDisabled for an item)? this.state = { - itemMap: this.initializeItemMap({}, items), + items: this.initializeItems(items), }; } @@ -47,12 +48,13 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { - const { orientation, items, label, labelId, renderItem } = this.props; + const { orientation, label, labelId, renderItem } = this.props; + const { items } = this.state; const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, this.props, this.itemMetaData[index]); + return renderItem(item, index, _items, this.props); }); - console.log(this.props, this.state, this.itemMetaData); + console.log(this.props, this.state); return (
                                      { - items.forEach((item, index) => { - const { type, items: childItems, id } = item; - const _id = id ? id : uuidv4(); - - //TODO: objects in itemMap cannot easily find their - //position in items, and objects in items cannot easily - //find their position in itemMap because we cannot modify - //the items prop to give them their ID - itemMap[_id] = { - id: _id, - ref: React.createRef(), - parentId, - index, - items, - childItems, - }; - - if(type === 'parentmenuitem') - itemMap = this.initializeItemMap(itemMap, childItems, _id); - }); - - return itemMap; - }; - - initializeMetaDataOld = (items, parentId) => { - const metaData = []; + initializeItems = (items, parentId) => { + const _items = []; items.forEach((item, i) => { - const { type, items: subItems, id } = item; + const { type, items: childItems, id } = item; const _id = id ? id : uuidv4(); - - metaData[i] = { + + //We can't modify the props being passed in here, + //so let's create a copy of items with some extra + //info attached. + _items.push(Object.assign({}, item, { id: _id, ref: React.createRef(), parentId, - }; + })); if(type === 'parentmenuitem') - metaData[i].childMetaData = this.initializeMetaDataOld(subItems, _id); + _items[i].items = this.initializeItems(childItems, _id); }); - return metaData; + return _items; }; } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8694923b..1cf79c73 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -13,11 +13,11 @@ import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils //idea to separate the opinionated stuff I'm adding on? const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { - children, items, childMetaData, isExpanded, isDisabled, isFocusable, + children, items, isExpanded, isDisabled, isFocusable, orientation, renderItem, id, } = props; const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, props, childMetaData[index]); + return renderItem(item, index, _items, props); }); return ( @@ -44,7 +44,6 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { ParentMenuItem.propTypes = { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, - childMetaData: MENU_ITEMS_METADATA_PROPTYPE.isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 44d44758..49d4a0ce 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -15,17 +15,16 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; * @param {number} index Location of the item within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu - * @param {object} metaData Extra data for the item (and any child items if it's a parent menuitem) */ -export function renderItem(item, index, items, menuProps, metaData) { +export function renderItem(item, index, items, menuProps) { const { renderMenuItem, renderParentMenuItem } = menuProps; const { type } = item; let node; if(type === 'menuitem') - node = renderMenuItem(item, index, items, menuProps, metaData); + node = renderMenuItem(item, index, items, menuProps); else if(type === 'parentmenuitem') - node = renderParentMenuItem(item, index, items, menuProps, metaData); + node = renderParentMenuItem(item, index, items, menuProps); return node; } @@ -37,11 +36,9 @@ export function renderItem(item, index, items, menuProps, metaData) { * @param {number} index Location of the menuitem within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu - * @param {object} metaData Extra data for the menuitem */ -export function renderMenuItem(menuItem, index, menuItems, menuProps, metaData) { - const { node, isDisabled, isFocusable } = menuItem; - const { ref, id } = metaData; +export function renderMenuItem(menuItem, index, menuItems, menuProps) { + const { node, isDisabled, isFocusable, ref, id } = menuItem; return ( { node } From 10cd7fe0c6aabfd1c4a594b7bc68d34d6f270195 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 13:33:44 -0500 Subject: [PATCH 042/286] rename "items" to "children", pass down a dummy keydown event handler --- src/App.jsx | 8 ++++---- src/Menu/MenuBar.jsx | 10 +++++++--- src/Menu/MenuItem.jsx | 4 +++- src/Menu/ParentMenuItem.jsx | 6 ++++-- src/Menu/utils.jsx | 19 ++++++++++++------- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index fe273b31..7b83f965 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -39,7 +39,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Parent Menuitem 1', - items: [ + children: [ { type: 'menuitem', node: 'Hello world!', @@ -57,7 +57,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Parent Menuitem 2', - items: [ + children: [ { type: 'menuitem', node: 'Hello world!', @@ -79,7 +79,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Parent Menuitem 3', - items: [ + children: [ { type: 'menuitem', node: 'Hello world!', @@ -91,7 +91,7 @@ const MENU_ITEMS = [ { type: 'parentmenuitem', node: 'Nested Parent Menuitem', - items: [ + children: [ { type: 'menuitem', node: 'Hello world!', diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f489025c..0cd22289 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -45,13 +45,17 @@ class MenuBar extends React.Component { } //---- Events ---- + onItemKeyDown = (event) => { + console.log(event.target.id); + console.log(event.target.getAttribute('role')); + }; //---- Rendering ---- render() { const { orientation, label, labelId, renderItem } = this.props; const { items } = this.state; const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, this.props); + return renderItem(item, index, _items, this.props, this.onItemKeyDown); }); console.log(this.props, this.state); @@ -73,7 +77,7 @@ class MenuBar extends React.Component { const _items = []; items.forEach((item, i) => { - const { type, items: childItems, id } = item; + const { type, children, id } = item; const _id = id ? id : uuidv4(); //We can't modify the props being passed in here, @@ -86,7 +90,7 @@ class MenuBar extends React.Component { })); if(type === 'parentmenuitem') - _items[i].items = this.initializeItems(childItems, _id); + _items[i].children = this.initializeItems(children, _id); }); return _items; diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index a69d113e..dbe2262d 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, isDisabled, isFocusable, id } = props; + const { children, isDisabled, isFocusable, id, onKeyDown, } = props; return (
                                    • { children }
                                    • @@ -20,6 +21,7 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { MenuItem.propTypes = { children: PropTypes.node.isRequired, + onKeyDown: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, id: PropTypes.string, diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 1cf79c73..f5c46d27 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -14,10 +14,10 @@ import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { children, items, isExpanded, isDisabled, isFocusable, - orientation, renderItem, id, + orientation, renderItem, id, onKeyDown, } = props; const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, props); + return renderItem(item, index, _items, props, onKeyDown); }); return ( @@ -31,6 +31,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { tabIndex={ isFocusable ? '0' : '-1' } ref={ ref } id={ id } + onKeyDown={ onKeyDown } > { children } @@ -44,6 +45,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { ParentMenuItem.propTypes = { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, + onKeyDown: PropTypes.func.isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 49d4a0ce..85924d2a 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -15,16 +15,17 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; * @param {number} index Location of the item within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu + * @param {function} onKeyDown */ -export function renderItem(item, index, items, menuProps) { +export function renderItem(item, index, items, menuProps, onKeyDown) { const { renderMenuItem, renderParentMenuItem } = menuProps; const { type } = item; let node; if(type === 'menuitem') - node = renderMenuItem(item, index, items, menuProps); + node = renderMenuItem(item, index, items, menuProps, onKeyDown); else if(type === 'parentmenuitem') - node = renderParentMenuItem(item, index, items, menuProps); + node = renderParentMenuItem(item, index, items, menuProps, onKeyDown); return node; } @@ -36,8 +37,9 @@ export function renderItem(item, index, items, menuProps) { * @param {number} index Location of the menuitem within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu + * @param {function} onKeyDown */ -export function renderMenuItem(menuItem, index, menuItems, menuProps) { +export function renderMenuItem(menuItem, index, menuItems, menuProps, onKeyDown) { const { node, isDisabled, isFocusable, ref, id } = menuItem; return ( @@ -47,6 +49,7 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps) { isFocusable={ isFocusable } ref={ ref } id={ id } + onKeyDown={ onKeyDown } > { node }
                                      @@ -65,20 +68,22 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps) { * @param {number} index Location of the parent menuitem within the current (sub-)menu * @param {Array} items Array of descriptors representing the items in the current (sub-)menu * @param {object} menuProps Props for the current (sub-)menu + * @param {function} onKeyDown */ -export function renderParentMenuItem(item, index, items, menuProps) { - const { node, items: childItems, isDisabled, isFocusable, orientation, isExpandable, ref, id } = item; +export function renderParentMenuItem(item, index, items, menuProps, onKeyDown) { + const { node, children, isDisabled, isFocusable, orientation, isExpandable, ref, id } = item; return ( { node } From d65d049def39c40ba3659fc4adca226edae31c93 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 13:34:49 -0500 Subject: [PATCH 043/286] the first item of the menubar is focusable --- src/Menu/MenuBar.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 0cd22289..04957468 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -86,6 +86,7 @@ class MenuBar extends React.Component { _items.push(Object.assign({}, item, { id: _id, ref: React.createRef(), + isFocusable: i === 0 && !parentId, parentId, })); From f90be277d73b86c074f6730c3706e9a6908f8632 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 13:36:05 -0500 Subject: [PATCH 044/286] get rid of linter ignore comment --- src/Menu/MenuBar.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 04957468..1aa177c2 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -17,8 +17,8 @@ class MenuBar extends React.Component { static propTypes = { orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), items: MENU_ITEMS_PROPTYPE.isRequired, - label: PropTypes.string, //eslint-disable-line react/require-default-props - labelId: PropTypes.string, //eslint-disable-line react/require-default-props + label: PropTypes.string, + labelId: PropTypes.string, renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -26,6 +26,8 @@ class MenuBar extends React.Component { static defaultProps = { orientation: 'horizontal', + label: undefined, + labelId: undefined, renderItem, renderMenuItem, renderParentMenuItem, From 9acddd49424795e678f4388979ccb2b52750728d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 14:19:14 -0500 Subject: [PATCH 045/286] "skeleton" for capturing key events --- src/Menu/MenuBar.jsx | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 1aa177c2..6599b4cb 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -48,8 +48,46 @@ class MenuBar extends React.Component { //---- Events ---- onItemKeyDown = (event) => { - console.log(event.target.id); - console.log(event.target.getAttribute('role')); + const { orientation } = this.props; + const { key, target, shiftKey } = event; + const id = target.id; + const role = target.getAttribute('role'); + const nextSibling = target.nextElementSibling; + const nextSiblingRole = nextSibling ? nextSibling.getAttribute('role') : undefined; + + //According to the WAI-ARIA Authoring Practices 1.1, + //the element with the role "menu" should be the + //sibling element immediately following its parent + //"menuitem". + console.log(key, shiftKey, id, role, nextSibling, nextSiblingRole); + + //TODO: Any key that corresponds to a printable character (Optional): + //Move focus to the next menu item in the current menu whose label begins + //with that printable character. + if(key === 'ArrowUp' || key === 'Up') { + } + else if(key === 'ArrowDown' || key === 'Down') { + } + else if(key === 'ArrowLeft' || key === 'Left') { + } + else if(key === 'ArrowRight' || key === 'Right') { + } + else if(key === 'Enter') { + } + else if(key === ' ' || key === 'Spacebar') { + } + else if(key === 'Home') { + } + else if(key === 'End') { + } + else if(key === 'Escape' || key === 'Esc') { + } + else if(key === 'Tab') { + if(shiftKey) { + } + else { + } + } }; //---- Rendering ---- From 0c4021cd5c679f4e7828d07f5befcd44eb03d4fd Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 15:11:09 -0500 Subject: [PATCH 046/286] rudimentary menubar navigation with arrow keys --- src/Accordion/index.jsx | 2 ++ src/Menu/MenuBar.jsx | 23 ++++++++++++++++++++++- src/Menu/MenuItem.jsx | 5 ++++- src/Menu/ParentMenuItem.jsx | 5 ++++- src/Menu/utils.jsx | 2 ++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/Accordion/index.jsx b/src/Accordion/index.jsx index ebdf0aaa..c51b48cf 100644 --- a/src/Accordion/index.jsx +++ b/src/Accordion/index.jsx @@ -45,6 +45,8 @@ class Accordion extends React.Component { }; onTriggerKeyDown = (event) => { + //TODO: should we be using a data-index here? feels like a potentially + //unnecessary prop being passed down to the menu items const { sections } = this.props; const { key } = event; const index = Number.parseInt(event.target.dataset.index, 10); diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 6599b4cb..0a033aee 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -49,36 +49,49 @@ class MenuBar extends React.Component { //---- Events ---- onItemKeyDown = (event) => { const { orientation } = this.props; + const { items } = this.state; const { key, target, shiftKey } = event; + const index = Number.parseInt(target.dataset.index, 10); + const item = items[index]; const id = target.id; const role = target.getAttribute('role'); const nextSibling = target.nextElementSibling; const nextSiblingRole = nextSibling ? nextSibling.getAttribute('role') : undefined; + let nextIndex = index; //According to the WAI-ARIA Authoring Practices 1.1, //the element with the role "menu" should be the //sibling element immediately following its parent //"menuitem". - console.log(key, shiftKey, id, role, nextSibling, nextSiblingRole); + console.log(key, shiftKey, id, role, nextSibling, nextSiblingRole, index, item); //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins //with that printable character. + //TODO: take into account orientation if(key === 'ArrowUp' || key === 'Up') { } else if(key === 'ArrowDown' || key === 'Down') { } else if(key === 'ArrowLeft' || key === 'Left') { + nextIndex = index === 0 ? items.length - 1 : index - 1; + event.preventDefault(); } else if(key === 'ArrowRight' || key === 'Right') { + nextIndex = index === items.length - 1 ? 0 : index + 1; + event.preventDefault(); } else if(key === 'Enter') { } else if(key === ' ' || key === 'Spacebar') { } else if(key === 'Home') { + nextIndex = 0; + event.preventDefault(); } else if(key === 'End') { + nextIndex = items.length - 1; + event.preventDefault(); } else if(key === 'Escape' || key === 'Esc') { } @@ -88,6 +101,14 @@ class MenuBar extends React.Component { else { } } + + this.setState(prevState => { + console.log(prevState.items, index, nextIndex); + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); }; //---- Rendering ---- diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index dbe2262d..27bbe192 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, isDisabled, isFocusable, id, onKeyDown, } = props; + const { children, isDisabled, isFocusable, id, onKeyDown, index } = props; return (
                                    • { children }
                                    • @@ -25,12 +26,14 @@ MenuItem.propTypes = { isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, id: PropTypes.string, + index: PropTypes.number, }; MenuItem.defaultProps = { isDisabled: false, isFocusable: false, id: undefined, + index: undefined, }; export default MenuItem; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index f5c46d27..4b5c6237 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -14,7 +14,7 @@ import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { children, items, isExpanded, isDisabled, isFocusable, - orientation, renderItem, id, onKeyDown, + orientation, renderItem, id, onKeyDown, index, } = props; const itemNodes = items.map((item, index, _items) => { return renderItem(item, index, _items, props, onKeyDown); @@ -32,6 +32,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { ref={ ref } id={ id } onKeyDown={ onKeyDown } + data-index={ index } > { children } @@ -51,6 +52,7 @@ ParentMenuItem.propTypes = { isFocusable: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), id: PropTypes.string, + index: PropTypes.number, renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -62,6 +64,7 @@ ParentMenuItem.defaultProps = { isFocusable: false, orientation: 'horizontal', id: undefined, + index: undefined, renderItem, renderMenuItem, renderParentMenuItem, diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 85924d2a..266ad516 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -50,6 +50,7 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps, onKeyDown) ref={ ref } id={ id } onKeyDown={ onKeyDown } + index={ index } > { node } @@ -84,6 +85,7 @@ export function renderParentMenuItem(item, index, items, menuProps, onKeyDown) { ref={ ref } id={ id } onKeyDown={ onKeyDown } + index={ index } > { node } From f0d1eea8a014cdafe966bc975719ad324b246726 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 15:29:11 -0500 Subject: [PATCH 047/286] use more destructuring --- src/Menu/MenuBar.jsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 0a033aee..f9944f0e 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -51,19 +51,18 @@ class MenuBar extends React.Component { const { orientation } = this.props; const { items } = this.state; const { key, target, shiftKey } = event; + const { id, nextElementSibling } = target; const index = Number.parseInt(target.dataset.index, 10); const item = items[index]; - const id = target.id; const role = target.getAttribute('role'); - const nextSibling = target.nextElementSibling; - const nextSiblingRole = nextSibling ? nextSibling.getAttribute('role') : undefined; + const isParentMenuitem = role === 'menuitem' && nextElementSibling && nextElementSibling.getAttribute('role') === 'menu'; let nextIndex = index; //According to the WAI-ARIA Authoring Practices 1.1, //the element with the role "menu" should be the //sibling element immediately following its parent //"menuitem". - console.log(key, shiftKey, id, role, nextSibling, nextSiblingRole, index, item); + console.log(key, shiftKey, id, index, item, isParentMenuitem); //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -103,7 +102,6 @@ class MenuBar extends React.Component { } this.setState(prevState => { - console.log(prevState.items, index, nextIndex); prevState.items[index].isFocusable = false; prevState.items[nextIndex].isFocusable = true; prevState.items[nextIndex].ref.current.focus(); From f4b5270260cb8a7921bc332c747ec6029d0c000d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 15:30:10 -0500 Subject: [PATCH 048/286] prevent default for all conditions --- src/Menu/MenuBar.jsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f9944f0e..d873c8be 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -69,32 +69,39 @@ class MenuBar extends React.Component { //with that printable character. //TODO: take into account orientation if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); } else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); } else if(key === 'ArrowLeft' || key === 'Left') { - nextIndex = index === 0 ? items.length - 1 : index - 1; event.preventDefault(); + nextIndex = index === 0 ? items.length - 1 : index - 1; } else if(key === 'ArrowRight' || key === 'Right') { - nextIndex = index === items.length - 1 ? 0 : index + 1; event.preventDefault(); + nextIndex = index === items.length - 1 ? 0 : index + 1; } else if(key === 'Enter') { + event.preventDefault(); } else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); } else if(key === 'Home') { - nextIndex = 0; event.preventDefault(); + nextIndex = 0; } else if(key === 'End') { - nextIndex = items.length - 1; event.preventDefault(); + nextIndex = items.length - 1; } else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); } else if(key === 'Tab') { + event.preventDefault(); + if(shiftKey) { } else { From dddede96ed076e2ca1716ed4c7214b2e79ca2d85 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 20:58:31 -0500 Subject: [PATCH 049/286] this should allow each node to know their position in the entire tree --- src/App.jsx | 42 ++++++++++++++++++++++++++++++++++++++++++ src/Menu/MenuBar.jsx | 13 +++++++------ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 7b83f965..7bd3b5e0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -66,6 +66,48 @@ const MENU_ITEMS = [ type: 'menuitem', node: 'Hello world!', }, + { + type: 'parentmenuitem', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'parentmenuitem', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + ], + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'parentmenuitem', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'menuitem', + node: 'Hello world!', + }, + { + type: 'menuitem', + node: 'Hello world!', + }, + ], + }, + ], + }, { type: 'menuitem', node: 'Hello world!', diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d873c8be..45c689c2 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -100,8 +100,6 @@ class MenuBar extends React.Component { event.preventDefault(); } else if(key === 'Tab') { - event.preventDefault(); - if(shiftKey) { } else { @@ -139,25 +137,28 @@ class MenuBar extends React.Component { } //---- Misc. ---- - initializeItems = (items, parentId) => { + initializeItems = (items, level = 0, position = []) => { const _items = []; items.forEach((item, i) => { const { type, children, id } = item; const _id = id ? id : uuidv4(); + position = [...position]; + position[level] = i; + //We can't modify the props being passed in here, //so let's create a copy of items with some extra //info attached. _items.push(Object.assign({}, item, { id: _id, ref: React.createRef(), - isFocusable: i === 0 && !parentId, - parentId, + isFocusable: i === 0 && level === 0, + position, })); if(type === 'parentmenuitem') - _items[i].children = this.initializeItems(children, _id); + _items[i].children = this.initializeItems(children, level + 1, position); }); return _items; From a8b89595f30d0fd908e8b2a66b02e593332cfb86 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 21:18:16 -0500 Subject: [PATCH 050/286] hide menus that are not expanded --- src/Menu/MenuBar.jsx | 39 ++++++++++++++++++++++++++++++++------- src/styles.scss | 4 ++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 45c689c2..34bd5859 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -76,11 +76,27 @@ class MenuBar extends React.Component { } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); + nextIndex = index === 0 ? items.length - 1 : index - 1; + + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); + nextIndex = index === items.length - 1 ? 0 : index + 1; + + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'Enter') { event.preventDefault(); @@ -90,11 +106,27 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); + nextIndex = 0; + + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'End') { event.preventDefault(); + nextIndex = items.length - 1; + + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); @@ -105,13 +137,6 @@ class MenuBar extends React.Component { else { } } - - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); - return prevState; - }); }; //---- Rendering ---- diff --git a/src/styles.scss b/src/styles.scss index 6e96ebaf..c0fcf853 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -2,6 +2,10 @@ display: none; } +[role="menuitem"][aria-expanded="false"] + [role="menu"] { + display: none; +} + //A technique for visually-hiding elements //but still keeping them readable to machines. //See: From 000190d271a2a7029250f5178878d4a7efb3c03e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 21:44:53 -0500 Subject: [PATCH 051/286] get rid of id and index in favor of position --- .pnp.cjs | 9 --------- package.json | 3 +-- src/Menu/MenuBar.jsx | 20 +++++++++++--------- src/Menu/MenuItem.jsx | 10 +++------- src/Menu/ParentMenuItem.jsx | 10 +++------- src/Menu/utils.jsx | 12 +++++------- yarn.lock | 10 ---------- 7 files changed, 23 insertions(+), 51 deletions(-) diff --git a/.pnp.cjs b/.pnp.cjs index f6f197ba..57daf54b 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -50,7 +50,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["sass", "npm:1.44.0"], ["sass-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:12.3.0"], ["style-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:3.3.1"], - ["uuid", "npm:8.3.2"], ["webpack", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:5.64.4"], ["webpack-cli", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.9.1"], ["webpack-dev-server", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.6.0"] @@ -7077,7 +7076,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["sass", "npm:1.44.0"], ["sass-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:12.3.0"], ["style-loader", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:3.3.1"], - ["uuid", "npm:8.3.2"], ["webpack", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:5.64.4"], ["webpack-cli", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.9.1"], ["webpack-dev-server", "virtual:f411e0364179af59a120321a5134f36948febc11ff7da7835e0733d90c2c13b08221b9721532556e99fcbb95d6e3b85e070504e6f6c90e5adfd392d8f996f9fd#npm:4.6.0"] @@ -8289,13 +8287,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["uuid", "npm:3.4.0"] ], "linkType": "HARD", - }], - ["npm:8.3.2", { - "packageLocation": "./.yarn/cache/uuid-npm-8.3.2-eca0baba53-5575a8a75c.zip/node_modules/uuid/", - "packageDependencies": [ - ["uuid", "npm:8.3.2"] - ], - "linkType": "HARD", }] ]], ["v8-compile-cache", [ diff --git a/package.json b/package.json index 12f0df1c..d489efc5 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "dependencies": { "prop-types": "^15.7.2", "react": "^17.0.2", - "react-dom": "^17.0.2", - "uuid": "^8.3.2" + "react-dom": "^17.0.2" }, "devDependencies": { "@babel/core": "^7.16.0", diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 34bd5859..1f4ba64b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { v4 as uuidv4 } from 'uuid'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -51,18 +50,22 @@ class MenuBar extends React.Component { const { orientation } = this.props; const { items } = this.state; const { key, target, shiftKey } = event; - const { id, nextElementSibling } = target; - const index = Number.parseInt(target.dataset.index, 10); - const item = items[index]; + const { nextElementSibling } = target; const role = target.getAttribute('role'); + const position = target.dataset.position.split(',').map(index => Number.parseInt(index, 10)); const isParentMenuitem = role === 'menuitem' && nextElementSibling && nextElementSibling.getAttribute('role') === 'menu'; + let item; + + console.log(position); + + /* let nextIndex = index; //According to the WAI-ARIA Authoring Practices 1.1, //the element with the role "menu" should be the //sibling element immediately following its parent //"menuitem". - console.log(key, shiftKey, id, index, item, isParentMenuitem); + console.log(key, shiftKey, position, item, isParentMenuitem); //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -137,6 +140,7 @@ class MenuBar extends React.Component { else { } } + */ }; //---- Rendering ---- @@ -166,17 +170,15 @@ class MenuBar extends React.Component { const _items = []; items.forEach((item, i) => { - const { type, children, id } = item; - const _id = id ? id : uuidv4(); + const { type, children } = item; - position = [...position]; + position = position.slice(0); position[level] = i; //We can't modify the props being passed in here, //so let's create a copy of items with some extra //info attached. _items.push(Object.assign({}, item, { - id: _id, ref: React.createRef(), isFocusable: i === 0 && level === 0, position, diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 27bbe192..a5357341 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, isDisabled, isFocusable, id, onKeyDown, index } = props; + const { children, isDisabled, isFocusable, onKeyDown, position } = props; return (
                                    • { children }
                                    • @@ -23,17 +22,14 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { MenuItem.propTypes = { children: PropTypes.node.isRequired, onKeyDown: PropTypes.func.isRequired, + position: PropTypes.arrayOf(PropTypes.number).isRequired, isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, - id: PropTypes.string, - index: PropTypes.number, }; MenuItem.defaultProps = { isDisabled: false, isFocusable: false, - id: undefined, - index: undefined, }; export default MenuItem; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 4b5c6237..240ea262 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -14,7 +14,7 @@ import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { children, items, isExpanded, isDisabled, isFocusable, - orientation, renderItem, id, onKeyDown, index, + orientation, renderItem, onKeyDown, position } = props; const itemNodes = items.map((item, index, _items) => { return renderItem(item, index, _items, props, onKeyDown); @@ -30,9 +30,8 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { aria-disabled={ isDisabled } tabIndex={ isFocusable ? '0' : '-1' } ref={ ref } - id={ id } onKeyDown={ onKeyDown } - data-index={ index } + data-position={ position.toString() } > { children } @@ -47,12 +46,11 @@ ParentMenuItem.propTypes = { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, onKeyDown: PropTypes.func.isRequired, + position: PropTypes.arrayOf(PropTypes.number).isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isFocusable: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - id: PropTypes.string, - index: PropTypes.number, renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -63,8 +61,6 @@ ParentMenuItem.defaultProps = { isDisabled: false, isFocusable: false, orientation: 'horizontal', - id: undefined, - index: undefined, renderItem, renderMenuItem, renderParentMenuItem, diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 266ad516..e49893be 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -40,7 +40,7 @@ export function renderItem(item, index, items, menuProps, onKeyDown) { * @param {function} onKeyDown */ export function renderMenuItem(menuItem, index, menuItems, menuProps, onKeyDown) { - const { node, isDisabled, isFocusable, ref, id } = menuItem; + const { node, isDisabled, isFocusable, ref, position } = menuItem; return ( { node } @@ -72,20 +71,19 @@ export function renderMenuItem(menuItem, index, menuItems, menuProps, onKeyDown) * @param {function} onKeyDown */ export function renderParentMenuItem(item, index, items, menuProps, onKeyDown) { - const { node, children, isDisabled, isFocusable, orientation, isExpandable, ref, id } = item; + const { node, children, isDisabled, isFocusable, orientation, isExpanded, ref, position } = item; return ( { node } diff --git a/yarn.lock b/yarn.lock index bfe86181..d9a3d4cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5265,7 +5265,6 @@ __metadata: sass: ^1.44.0 sass-loader: ^12.3.0 style-loader: ^3.3.1 - uuid: ^8.3.2 webpack: ^5.64.4 webpack-cli: ^4.9.1 webpack-dev-server: ^4.6.0 @@ -6338,15 +6337,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^8.3.2": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df - languageName: node - linkType: hard - "v8-compile-cache@npm:^2.0.3": version: 2.3.0 resolution: "v8-compile-cache@npm:2.3.0" From ece4589551ca6e330be9dbe4a90031eebe012cc3 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 29 Dec 2021 22:17:26 -0500 Subject: [PATCH 052/286] begin using position to derive the index --- src/Menu/MenuBar.jsx | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 1f4ba64b..079454e8 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -52,21 +52,31 @@ class MenuBar extends React.Component { const { key, target, shiftKey } = event; const { nextElementSibling } = target; const role = target.getAttribute('role'); - const position = target.dataset.position.split(',').map(index => Number.parseInt(index, 10)); + const position = target.dataset.position.split(','); const isParentMenuitem = role === 'menuitem' && nextElementSibling && nextElementSibling.getAttribute('role') === 'menu'; let item; + let nextIndex; + let index; //location of item within subItems + let subItems = items; //(sub-)menu that item belongs in - console.log(position); + position.forEach(i => { + index = Number.parseInt(i, 10); + item = subItems[index]; + + //Don't do this on the last iteration so we know + //the subset of items that item belongs in. Otherwise, + //subItems would be the children of item (assuming + //item is a parent menuitem). + if(index < position.length - 1) + subItems = item.children; + }); - /* - let nextIndex = index; + console.log(position, items, subItems, item, index) //According to the WAI-ARIA Authoring Practices 1.1, //the element with the role "menu" should be the //sibling element immediately following its parent //"menuitem". - console.log(key, shiftKey, position, item, isParentMenuitem); - //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins //with that printable character. @@ -80,7 +90,7 @@ class MenuBar extends React.Component { else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - nextIndex = index === 0 ? items.length - 1 : index - 1; + nextIndex = index === 0 ? subItems.length - 1 : index - 1; this.setState(prevState => { prevState.items[index].isFocusable = false; @@ -92,7 +102,7 @@ class MenuBar extends React.Component { else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - nextIndex = index === items.length - 1 ? 0 : index + 1; + nextIndex = index === subItems.length - 1 ? 0 : index + 1; this.setState(prevState => { prevState.items[index].isFocusable = false; @@ -122,7 +132,7 @@ class MenuBar extends React.Component { else if(key === 'End') { event.preventDefault(); - nextIndex = items.length - 1; + nextIndex = subItems.length - 1; this.setState(prevState => { prevState.items[index].isFocusable = false; @@ -140,7 +150,6 @@ class MenuBar extends React.Component { else { } } - */ }; //---- Rendering ---- From 4a90abfd8ef41c94ab46950b04fd921ca9f4dd61 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 30 Dec 2021 11:30:19 -0500 Subject: [PATCH 053/286] specify basic nav for the root level --- src/Menu/MenuBar.jsx | 104 +++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 079454e8..975bd0b5 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -54,24 +54,26 @@ class MenuBar extends React.Component { const role = target.getAttribute('role'); const position = target.dataset.position.split(','); const isParentMenuitem = role === 'menuitem' && nextElementSibling && nextElementSibling.getAttribute('role') === 'menu'; - let item; - let nextIndex; - let index; //location of item within subItems + const level = position.length - 1; + let index = Number.parseInt(position[position.length - 1], 10); + let item = items[index]; let subItems = items; //(sub-)menu that item belongs in - position.forEach(i => { - index = Number.parseInt(i, 10); - item = subItems[index]; - - //Don't do this on the last iteration so we know - //the subset of items that item belongs in. Otherwise, - //subItems would be the children of item (assuming - //item is a parent menuitem). - if(index < position.length - 1) - subItems = item.children; - }); + if(level > 0) { + for(let i = 1; i < positions.length; i++) { + const pos = Number.parseInt(positions[i], 10); + item = subItems[pos]; + + //Don't do this on the last iteration so we know + //the subset of items that item belongs in. Otherwise, + //subItems would be the children of item (assuming + //item is a parent menuitem). + if(i < position.length - 1) + subItems = item.children; + } + } - console.log(position, items, subItems, item, index) + console.log(level, index, item, subItems); //According to the WAI-ARIA Authoring Practices 1.1, //the element with the role "menu" should be the @@ -90,26 +92,34 @@ class MenuBar extends React.Component { else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - nextIndex = index === 0 ? subItems.length - 1 : index - 1; + if(level === 0) { + const nextIndex = index === 0 ? items.length - 1 : index - 1; - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); - return prevState; - }); + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); + } + else { + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - nextIndex = index === subItems.length - 1 ? 0 : index + 1; + if(level === 0) { + const nextIndex = index === items.length - 1 ? 0 : index + 1; - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); - return prevState; - }); + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); + } + else { + } } else if(key === 'Enter') { event.preventDefault(); @@ -120,26 +130,34 @@ class MenuBar extends React.Component { else if(key === 'Home') { event.preventDefault(); - nextIndex = 0; + if(level === 0) { + const nextIndex = 0; - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); - return prevState; - }); + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); + } + else { + } } else if(key === 'End') { event.preventDefault(); - nextIndex = subItems.length - 1; + if(level === 0) { + const nextIndex = items.length - 1; - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); - return prevState; - }); + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[nextIndex].isFocusable = true; + prevState.items[nextIndex].ref.current.focus(); + return prevState; + }); + } + else { + } } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); From 0382dbc395dc05c56123b433c20684511db811d4 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 30 Dec 2021 13:02:47 -0500 Subject: [PATCH 054/286] begin work on arrowdown handler and consolidate some code --- src/Menu/MenuBar.jsx | 111 ++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 975bd0b5..9a88aa72 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -55,30 +55,25 @@ class MenuBar extends React.Component { const position = target.dataset.position.split(','); const isParentMenuitem = role === 'menuitem' && nextElementSibling && nextElementSibling.getAttribute('role') === 'menu'; const level = position.length - 1; - let index = Number.parseInt(position[position.length - 1], 10); - let item = items[index]; + let index; + let nextIndex; + let item; let subItems = items; //(sub-)menu that item belongs in - if(level > 0) { - for(let i = 1; i < positions.length; i++) { - const pos = Number.parseInt(positions[i], 10); - item = subItems[pos]; - - //Don't do this on the last iteration so we know - //the subset of items that item belongs in. Otherwise, - //subItems would be the children of item (assuming - //item is a parent menuitem). - if(i < position.length - 1) - subItems = item.children; - } - } - + position.forEach((pos, i) => { + index = Number.parseInt(pos, 10); + item = subItems[index]; + + //Don't do this on the last iteration so we know + //the subset of items that item belongs in. Otherwise, + //subItems would be the children of item (assuming + //item is a parent menuitem). + if(i < position.length - 1) + subItems = item.children; + }); + console.log(level, index, item, subItems); - //According to the WAI-ARIA Authoring Practices 1.1, - //the element with the role "menu" should be the - //sibling element immediately following its parent - //"menuitem". //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins //with that printable character. @@ -88,17 +83,36 @@ class MenuBar extends React.Component { } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + + if(level > 0) { + this.setState(prevState => { + nextIndex = index === subItems.length - 1 ? 0 : index + 1; + subItems[index].isFocusable = false; + subItems[nextIndex].isFocusable = true; + subItems[nextIndex].ref.current.focus(); + return prevState; + }); + } + else if(isParentMenuitem) { + this.setState(prevState => { + prevState.items[index].isFocusable = false; + prevState.items[index].isExpanded = true; + prevState.items[index].children[0].isFocusable = true; + return prevState; + }, () => { + this.state.items[index].children[0].ref.current.focus(); + }); + } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); if(level === 0) { - const nextIndex = index === 0 ? items.length - 1 : index - 1; - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); + nextIndex = index === 0 ? subItems.length - 1 : index - 1; + subItems[index].isFocusable = false; + subItems[nextIndex].isFocusable = true; + subItems[nextIndex].ref.current.focus(); return prevState; }); } @@ -109,12 +123,11 @@ class MenuBar extends React.Component { event.preventDefault(); if(level === 0) { - const nextIndex = index === items.length - 1 ? 0 : index + 1; - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); + nextIndex = index === subItems.length - 1 ? 0 : index + 1; + subItems[index].isFocusable = false; + subItems[nextIndex].isFocusable = true; + subItems[nextIndex].ref.current.focus(); return prevState; }); } @@ -130,34 +143,24 @@ class MenuBar extends React.Component { else if(key === 'Home') { event.preventDefault(); - if(level === 0) { - const nextIndex = 0; - - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); - return prevState; - }); - } - else { - } + this.setState(prevState => { + nextIndex = 0; + subItems[index].isFocusable = false; + subItems[nextIndex].isFocusable = true; + subItems[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'End') { event.preventDefault(); - if(level === 0) { - const nextIndex = items.length - 1; - - this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[nextIndex].isFocusable = true; - prevState.items[nextIndex].ref.current.focus(); - return prevState; - }); - } - else { - } + this.setState(prevState => { + nextIndex = subItems.length - 1; + subItems[index].isFocusable = false; + subItems[nextIndex].isFocusable = true; + subItems[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); From 5ebb66d54aa7f1fab74f79d02dcc82b3313922c2 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 30 Dec 2021 13:11:00 -0500 Subject: [PATCH 055/286] first pass at up arrow handling --- src/Menu/MenuBar.jsx | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 9a88aa72..c106a807 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -59,7 +59,10 @@ class MenuBar extends React.Component { let nextIndex; let item; let subItems = items; //(sub-)menu that item belongs in - + + //TODO this should probably be put inside the setState() calls + //so that subItems is referencing prevState rather than + //this.state position.forEach((pos, i) => { index = Number.parseInt(pos, 10); item = subItems[index]; @@ -80,6 +83,26 @@ class MenuBar extends React.Component { //TODO: take into account orientation if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); + + if(level > 0) { + this.setState(prevState => { + nextIndex = index === 0 ? subItems.length - 1 : index - 1; + subItems[index].isFocusable = false; + subItems[nextIndex].isFocusable = true; + subItems[nextIndex].ref.current.focus(); + return prevState; + }); + } + else if(isParentMenuitem) { + this.setState(prevState => { + item.isFocusable = false; + item.isExpanded = true; + item.children[item.children.length - 1].isFocusable = true; + return prevState; + }, () => { + item.children[item.children.length - 1].ref.current.focus(); + }); + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); @@ -95,12 +118,12 @@ class MenuBar extends React.Component { } else if(isParentMenuitem) { this.setState(prevState => { - prevState.items[index].isFocusable = false; - prevState.items[index].isExpanded = true; - prevState.items[index].children[0].isFocusable = true; + item.isFocusable = false; + item.isExpanded = true; + item.children[0].isFocusable = true; return prevState; }, () => { - this.state.items[index].children[0].ref.current.focus(); + item.children[0].ref.current.focus(); }); } } From 23fee83fb3552668727084709e5032e8215733f7 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 30 Dec 2021 13:18:41 -0500 Subject: [PATCH 056/286] first pass at handling enter --- src/Menu/MenuBar.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index c106a807..35325cbf 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -159,6 +159,23 @@ class MenuBar extends React.Component { } else if(key === 'Enter') { event.preventDefault(); + + if(isParentMenuitem) { + this.setState(prevState => { + item.isFocusable = false; + item.isExpanded = true; + item.children[0].isFocusable = true; + return prevState; + }, () => { + item.children[0].ref.current.focus(); + }); + } + else { + //TODO activate the item and close the menu. + //does that activation need to be done manually + //here? Also, I'm pretty sure we need to close the whole menu, + //not just the current sub-menu + } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); From 447cfb465da0611ad43a26423eb2e5d1e82f46dd Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 30 Dec 2021 13:21:25 -0500 Subject: [PATCH 057/286] first pass at handling spacebar --- src/Menu/MenuBar.jsx | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 35325cbf..e0a5966f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -171,14 +171,31 @@ class MenuBar extends React.Component { }); } else { - //TODO activate the item and close the menu. - //does that activation need to be done manually - //here? Also, I'm pretty sure we need to close the whole menu, - //not just the current sub-menu + //TODO activate the item and close the (whole?) menu } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + + if(isParentMenuitem) { + this.setState(prevState => { + item.isFocusable = false; + item.isExpanded = true; + item.children[0].isFocusable = true; + return prevState; + }, () => { + item.children[0].ref.current.focus(); + }); + } + else if(role === 'menuitemcheckbox') { + //TODO: change state without closing the menu + } + else if(role === 'menuitemradio') { + //TODO change state without closing the menu + } + else { + //TODO: activate the item and closes the (whole?) menu + } } else if(key === 'Home') { event.preventDefault(); From 45ac80acaf1d0de89baa4f5374732e69ffbec0c8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 30 Dec 2021 13:54:10 -0500 Subject: [PATCH 058/286] implement more of the right arrow handler --- src/Menu/MenuBar.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index e0a5966f..621de862 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -154,6 +154,16 @@ class MenuBar extends React.Component { return prevState; }); } + else if(isParentMenuitem) { + this.setState(prevState => { + item.isFocusable = false; + item.isExpanded = true; + item.children[0].isFocusable = true; + return prevState; + }, () => { + item.children[0].ref.current.focus(); + }); + } else { } } From 18ab7326904383a3ce862047cd6643c31af866a8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 31 Dec 2021 13:37:49 -0500 Subject: [PATCH 059/286] write an isParentMenuitem utility function --- src/Menu/MenuBar.jsx | 14 ++++++-------- src/Menu/utils.jsx | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 621de862..da4c0240 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; -import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; +import { renderItem, renderMenuItem, renderParentMenuItem, isParentMenuitem } from 'src/Menu/utils'; /* * Some notes on props: @@ -50,10 +50,8 @@ class MenuBar extends React.Component { const { orientation } = this.props; const { items } = this.state; const { key, target, shiftKey } = event; - const { nextElementSibling } = target; const role = target.getAttribute('role'); const position = target.dataset.position.split(','); - const isParentMenuitem = role === 'menuitem' && nextElementSibling && nextElementSibling.getAttribute('role') === 'menu'; const level = position.length - 1; let index; let nextIndex; @@ -93,7 +91,7 @@ class MenuBar extends React.Component { return prevState; }); } - else if(isParentMenuitem) { + else if(isParentMenuitem(item)) { this.setState(prevState => { item.isFocusable = false; item.isExpanded = true; @@ -116,7 +114,7 @@ class MenuBar extends React.Component { return prevState; }); } - else if(isParentMenuitem) { + else if(isParentMenuitem(item)) { this.setState(prevState => { item.isFocusable = false; item.isExpanded = true; @@ -154,7 +152,7 @@ class MenuBar extends React.Component { return prevState; }); } - else if(isParentMenuitem) { + else if(isParentMenuitem(item)) { this.setState(prevState => { item.isFocusable = false; item.isExpanded = true; @@ -170,7 +168,7 @@ class MenuBar extends React.Component { else if(key === 'Enter') { event.preventDefault(); - if(isParentMenuitem) { + if(isParentMenuitem(item)) { this.setState(prevState => { item.isFocusable = false; item.isExpanded = true; @@ -187,7 +185,7 @@ class MenuBar extends React.Component { else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); - if(isParentMenuitem) { + if(isParentMenuitem(item)) { this.setState(prevState => { item.isFocusable = false; item.isExpanded = true; diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index e49893be..29325839 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -89,3 +89,29 @@ export function renderParentMenuItem(item, index, items, menuProps, onKeyDown) { ); } + +/** + * Checks if the item is a "parent menuitem". + * + * According to the WAI-ARIA Authoring Practices 1.1, + * we know that a sub-menu must be the immediate + * sibling of the menuitem that opens it. + * + * See: + * https://www.w3.org/TR/wai-aria-practices-1.1/#menu + * + * @param {(object)|(HTMLElement)} item + * @return {boolean} + */ +export function isParentMenuitem(item) { + if(item === undefined || item === null) + return false; + else if(item instanceof HTMLElement) { + const role = item.getAttribute('role'); + const nextSibling = item.nextElementSibling; + const nextSiblingRole = nextSibling ? nextSibling.getAttribute('role') : undefined; + return role === 'menuitem' && nextSiblingRole === 'menu'; + } + else + return item.type === 'parentmenuitem'; +} From c02ddf9adae1e7f61ec95b20514e71c5127f61df Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 31 Dec 2021 13:55:55 -0500 Subject: [PATCH 060/286] rudimentary styling to help visualize behavior --- src/Menu/MenuBar.jsx | 40 +++++++++++++++++++++++++++++++++++++--- src/styles.scss | 8 ++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index da4c0240..2d960c83 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -75,9 +75,6 @@ class MenuBar extends React.Component { console.log(level, index, item, subItems); - //TODO: Any key that corresponds to a printable character (Optional): - //Move focus to the next menu item in the current menu whose label begins - //with that printable character. //TODO: take into account orientation if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); @@ -163,6 +160,38 @@ class MenuBar extends React.Component { }); } else { + this.setState(prevState => { + //Close the submenu and any parent menus + let _items = items; + let _menuitem; + + position.forEach((pos, i) => { + const _pos = Number.parseInt(pos, 10); + position[i] = _pos; + _menuitem = _items[_pos]; + _items = _menuitem.children; + + _menuitem.isFocusable = false; + + if(isParentMenuitem(_menuitem)) + _menuitem.isExpanded = false; + }); + + //Move focus to the next menuitem in the menubar, + //and if it's a parent menuitem, open the submenu + //without changing focus + const index = position[0]; + const nextIndex = index === items.length - 1 ? 0 : index + 1; + const nextItem = items[nextIndex]; + + nextItem.isFocusable = true; + nextItem.ref.current.focus(); + + if(isParentMenuitem(nextItem)) + nextItem.isExpanded = true; + + return prevState; + }); } } else if(key === 'Enter') { @@ -236,6 +265,11 @@ class MenuBar extends React.Component { else { } } + else { + //TODO: Any key that corresponds to a printable character (Optional): + //Move focus to the next menu item in the current menu whose label begins + //with that printable character. + } }; //---- Rendering ---- diff --git a/src/styles.scss b/src/styles.scss index c0fcf853..e9d43629 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -6,6 +6,14 @@ display: none; } +[role="menubar"][aria-orientation="horizontal"] { + display: flex; + + > li { + flex: 1 1 auto; + } +} + //A technique for visually-hiding elements //but still keeping them readable to machines. //See: From cbb1d11b70ecb1178c77cd9b7847898d907670d6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 31 Dec 2021 14:07:40 -0500 Subject: [PATCH 061/286] accidentally did it in the prev commit, but (mostly) finish implementing right arrow behavior --- src/Menu/MenuBar.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 2d960c83..048b6cc8 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -129,6 +129,7 @@ class MenuBar extends React.Component { this.setState(prevState => { nextIndex = index === 0 ? subItems.length - 1 : index - 1; subItems[index].isFocusable = false; + subItems[index].isExpanded = false; subItems[nextIndex].isFocusable = true; subItems[nextIndex].ref.current.focus(); return prevState; @@ -142,10 +143,23 @@ class MenuBar extends React.Component { if(level === 0) { this.setState(prevState => { + //const wasExpanded = subItems[index].isExpanded; + nextIndex = index === subItems.length - 1 ? 0 : index + 1; subItems[index].isFocusable = false; + subItems[index].isExpanded = false; subItems[nextIndex].isFocusable = true; subItems[nextIndex].ref.current.focus(); + + //This behavior isn't defined in the authoring prac + //but is exhibited in the example implementations + //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html + //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-2/menubar-2.html + //FIXME: currently broken - what if subItems[nextIndex] isn't a parent menuitem, + //but the subsequent item is? we lose the "root expanded" state. + //if(isParentMenu(subItems[nextIndex]) && wasExpanded) + // subItems[nextIndex].isExpanded = true; + return prevState; }); } From 1639a7d1306bf72b66a9a564dfa69035b7d328e6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 31 Dec 2021 14:55:53 -0500 Subject: [PATCH 062/286] implement more of the left arrow handler --- src/Menu/MenuBar.jsx | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 048b6cc8..d622e302 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -126,6 +126,8 @@ class MenuBar extends React.Component { event.preventDefault(); if(level === 0) { + //TODO Like with arrow right, also maintain + //"root expand state"? this.setState(prevState => { nextIndex = index === 0 ? subItems.length - 1 : index - 1; subItems[index].isFocusable = false; @@ -135,7 +137,30 @@ class MenuBar extends React.Component { return prevState; }); } + else if (level === 1) { + this.setState(prevState => { + const pos1 = Number.parseInt(position[0]); + const pos2 = Number.parseInt(position[1]); + const nextIndex = pos1 === 0 ? items.length - 1 : pos1 - 1; + const nextItem = items[nextIndex]; + const parentMenuitem = items[pos1]; + const subMenuitem = items[pos1].children[pos2]; + + subMenuitem.isFocusable = false; + parentMenuitem.isExpanded = false; + nextItem.isFocusable = true; + nextItem.ref.current.focus(); + + if(isParentMenuitem) + nextItem.isExpanded = true; + + return prevState; + }); + } else { + this.setState(prevState => { + return prevState; + }); } } else if(key === 'ArrowRight' || key === 'Right') { From b9c105552f9307c5a7ae5f7a2576aff838fb556a Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 31 Dec 2021 15:05:25 -0500 Subject: [PATCH 063/286] this should (mostly) finish implementing the left arrow --- src/Menu/MenuBar.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d622e302..0e37be6f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -159,6 +159,23 @@ class MenuBar extends React.Component { } else { this.setState(prevState => { + let _items = items; + let _item; + + position.forEach((pos, i) => { + const _pos = Number.parseInt(pos, 10); + _item = _items[_pos]; + _items = _item.children; + + if(i === position.length - 2) { + _item.isFocusable = true; + _item.isExpanded = false; + _item.ref.current.focus(); + } + else if(i === position.length - 1) + _item.isFocusable = false; + }); + return prevState; }); } From a0dbc487a213d921b500aa8461511d0c0f7296fd Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 31 Dec 2021 15:15:51 -0500 Subject: [PATCH 064/286] implement the esc key --- src/Menu/MenuBar.jsx | 25 ++++++++++++++++++++++++- src/Menu/utils.jsx | 15 +-------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 0e37be6f..32b4581f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -151,7 +151,7 @@ class MenuBar extends React.Component { nextItem.isFocusable = true; nextItem.ref.current.focus(); - if(isParentMenuitem) + if(isParentMenuitem(nextItem)) nextItem.isExpanded = true; return prevState; @@ -314,6 +314,29 @@ class MenuBar extends React.Component { } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); + + if(level > 0) { + this.setState(prevState => { + let _items = items; + let _item; + + position.forEach((pos, i) => { + const _pos = Number.parseInt(pos); + _item = _items[pos]; + _items = _item.children; + + if(i === position.length - 2) { + _item.isExpanded = false; + _item.isFocusable = true; + _item.ref.current.focus(); + } + else if(i === position.length - 1) + _item.isFocusable = false; + }); + + return prevState; + }); + } } else if(key === 'Tab') { if(shiftKey) { diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 29325839..273b952a 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -93,25 +93,12 @@ export function renderParentMenuItem(item, index, items, menuProps, onKeyDown) { /** * Checks if the item is a "parent menuitem". * - * According to the WAI-ARIA Authoring Practices 1.1, - * we know that a sub-menu must be the immediate - * sibling of the menuitem that opens it. - * - * See: - * https://www.w3.org/TR/wai-aria-practices-1.1/#menu - * - * @param {(object)|(HTMLElement)} item + * @param {object} item * @return {boolean} */ export function isParentMenuitem(item) { if(item === undefined || item === null) return false; - else if(item instanceof HTMLElement) { - const role = item.getAttribute('role'); - const nextSibling = item.nextElementSibling; - const nextSiblingRole = nextSibling ? nextSibling.getAttribute('role') : undefined; - return role === 'menuitem' && nextSiblingRole === 'menu'; - } else return item.type === 'parentmenuitem'; } From 9b96d4b988765c11b993412a994c500f15dfd2c2 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 31 Dec 2021 16:08:13 -0500 Subject: [PATCH 065/286] first attempt at tab handling --- src/Menu/MenuBar.jsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 32b4581f..d7129bec 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -338,11 +338,24 @@ class MenuBar extends React.Component { }); } } - else if(key === 'Tab') { - if(shiftKey) { - } - else { - } + else if(key === 'Tab' && level > 0) { + this.setState(prevState => { + let _items = items; + let _item; + + position.forEach((pos, i) => { + const _pos = Number.parseInt(pos, 10); + _item = _items[pos]; + _items = _item.children; + + //FIXME: broken in both directions. shift+tab goes to the + //root item rather than the element before the menubar + _item.isFocusable = i === 0; + _item.isExpanded = false; + }); + + return prevState; + }); } else { //TODO: Any key that corresponds to a printable character (Optional): From 8ff92b48941a6a295bed20b8a0cff1af4b2b9b62 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Sun, 2 Jan 2022 12:54:02 -0500 Subject: [PATCH 066/286] rename "isFocusable" to "isTabbable" --- src/Menu/Menu.jsx | 1 + src/Menu/MenuBar.jsx | 64 ++++++++++++++++++------------------- src/Menu/MenuItem.jsx | 8 ++--- src/Menu/ParentMenuItem.jsx | 8 ++--- src/Menu/utils.jsx | 8 ++--- src/utils/propTypes.js | 2 +- 6 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index e493e84b..72b88b95 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +//TODO: needs a label/labelId prop function Menu(props) { const { children, orientation } = props; diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d7129bec..f8d733e8 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -82,17 +82,17 @@ class MenuBar extends React.Component { if(level > 0) { this.setState(prevState => { nextIndex = index === 0 ? subItems.length - 1 : index - 1; - subItems[index].isFocusable = false; - subItems[nextIndex].isFocusable = true; + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); return prevState; }); } else if(isParentMenuitem(item)) { this.setState(prevState => { - item.isFocusable = false; + item.isTabbable = false; item.isExpanded = true; - item.children[item.children.length - 1].isFocusable = true; + item.children[item.children.length - 1].isTabbable = true; return prevState; }, () => { item.children[item.children.length - 1].ref.current.focus(); @@ -105,17 +105,17 @@ class MenuBar extends React.Component { if(level > 0) { this.setState(prevState => { nextIndex = index === subItems.length - 1 ? 0 : index + 1; - subItems[index].isFocusable = false; - subItems[nextIndex].isFocusable = true; + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); return prevState; }); } else if(isParentMenuitem(item)) { this.setState(prevState => { - item.isFocusable = false; + item.isTabbable = false; item.isExpanded = true; - item.children[0].isFocusable = true; + item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -130,9 +130,9 @@ class MenuBar extends React.Component { //"root expand state"? this.setState(prevState => { nextIndex = index === 0 ? subItems.length - 1 : index - 1; - subItems[index].isFocusable = false; + subItems[index].isTabbable = false; subItems[index].isExpanded = false; - subItems[nextIndex].isFocusable = true; + subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); return prevState; }); @@ -146,9 +146,9 @@ class MenuBar extends React.Component { const parentMenuitem = items[pos1]; const subMenuitem = items[pos1].children[pos2]; - subMenuitem.isFocusable = false; + subMenuitem.isTabbable = false; parentMenuitem.isExpanded = false; - nextItem.isFocusable = true; + nextItem.isTabbable = true; nextItem.ref.current.focus(); if(isParentMenuitem(nextItem)) @@ -168,12 +168,12 @@ class MenuBar extends React.Component { _items = _item.children; if(i === position.length - 2) { - _item.isFocusable = true; + _item.isTabbable = true; _item.isExpanded = false; _item.ref.current.focus(); } else if(i === position.length - 1) - _item.isFocusable = false; + _item.isTabbable = false; }); return prevState; @@ -188,9 +188,9 @@ class MenuBar extends React.Component { //const wasExpanded = subItems[index].isExpanded; nextIndex = index === subItems.length - 1 ? 0 : index + 1; - subItems[index].isFocusable = false; + subItems[index].isTabbable = false; subItems[index].isExpanded = false; - subItems[nextIndex].isFocusable = true; + subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); //This behavior isn't defined in the authoring prac @@ -207,9 +207,9 @@ class MenuBar extends React.Component { } else if(isParentMenuitem(item)) { this.setState(prevState => { - item.isFocusable = false; + item.isTabbable = false; item.isExpanded = true; - item.children[0].isFocusable = true; + item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -227,7 +227,7 @@ class MenuBar extends React.Component { _menuitem = _items[_pos]; _items = _menuitem.children; - _menuitem.isFocusable = false; + _menuitem.isTabbable = false; if(isParentMenuitem(_menuitem)) _menuitem.isExpanded = false; @@ -240,7 +240,7 @@ class MenuBar extends React.Component { const nextIndex = index === items.length - 1 ? 0 : index + 1; const nextItem = items[nextIndex]; - nextItem.isFocusable = true; + nextItem.isTabbable = true; nextItem.ref.current.focus(); if(isParentMenuitem(nextItem)) @@ -255,9 +255,9 @@ class MenuBar extends React.Component { if(isParentMenuitem(item)) { this.setState(prevState => { - item.isFocusable = false; + item.isTabbable = false; item.isExpanded = true; - item.children[0].isFocusable = true; + item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -272,9 +272,9 @@ class MenuBar extends React.Component { if(isParentMenuitem(item)) { this.setState(prevState => { - item.isFocusable = false; + item.isTabbable = false; item.isExpanded = true; - item.children[0].isFocusable = true; + item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -295,8 +295,8 @@ class MenuBar extends React.Component { this.setState(prevState => { nextIndex = 0; - subItems[index].isFocusable = false; - subItems[nextIndex].isFocusable = true; + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); return prevState; }); @@ -306,8 +306,8 @@ class MenuBar extends React.Component { this.setState(prevState => { nextIndex = subItems.length - 1; - subItems[index].isFocusable = false; - subItems[nextIndex].isFocusable = true; + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); return prevState; }); @@ -327,11 +327,11 @@ class MenuBar extends React.Component { if(i === position.length - 2) { _item.isExpanded = false; - _item.isFocusable = true; + _item.isTabbable = true; _item.ref.current.focus(); } else if(i === position.length - 1) - _item.isFocusable = false; + _item.isTabbable = false; }); return prevState; @@ -350,7 +350,7 @@ class MenuBar extends React.Component { //FIXME: broken in both directions. shift+tab goes to the //root item rather than the element before the menubar - _item.isFocusable = i === 0; + _item.isTabbable = i === 0; _item.isExpanded = false; }); @@ -401,7 +401,7 @@ class MenuBar extends React.Component { //info attached. _items.push(Object.assign({}, item, { ref: React.createRef(), - isFocusable: i === 0 && level === 0, + isTabbable: i === 0 && level === 0, position, })); diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index a5357341..da684771 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -3,13 +3,13 @@ import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, isDisabled, isFocusable, onKeyDown, position } = props; + const { children, isDisabled, isTabbable, onKeyDown, position } = props; return (
                                    • { @@ -28,7 +28,7 @@ const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { aria-haspopup="menu" aria-expanded={ isExpanded } aria-disabled={ isDisabled } - tabIndex={ isFocusable ? '0' : '-1' } + tabIndex={ isTabbable ? '0' : '-1' } ref={ ref } onKeyDown={ onKeyDown } data-position={ position.toString() } @@ -49,7 +49,7 @@ ParentMenuItem.propTypes = { position: PropTypes.arrayOf(PropTypes.number).isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, - isFocusable: PropTypes.bool, + isTabbable: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -59,7 +59,7 @@ ParentMenuItem.propTypes = { ParentMenuItem.defaultProps = { isExpanded: false, isDisabled: false, - isFocusable: false, + isTabbable: false, orientation: 'horizontal', renderItem, renderMenuItem, diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 273b952a..8c9955d7 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -40,13 +40,13 @@ export function renderItem(item, index, items, menuProps, onKeyDown) { * @param {function} onKeyDown */ export function renderMenuItem(menuItem, index, menuItems, menuProps, onKeyDown) { - const { node, isDisabled, isFocusable, ref, position } = menuItem; + const { node, isDisabled, isTabbable, ref, position } = menuItem; return ( Date: Mon, 3 Jan 2022 11:09:30 -0500 Subject: [PATCH 067/286] only root-level menu items should be tabbable --- src/Menu/MenuBar.jsx | 54 ++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f8d733e8..637c69a2 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -40,6 +40,7 @@ class MenuBar extends React.Component { //TODO: feels incredibly awkward, e.g.: //- refs in state? //- what if someone passes new props (e.g. change isDisabled for an item)? + //- are we updating state properly? deep copy/nested weirdness? this.state = { items: this.initializeItems(items), }; @@ -75,24 +76,22 @@ class MenuBar extends React.Component { console.log(level, index, item, subItems); - //TODO: take into account orientation + //TODO: + //- take into account orientation + //- consolidate logic and move into separate methods? if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); if(level > 0) { this.setState(prevState => { nextIndex = index === 0 ? subItems.length - 1 : index - 1; - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); return prevState; }); } else if(isParentMenuitem(item)) { this.setState(prevState => { - item.isTabbable = false; item.isExpanded = true; - item.children[item.children.length - 1].isTabbable = true; return prevState; }, () => { item.children[item.children.length - 1].ref.current.focus(); @@ -105,17 +104,13 @@ class MenuBar extends React.Component { if(level > 0) { this.setState(prevState => { nextIndex = index === subItems.length - 1 ? 0 : index + 1; - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); return prevState; }); } else if(isParentMenuitem(item)) { this.setState(prevState => { - item.isTabbable = false; item.isExpanded = true; - item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -144,9 +139,8 @@ class MenuBar extends React.Component { const nextIndex = pos1 === 0 ? items.length - 1 : pos1 - 1; const nextItem = items[nextIndex]; const parentMenuitem = items[pos1]; - const subMenuitem = items[pos1].children[pos2]; - - subMenuitem.isTabbable = false; + + parentMenuitem.isTabbable = false; parentMenuitem.isExpanded = false; nextItem.isTabbable = true; nextItem.ref.current.focus(); @@ -168,12 +162,9 @@ class MenuBar extends React.Component { _items = _item.children; if(i === position.length - 2) { - _item.isTabbable = true; _item.isExpanded = false; _item.ref.current.focus(); } - else if(i === position.length - 1) - _item.isTabbable = false; }); return prevState; @@ -207,9 +198,7 @@ class MenuBar extends React.Component { } else if(isParentMenuitem(item)) { this.setState(prevState => { - item.isTabbable = false; item.isExpanded = true; - item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -227,8 +216,6 @@ class MenuBar extends React.Component { _menuitem = _items[_pos]; _items = _menuitem.children; - _menuitem.isTabbable = false; - if(isParentMenuitem(_menuitem)) _menuitem.isExpanded = false; }); @@ -239,7 +226,9 @@ class MenuBar extends React.Component { const index = position[0]; const nextIndex = index === items.length - 1 ? 0 : index + 1; const nextItem = items[nextIndex]; - + const item = items[index]; + + item.isTabbable = false; nextItem.isTabbable = true; nextItem.ref.current.focus(); @@ -255,9 +244,7 @@ class MenuBar extends React.Component { if(isParentMenuitem(item)) { this.setState(prevState => { - item.isTabbable = false; item.isExpanded = true; - item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -272,9 +259,7 @@ class MenuBar extends React.Component { if(isParentMenuitem(item)) { this.setState(prevState => { - item.isTabbable = false; item.isExpanded = true; - item.children[0].isTabbable = true; return prevState; }, () => { item.children[0].ref.current.focus(); @@ -295,8 +280,12 @@ class MenuBar extends React.Component { this.setState(prevState => { nextIndex = 0; - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; + + if(level === 0) { + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; + } + subItems[nextIndex].ref.current.focus(); return prevState; }); @@ -306,8 +295,12 @@ class MenuBar extends React.Component { this.setState(prevState => { nextIndex = subItems.length - 1; - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; + + if(level === 0) { + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; + } + subItems[nextIndex].ref.current.focus(); return prevState; }); @@ -327,11 +320,8 @@ class MenuBar extends React.Component { if(i === position.length - 2) { _item.isExpanded = false; - _item.isTabbable = true; _item.ref.current.focus(); } - else if(i === position.length - 1) - _item.isTabbable = false; }); return prevState; @@ -350,7 +340,7 @@ class MenuBar extends React.Component { //FIXME: broken in both directions. shift+tab goes to the //root item rather than the element before the menubar - _item.isTabbable = i === 0; + //_item.isTabbable = i === 0; _item.isExpanded = false; }); From 3f168e4712ccac3f86af296e6555200b042490b8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 3 Jan 2022 13:00:45 -0500 Subject: [PATCH 068/286] comment out tab handling for now --- src/Menu/MenuBar.jsx | 4 +++- src/styles.scss | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 637c69a2..41f38936 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -328,7 +328,8 @@ class MenuBar extends React.Component { }); } } - else if(key === 'Tab' && level > 0) { + else if(key === 'Tab') { + /* this.setState(prevState => { let _items = items; let _item; @@ -346,6 +347,7 @@ class MenuBar extends React.Component { return prevState; }); + */ } else { //TODO: Any key that corresponds to a printable character (Optional): diff --git a/src/styles.scss b/src/styles.scss index e9d43629..23bcdbed 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,3 +1,7 @@ +:focus { + background-color: red; +} + .hidden { display: none; } From f7b7937c316963af99b4894f5bbf000bdc14e394 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 3 Jan 2022 15:38:47 -0500 Subject: [PATCH 069/286] bring back collapse-on-tab --- src/Menu/MenuBar.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 41f38936..609487ec 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -329,7 +329,6 @@ class MenuBar extends React.Component { } } else if(key === 'Tab') { - /* this.setState(prevState => { let _items = items; let _item; @@ -340,14 +339,18 @@ class MenuBar extends React.Component { _items = _item.children; //FIXME: broken in both directions. shift+tab goes to the - //root item rather than the element before the menubar + //root item rather than the element before the menubar. + //Tabbing forward seems to focus on some unknown "thing" + //rather than something on the browser (though that only + //seems to occur if the menubar is NOT the last element). + //This behavior does not show up on the WAI-ARIA example + //implementations so it's likely something we're doing. //_item.isTabbable = i === 0; _item.isExpanded = false; }); return prevState; }); - */ } else { //TODO: Any key that corresponds to a printable character (Optional): From d0ae1b3bcc5e4be0b8abc5934a7407651a5ef3fd Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 3 Jan 2022 15:40:28 -0500 Subject: [PATCH 070/286] nobody uses this proptype anymore --- src/Menu/MenuBar.jsx | 26 +++++++++++++------------- src/Menu/ParentMenuItem.jsx | 4 ++-- src/utils/propTypes.js | 10 ---------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 609487ec..135ec0d6 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -36,7 +36,7 @@ class MenuBar extends React.Component { super(props); const { items } = props; - + //TODO: feels incredibly awkward, e.g.: //- refs in state? //- what if someone passes new props (e.g. change isDisabled for an item)? @@ -58,14 +58,14 @@ class MenuBar extends React.Component { let nextIndex; let item; let subItems = items; //(sub-)menu that item belongs in - + //TODO this should probably be put inside the setState() calls //so that subItems is referencing prevState rather than //this.state position.forEach((pos, i) => { index = Number.parseInt(pos, 10); item = subItems[index]; - + //Don't do this on the last iteration so we know //the subset of items that item belongs in. Otherwise, //subItems would be the children of item (assuming @@ -73,7 +73,7 @@ class MenuBar extends React.Component { if(i < position.length - 1) subItems = item.children; }); - + console.log(level, index, item, subItems); //TODO: @@ -132,14 +132,14 @@ class MenuBar extends React.Component { return prevState; }); } - else if (level === 1) { + else if(level === 1) { this.setState(prevState => { const pos1 = Number.parseInt(position[0]); const pos2 = Number.parseInt(position[1]); const nextIndex = pos1 === 0 ? items.length - 1 : pos1 - 1; const nextItem = items[nextIndex]; const parentMenuitem = items[pos1]; - + parentMenuitem.isTabbable = false; parentMenuitem.isExpanded = false; nextItem.isTabbable = true; @@ -176,14 +176,14 @@ class MenuBar extends React.Component { if(level === 0) { this.setState(prevState => { - //const wasExpanded = subItems[index].isExpanded; + //Const wasExpanded = subItems[index].isExpanded; nextIndex = index === subItems.length - 1 ? 0 : index + 1; subItems[index].isTabbable = false; subItems[index].isExpanded = false; subItems[nextIndex].isTabbable = true; subItems[nextIndex].ref.current.focus(); - + //This behavior isn't defined in the authoring prac //but is exhibited in the example implementations //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html @@ -227,7 +227,7 @@ class MenuBar extends React.Component { const nextIndex = index === items.length - 1 ? 0 : index + 1; const nextItem = items[nextIndex]; const item = items[index]; - + item.isTabbable = false; nextItem.isTabbable = true; nextItem.ref.current.focus(); @@ -256,7 +256,7 @@ class MenuBar extends React.Component { } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); - + if(isParentMenuitem(item)) { this.setState(prevState => { item.isExpanded = true; @@ -307,7 +307,7 @@ class MenuBar extends React.Component { } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); - + if(level > 0) { this.setState(prevState => { let _items = items; @@ -337,7 +337,7 @@ class MenuBar extends React.Component { const _pos = Number.parseInt(pos, 10); _item = _items[pos]; _items = _item.children; - + //FIXME: broken in both directions. shift+tab goes to the //root item rather than the element before the menubar. //Tabbing forward seems to focus on some unknown "thing" @@ -387,7 +387,7 @@ class MenuBar extends React.Component { items.forEach((item, i) => { const { type, children } = item; - + position = position.slice(0); position[level] = i; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 84a3eb53..48979581 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import Menu from 'src/Menu/Menu'; //Misc. -import { MENU_ITEMS_PROPTYPE, MENU_ITEMS_METADATA_PROPTYPE } from 'src/utils/propTypes'; +import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; //TODO: this is straying further and further away @@ -14,7 +14,7 @@ import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { children, items, isExpanded, isDisabled, isTabbable, - orientation, renderItem, onKeyDown, position + orientation, renderItem, onKeyDown, position, } = props; const itemNodes = items.map((item, index, _items) => { return renderItem(item, index, _items, props, onKeyDown); diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index c343245b..ab7d50df 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -66,14 +66,4 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ isTabbable: PropTypes.bool, }); -export const MENU_ITEM_METADATA_PROPTYPE = PropTypes.shape({ - id: PropTypes.string, //TODO required? - parentId: PropTypes.string, - ref: PropTypes.shape({ - current: PropTypes.object, - }).isRequired, - childMetaData: MENU_ITEMS_METADATA_PROPTYPE, //Only required for "parentmenuitem" -}); - export const MENU_ITEMS_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_PROPTYPE); -export const MENU_ITEMS_METADATA_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_METADATA_PROPTYPE); From 815abad640fe1a085a5231a079561f39d07552a7 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 3 Jan 2022 15:44:01 -0500 Subject: [PATCH 071/286] resolve linter complaints --- src/Menu/MenuBar.jsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 135ec0d6..738edc6d 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -50,7 +50,7 @@ class MenuBar extends React.Component { onItemKeyDown = (event) => { const { orientation } = this.props; const { items } = this.state; - const { key, target, shiftKey } = event; + const { key, target } = event; const role = target.getAttribute('role'); const position = target.dataset.position.split(','); const level = position.length - 1; @@ -134,8 +134,7 @@ class MenuBar extends React.Component { } else if(level === 1) { this.setState(prevState => { - const pos1 = Number.parseInt(position[0]); - const pos2 = Number.parseInt(position[1]); + const pos1 = Number.parseInt(position[0], 10); const nextIndex = pos1 === 0 ? items.length - 1 : pos1 - 1; const nextItem = items[nextIndex]; const parentMenuitem = items[pos1]; @@ -314,8 +313,8 @@ class MenuBar extends React.Component { let _item; position.forEach((pos, i) => { - const _pos = Number.parseInt(pos); - _item = _items[pos]; + const _pos = Number.parseInt(pos, 10); + _item = _items[_pos]; _items = _item.children; if(i === position.length - 2) { @@ -333,9 +332,9 @@ class MenuBar extends React.Component { let _items = items; let _item; - position.forEach((pos, i) => { + position.forEach(pos => { const _pos = Number.parseInt(pos, 10); - _item = _items[pos]; + _item = _items[_pos]; _items = _item.children; //FIXME: broken in both directions. shift+tab goes to the From aa2f26f9476c423a0a2935a5f1281e029f6512ac Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 00:11:33 -0500 Subject: [PATCH 072/286] make ParentMenuItem a class component --- src/Menu/ParentMenuItem.jsx | 67 ++++++++++++++++++++++++++++++++++++- src/utils/propTypes.js | 4 +++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 48979581..8b5342c2 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -5,12 +5,76 @@ import PropTypes from 'prop-types'; import Menu from 'src/Menu/Menu'; //Misc. -import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { MENU_ITEMS_PROPTYPE, REF_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; +class ParentMenuItem extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + items: MENU_ITEMS_PROPTYPE.isRequired, + onKeyDown: PropTypes.func.isRequired, + position: PropTypes.arrayOf(PropTypes.number).isRequired, + forwardedRef: REF_PROPTYPE.isRequired, + isExpanded: PropTypes.bool, + isDisabled: PropTypes.bool, + isTabbable: PropTypes.bool, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + renderItem: PropTypes.func, + renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types + renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types + }; + + static defaultProps = { + isExpanded: false, + isDisabled: false, + isTabbable: false, + orientation: 'horizontal', + renderItem, + renderMenuItem, + renderParentMenuItem, + }; + + //---- Rendering ---- + render() { + const { + children, items, isExpanded, isDisabled, isTabbable, + orientation, renderItem, onKeyDown, position, forwardedRef, + } = this.props; + const itemNodes = items.map((item, index, _items) => { + return renderItem(item, index, _items, this.props, onKeyDown); + }); + + return ( +
                                    • + + { children } + + + { itemNodes } + +
                                    • + ); + } +} + +export default React.forwardRef((props, ref) => { + return ; +}); + //TODO: this is straying further and further away //from the idea of a "base" component - might be a good //idea to separate the opinionated stuff I'm adding on? +/* const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { const { children, items, isExpanded, isDisabled, isTabbable, @@ -67,3 +131,4 @@ ParentMenuItem.defaultProps = { }; export default ParentMenuItem; +*/ diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index ab7d50df..f9a4181f 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -67,3 +67,7 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ }); export const MENU_ITEMS_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_PROPTYPE); + +export const REF_PROPTYPE = PropTypes.shape({ + current: PropTypes.object, +}); From 7b1e3c9c9385b3688a824e85ac9411617d3a3586 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 10:12:58 -0500 Subject: [PATCH 073/286] for now have ParentMenuItem be directly responsible for rendering its children --- src/Menu/ParentMenuItem.jsx | 54 ++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8b5342c2..1d1e193e 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; //Components and Styles import Menu from 'src/Menu/Menu'; +import MenuItem from 'src/Menu/MenuItem'; //Misc. import { MENU_ITEMS_PROPTYPE, REF_PROPTYPE } from 'src/utils/propTypes'; @@ -34,15 +35,26 @@ class ParentMenuItem extends React.Component { renderParentMenuItem, }; + constructor(props) { + super(props); + + const { items } = props; + + this.itemRefs = items.map(() => React.createRef()); + } + + //---- Events ---- + onItemKeyDown = (event) => { + + }; + //---- Rendering ---- render() { const { children, items, isExpanded, isDisabled, isTabbable, orientation, renderItem, onKeyDown, position, forwardedRef, } = this.props; - const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, this.props, onKeyDown); - }); + const itemNodes = items.map(this.renderItem); return (
                                    • @@ -65,6 +77,42 @@ class ParentMenuItem extends React.Component {
                                    • ); } + + renderItem = (item, index, items) => { + const { position } = this.props; + const { node, type, isDisabled, children, isExpanded, isTabbable, orientation } = item; + + if(type === 'menuitem') { + return ( + + { node } + + ); + } + else if(type === 'parentmenuitem') { + return ( + + { node } + + ); + } + }; } export default React.forwardRef((props, ref) => { From 7b1dafb10551a339b09e3776b98c8f45a847ec35 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 11:13:13 -0500 Subject: [PATCH 074/286] have MenuBar handle its own rendering for now --- src/Menu/MenuBar.jsx | 50 ++++++++++++++++++++++++++++++++++--- src/Menu/ParentMenuItem.jsx | 8 +++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 738edc6d..b08041ab 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -1,6 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; +//Components and Styles +import MenuItem from 'src/Menu/MenuItem'; +import ParentMenuItem from 'src/Menu/ParentMenuItem'; + //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem, isParentMenuitem } from 'src/Menu/utils'; @@ -44,6 +48,8 @@ class MenuBar extends React.Component { this.state = { items: this.initializeItems(items), }; + + this.itemRefs = items.map(() => React.createRef()); } //---- Events ---- @@ -362,9 +368,11 @@ class MenuBar extends React.Component { render() { const { orientation, label, labelId, renderItem } = this.props; const { items } = this.state; - const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, this.props, this.onItemKeyDown); - }); + const itemNodes = items.map(this.renderItems); + //const { items } = this.state; + //const itemNodes = items.map((item, index, _items) => { + // return renderItem(item, index, _items, this.props, this.onItemKeyDown); + //}); console.log(this.props, this.state); @@ -380,6 +388,42 @@ class MenuBar extends React.Component { ); } + renderItems = (item, index, items) => { + const { type, node, position, children, isDisabled, isExpanded, isTabbable, orientation } = item; + + if(type === 'menuitem') { + return ( + + { node } + + ); + } + else if(type === 'parentmenuitem') { + return ( + + { node } + + ); + } + }; + //---- Misc. ---- initializeItems = (items, level = 0, position = []) => { const _items = []; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 1d1e193e..979af307 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -9,7 +9,7 @@ import MenuItem from 'src/Menu/MenuItem'; import { MENU_ITEMS_PROPTYPE, REF_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; -class ParentMenuItem extends React.Component { +class _ParentMenuItem extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, @@ -115,10 +115,12 @@ class ParentMenuItem extends React.Component { }; } -export default React.forwardRef((props, ref) => { - return ; +const ParentMenuItem = React.forwardRef((props, ref) => { + return <_ParentMenuItem {...props} forwardedRef={ ref } />; }); +export default ParentMenuItem; + //TODO: this is straying further and further away //from the idea of a "base" component - might be a good //idea to separate the opinionated stuff I'm adding on? From a0c77b41f36b0ed836d10d2699421338d703b5f9 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 11:32:11 -0500 Subject: [PATCH 075/286] move tabbability into the state --- src/Menu/MenuBar.jsx | 10 +++++----- src/Menu/ParentMenuItem.jsx | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index b08041ab..7a3b51bf 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -47,6 +47,7 @@ class MenuBar extends React.Component { //- are we updating state properly? deep copy/nested weirdness? this.state = { items: this.initializeItems(items), + tabbableIndex: 0, }; this.itemRefs = items.map(() => React.createRef()); @@ -389,7 +390,8 @@ class MenuBar extends React.Component { } renderItems = (item, index, items) => { - const { type, node, position, children, isDisabled, isExpanded, isTabbable, orientation } = item; + const { tabbableIndex } = this.state + const { type, node, position, children, isDisabled, isExpanded, orientation } = item; if(type === 'menuitem') { return ( @@ -399,7 +401,7 @@ class MenuBar extends React.Component { ref={ this.itemRefs[index] } onKeyDown={ this.onItemKeyDown } isDisabled={ isDisabled } - isTabbable={ isTabbable } + isTabbable={ index === tabbableIndex } > { node } @@ -416,7 +418,7 @@ class MenuBar extends React.Component { orientation={ orientation } isDisabled={ isDisabled } isExpanded={ isExpanded } - isTabbable={ isTabbable } + isTabbable={ index === tabbableIndex } > { node } @@ -438,8 +440,6 @@ class MenuBar extends React.Component { //so let's create a copy of items with some extra //info attached. _items.push(Object.assign({}, item, { - ref: React.createRef(), - isTabbable: i === 0 && level === 0, position, })); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 979af307..1aab7d17 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -80,7 +80,7 @@ class _ParentMenuItem extends React.Component { renderItem = (item, index, items) => { const { position } = this.props; - const { node, type, isDisabled, children, isExpanded, isTabbable, orientation } = item; + const { node, type, isDisabled, children, isExpanded, orientation } = item; if(type === 'menuitem') { return ( @@ -106,7 +106,6 @@ class _ParentMenuItem extends React.Component { onKeyDown={ this.onItemKeyDown } isExpanded={ isExpanded } isDisabled={ isDisabled } - isTabbable={ isTabbable } > { node }
                                      From 892d07f3fbd04d482633bb0bb748e003d69baeaf Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 11:38:04 -0500 Subject: [PATCH 076/286] try using only an index rather than the full position --- src/Menu/MenuBar.jsx | 17 ++++++++++++----- src/Menu/MenuItem.jsx | 6 +++--- src/Menu/ParentMenuItem.jsx | 11 +++++------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 7a3b51bf..0fc437e1 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -55,6 +55,13 @@ class MenuBar extends React.Component { //---- Events ---- onItemKeyDown = (event) => { + const { orientation } = this.props; + const { key, target } = event; + const index = Number.parseInt(target.dataset.index); + + console.log(index); + + /* const { orientation } = this.props; const { items } = this.state; const { key, target } = event; @@ -363,12 +370,12 @@ class MenuBar extends React.Component { //Move focus to the next menu item in the current menu whose label begins //with that printable character. } + */ }; //---- Rendering ---- render() { - const { orientation, label, labelId, renderItem } = this.props; - const { items } = this.state; + const { orientation, label, labelId, renderItem, items } = this.props; const itemNodes = items.map(this.renderItems); //const { items } = this.state; //const itemNodes = items.map((item, index, _items) => { @@ -391,13 +398,13 @@ class MenuBar extends React.Component { renderItems = (item, index, items) => { const { tabbableIndex } = this.state - const { type, node, position, children, isDisabled, isExpanded, orientation } = item; + const { type, node, children, isDisabled, isExpanded, orientation } = item; if(type === 'menuitem') { return ( { children } @@ -22,7 +22,7 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { MenuItem.propTypes = { children: PropTypes.node.isRequired, onKeyDown: PropTypes.func.isRequired, - position: PropTypes.arrayOf(PropTypes.number).isRequired, + index: PropTypes.number.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 1aab7d17..c3b27252 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -14,7 +14,7 @@ class _ParentMenuItem extends React.Component { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, onKeyDown: PropTypes.func.isRequired, - position: PropTypes.arrayOf(PropTypes.number).isRequired, + index: PropTypes.number.isRequired, forwardedRef: REF_PROPTYPE.isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, @@ -52,7 +52,7 @@ class _ParentMenuItem extends React.Component { render() { const { children, items, isExpanded, isDisabled, isTabbable, - orientation, renderItem, onKeyDown, position, forwardedRef, + orientation, renderItem, onKeyDown, index, forwardedRef, } = this.props; const itemNodes = items.map(this.renderItem); @@ -67,7 +67,7 @@ class _ParentMenuItem extends React.Component { tabIndex={ isTabbable ? '0' : '-1' } ref={ forwardedRef } onKeyDown={ onKeyDown } - data-position={ position.toString() } + data-index={ index } > { children } @@ -79,14 +79,13 @@ class _ParentMenuItem extends React.Component { } renderItem = (item, index, items) => { - const { position } = this.props; const { node, type, isDisabled, children, isExpanded, orientation } = item; if(type === 'menuitem') { return ( Date: Tue, 4 Jan 2022 11:38:46 -0500 Subject: [PATCH 077/286] get rid of some commented code for now --- src/Menu/MenuBar.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 0fc437e1..7e95eb45 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -377,10 +377,6 @@ class MenuBar extends React.Component { render() { const { orientation, label, labelId, renderItem, items } = this.props; const itemNodes = items.map(this.renderItems); - //const { items } = this.state; - //const itemNodes = items.map((item, index, _items) => { - // return renderItem(item, index, _items, this.props, this.onItemKeyDown); - //}); console.log(this.props, this.state); From 5c311a1376751961ebd051d149f5b794f4867bdf Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 11:42:09 -0500 Subject: [PATCH 078/286] skeleton for conditions --- src/Menu/MenuBar.jsx | 50 ++++++++++++++++++++++++++++++++-- src/Menu/ParentMenuItem.jsx | 54 ++++++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 7e95eb45..af642b1c 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -55,11 +55,57 @@ class MenuBar extends React.Component { //---- Events ---- onItemKeyDown = (event) => { - const { orientation } = this.props; + const { orientation, items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index); + const item = items[index]; + + console.log(index, item, items); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + + } + else if(key === 'Enter') { + event.preventDefault(); + + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + + } + else if(key === 'Home') { + event.preventDefault(); - console.log(index); + } + else if(key === 'End') { + event.preventDefault(); + + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + + } + else if(key === 'Tab') { + + } + else { + //TODO: Any key that corresponds to a printable character (Optional): + //Move focus to the next menu item in the current menu whose label begins + //with that printable character. + } /* const { orientation } = this.props; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index c3b27252..fe21e083 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -45,7 +45,59 @@ class _ParentMenuItem extends React.Component { //---- Events ---- onItemKeyDown = (event) => { - + const { orientation, items } = this.props; + const { key, target } = event; + const index = Number.parseInt(target.dataset.index); + const item = items[index]; + + console.log(index, item, items); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + + } + else if(key === 'Enter') { + event.preventDefault(); + + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + + } + else if(key === 'Home') { + event.preventDefault(); + + } + else if(key === 'End') { + event.preventDefault(); + + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + + } + else if(key === 'Tab') { + + } + else { + //TODO: Any key that corresponds to a printable character (Optional): + //Move focus to the next menu item in the current menu whose label begins + //with that printable character. + } + + }; //---- Rendering ---- From efb1f64f0810a0b29ccd9d7d4312dcc671b9ed12 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 11:47:47 -0500 Subject: [PATCH 079/286] trying out an "expandedIndex" to see which menus are expanded --- src/Menu/MenuBar.jsx | 7 ++++--- src/Menu/ParentMenuItem.jsx | 9 +++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index af642b1c..f59201a2 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -48,6 +48,7 @@ class MenuBar extends React.Component { this.state = { items: this.initializeItems(items), tabbableIndex: 0, + expandedIndex: undefined, }; this.itemRefs = items.map(() => React.createRef()); @@ -439,8 +440,8 @@ class MenuBar extends React.Component { } renderItems = (item, index, items) => { - const { tabbableIndex } = this.state - const { type, node, children, isDisabled, isExpanded, orientation } = item; + const { tabbableIndex, expandedIndex } = this.state + const { type, node, children, isDisabled, orientation } = item; if(type === 'menuitem') { return ( @@ -466,7 +467,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onItemKeyDown } orientation={ orientation } isDisabled={ isDisabled } - isExpanded={ isExpanded } + isExpanded={ index === expandedIndex } isTabbable={ index === tabbableIndex } > { node } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index fe21e083..4802c2de 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -40,6 +40,10 @@ class _ParentMenuItem extends React.Component { const { items } = props; + this.state = { + expandedIndex: undefined, + }; + this.itemRefs = items.map(() => React.createRef()); } @@ -131,7 +135,8 @@ class _ParentMenuItem extends React.Component { } renderItem = (item, index, items) => { - const { node, type, isDisabled, children, isExpanded, orientation } = item; + const { node, type, isDisabled, children, orientation } = item; + const { expandedIndex } = this.state; if(type === 'menuitem') { return ( @@ -155,7 +160,7 @@ class _ParentMenuItem extends React.Component { orientation={ orientation } ref={ this.itemRefs[index] } onKeyDown={ this.onItemKeyDown } - isExpanded={ isExpanded } + isExpanded={ index === expandedIndex } isDisabled={ isDisabled } > { node } From 968bfd07b34894d1b2ba5921f4cc80a4c9a854b0 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 11:51:12 -0500 Subject: [PATCH 080/286] ParentMenuItem no longer forwards its ref --- src/Menu/MenuBar.jsx | 6 ++++++ src/Menu/ParentMenuItem.jsx | 7 +------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f59201a2..f8d4dec2 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -66,6 +66,12 @@ class MenuBar extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); + this.setState({ + expandedIndex: index, + }, () => { + console.log(this.itemRefs[index]); + }); + } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 4802c2de..f8834a49 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -9,13 +9,12 @@ import MenuItem from 'src/Menu/MenuItem'; import { MENU_ITEMS_PROPTYPE, REF_PROPTYPE } from 'src/utils/propTypes'; import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; -class _ParentMenuItem extends React.Component { +class ParentMenuItem extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, onKeyDown: PropTypes.func.isRequired, index: PropTypes.number.isRequired, - forwardedRef: REF_PROPTYPE.isRequired, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, @@ -170,10 +169,6 @@ class _ParentMenuItem extends React.Component { }; } -const ParentMenuItem = React.forwardRef((props, ref) => { - return <_ParentMenuItem {...props} forwardedRef={ ref } />; -}); - export default ParentMenuItem; //TODO: this is straying further and further away From d448be7e0ae60454e012c9e98dfaf638ce3c95da Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 12:10:54 -0500 Subject: [PATCH 081/286] ParentMenuItem has a ref for itself, begin reimplementing keydown functionality on MenuBar --- src/Menu/MenuBar.jsx | 14 +++++++++++++- src/Menu/ParentMenuItem.jsx | 31 +++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f8d4dec2..ebd680fb 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -69,17 +69,29 @@ class MenuBar extends React.Component { this.setState({ expandedIndex: index, }, () => { - console.log(this.itemRefs[index]); + this.itemRefs[index].current.focusLastItem(); }); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + this.setState({ + expandedIndex: index, + }, () => { + this.itemRefs[index].current.focusFirstItem(); + }); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); + const newIndex = index === 0 ? items.length - 1 : index - 1; + this.setState({ + tabbableIndex: newIndex, + expandedIndex: undefined, //TODO: wai aria implementation maintains this if previously expanded + }, () => { + this.itemRefs[newIndex].current.focus(); + }); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index f8834a49..f94426ba 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -42,8 +42,9 @@ class ParentMenuItem extends React.Component { this.state = { expandedIndex: undefined, }; - - this.itemRefs = items.map(() => React.createRef()); + + this.itemRef = React.createRef(); + this.childItemRefs = items.map(() => React.createRef()); } //---- Events ---- @@ -107,7 +108,7 @@ class ParentMenuItem extends React.Component { render() { const { children, items, isExpanded, isDisabled, isTabbable, - orientation, renderItem, onKeyDown, index, forwardedRef, + orientation, renderItem, onKeyDown, index } = this.props; const itemNodes = items.map(this.renderItem); @@ -120,7 +121,7 @@ class ParentMenuItem extends React.Component { aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } - ref={ forwardedRef } + ref={ this.itemRef } onKeyDown={ onKeyDown } data-index={ index } > @@ -142,7 +143,7 @@ class ParentMenuItem extends React.Component { @@ -157,7 +158,7 @@ class ParentMenuItem extends React.Component { index={ index } items={ children } orientation={ orientation } - ref={ this.itemRefs[index] } + ref={ this.childItemRefs[index] } onKeyDown={ this.onItemKeyDown } isExpanded={ index === expandedIndex } isDisabled={ isDisabled } @@ -167,6 +168,24 @@ class ParentMenuItem extends React.Component { ); } }; + + //---- Events ---- + focus = () => { + this.itemRef.current.focus(); + }; + + focusChild = (index) => { + this.childItemRefs[index].current.focus(); + }; + + focusFirstChild = () => { + this.focusChild(0); + }; + + focusLastChild = () => { + const { items } = this.props; + this.focusChild(items.length - 1); + }; } export default ParentMenuItem; From 07fa73b7f143420ad8554fb451aa27cf340d7e91 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 12:23:57 -0500 Subject: [PATCH 082/286] bring back most of the functionality for menubar --- src/Menu/MenuBar.jsx | 55 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index ebd680fb..b95fe896 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -60,6 +60,7 @@ class MenuBar extends React.Component { const { key, target } = event; const index = Number.parseInt(target.dataset.index); const item = items[index]; + const { type } = item; console.log(index, item, items); @@ -95,30 +96,72 @@ class MenuBar extends React.Component { } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); + const newIndex = index === items.length - 1 ? 0 : index + 1; + + this.setState({ + tabbableIndex: newIndex, + expandedIndex: undefined, //TODO: wai aria implementation maintains this if previously expanded + }, () => { + this.itemRefs[newIndex].current.focus(); + }); } else if(key === 'Enter') { event.preventDefault(); - + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.itemRefs[index].current.focusFirstChild(); + }); + } + else { + //TODO: activate the item and close the (whole?) menu + } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.itemRefs[index].current.focusFirstChild(); + }); + } + else if(role === 'menuitemcheckbox') { + //TODO: change state without closing the menu + } + else if(role === 'menuitemradio') { + //TODO: change state without closing the menu + } + else if(role === 'menuitem') { + //TODO: activate the item and close the (whole?) menu + } } else if(key === 'Home') { event.preventDefault(); + this.setState({ + tabbableIndex: 0, + expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this if previous item was expanded + }, () => { + this.itemRefs[0].current.focus(); + }); } else if(key === 'End') { event.preventDefault(); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - + this.setState({ + tabbableIndex: items.length - 1, + expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this if previous item was expanded + }, () => { + this.itemRefs[items.length - 1].current.focus(); + }); } else if(key === 'Tab') { - + //TODO } else { //TODO: Any key that corresponds to a printable character (Optional): From ddb4d043de2e77b5098950bed4d52d6562695b4d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 13:08:23 -0500 Subject: [PATCH 083/286] bring back some more functionality for submenus --- src/Menu/MenuBar.jsx | 4 +- src/Menu/ParentMenuItem.jsx | 342 +++++++++++++++++++++++++++++++++++- 2 files changed, 341 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index b95fe896..bf2bacd0 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -70,7 +70,7 @@ class MenuBar extends React.Component { this.setState({ expandedIndex: index, }, () => { - this.itemRefs[index].current.focusLastItem(); + this.itemRefs[index].current.focusLastChild(); }); } @@ -80,7 +80,7 @@ class MenuBar extends React.Component { this.setState({ expandedIndex: index, }, () => { - this.itemRefs[index].current.focusFirstItem(); + this.itemRefs[index].current.focusFirstChild(); }); } else if(key === 'ArrowLeft' || key === 'Left') { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index f94426ba..90528872 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -53,55 +53,391 @@ class ParentMenuItem extends React.Component { const { key, target } = event; const index = Number.parseInt(target.dataset.index); const item = items[index]; + const { type } = item; console.log(index, item, items); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); + this.focusChild(index === 0 ? items.length - 1 : index - 1); + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + this.focusChild(index === items.length - 1 ? 0 : index + 1); + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + //TODO + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + //TODO + } + else if(key === 'Enter') { + event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.childItemRefs[index].current.focusFirstChild(); + }); + } + else { + //TODO: activate the item and close the (whole?) menu + } + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.childItemRefs[index].current.focusFirstChild(); + }); + } + else if(type === 'menuitemcheckbox') { + //TODO change state without closing the menu + } + else if(type === 'menuitemradio') { + //TODO change state without closing the menu + } + else if(type === 'menuitem') { + //TODO activate the item and close the (whole?) menu + } + } + else if(key === 'Home') { + event.preventDefault(); + this.focusFirstChild(); + } + else if(key === 'End') { + event.preventDefault(); + this.focusLastChild(); + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + + } + else if(key === 'Tab') { + } + else { + //TODO: Any key that corresponds to a printable character (Optional): + //Move focus to the next menu item in the current menu whose label begins + //with that printable character. + } + + /* + const { orientation } = this.props; + const { items } = this.state; + const { key, target } = event; + const role = target.getAttribute('role'); + const position = target.dataset.position.split(','); + const level = position.length - 1; + let index; + let nextIndex; + let item; + let subItems = items; //(sub-)menu that item belongs in + + //TODO this should probably be put inside the setState() calls + //so that subItems is referencing prevState rather than + //this.state + position.forEach((pos, i) => { + index = Number.parseInt(pos, 10); + item = subItems[index]; + + //Don't do this on the last iteration so we know + //the subset of items that item belongs in. Otherwise, + //subItems would be the children of item (assuming + //item is a parent menuitem). + if(i < position.length - 1) + subItems = item.children; + }); + + console.log(level, index, item, subItems); + + //TODO: + //- take into account orientation + //- consolidate logic and move into separate methods? + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + + if(level > 0) { + this.setState(prevState => { + nextIndex = index === 0 ? subItems.length - 1 : index - 1; + subItems[nextIndex].ref.current.focus(); + return prevState; + }); + } + else if(isParentMenuitem(item)) { + this.setState(prevState => { + item.isExpanded = true; + return prevState; + }, () => { + item.children[item.children.length - 1].ref.current.focus(); + }); + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + if(level > 0) { + this.setState(prevState => { + nextIndex = index === subItems.length - 1 ? 0 : index + 1; + subItems[nextIndex].ref.current.focus(); + return prevState; + }); + } + else if(isParentMenuitem(item)) { + this.setState(prevState => { + item.isExpanded = true; + return prevState; + }, () => { + item.children[0].ref.current.focus(); + }); + } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); + if(level === 0) { + //TODO Like with arrow right, also maintain + //"root expand state"? + this.setState(prevState => { + nextIndex = index === 0 ? subItems.length - 1 : index - 1; + subItems[index].isTabbable = false; + subItems[index].isExpanded = false; + subItems[nextIndex].isTabbable = true; + subItems[nextIndex].ref.current.focus(); + return prevState; + }); + } + else if(level === 1) { + this.setState(prevState => { + const pos1 = Number.parseInt(position[0], 10); + const nextIndex = pos1 === 0 ? items.length - 1 : pos1 - 1; + const nextItem = items[nextIndex]; + const parentMenuitem = items[pos1]; + + parentMenuitem.isTabbable = false; + parentMenuitem.isExpanded = false; + nextItem.isTabbable = true; + nextItem.ref.current.focus(); + + if(isParentMenuitem(nextItem)) + nextItem.isExpanded = true; + + return prevState; + }); + } + else { + this.setState(prevState => { + let _items = items; + let _item; + + position.forEach((pos, i) => { + const _pos = Number.parseInt(pos, 10); + _item = _items[_pos]; + _items = _item.children; + + if(i === position.length - 2) { + _item.isExpanded = false; + _item.ref.current.focus(); + } + }); + + return prevState; + }); + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); + if(level === 0) { + this.setState(prevState => { + //Const wasExpanded = subItems[index].isExpanded; + + nextIndex = index === subItems.length - 1 ? 0 : index + 1; + subItems[index].isTabbable = false; + subItems[index].isExpanded = false; + subItems[nextIndex].isTabbable = true; + subItems[nextIndex].ref.current.focus(); + + //This behavior isn't defined in the authoring prac + //but is exhibited in the example implementations + //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html + //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-2/menubar-2.html + //FIXME: currently broken - what if subItems[nextIndex] isn't a parent menuitem, + //but the subsequent item is? we lose the "root expanded" state. + //if(isParentMenu(subItems[nextIndex]) && wasExpanded) + // subItems[nextIndex].isExpanded = true; + + return prevState; + }); + } + else if(isParentMenuitem(item)) { + this.setState(prevState => { + item.isExpanded = true; + return prevState; + }, () => { + item.children[0].ref.current.focus(); + }); + } + else { + this.setState(prevState => { + //Close the submenu and any parent menus + let _items = items; + let _menuitem; + + position.forEach((pos, i) => { + const _pos = Number.parseInt(pos, 10); + position[i] = _pos; + _menuitem = _items[_pos]; + _items = _menuitem.children; + + if(isParentMenuitem(_menuitem)) + _menuitem.isExpanded = false; + }); + + //Move focus to the next menuitem in the menubar, + //and if it's a parent menuitem, open the submenu + //without changing focus + const index = position[0]; + const nextIndex = index === items.length - 1 ? 0 : index + 1; + const nextItem = items[nextIndex]; + const item = items[index]; + + item.isTabbable = false; + nextItem.isTabbable = true; + nextItem.ref.current.focus(); + + if(isParentMenuitem(nextItem)) + nextItem.isExpanded = true; + + return prevState; + }); + } } else if(key === 'Enter') { event.preventDefault(); + if(isParentMenuitem(item)) { + this.setState(prevState => { + item.isExpanded = true; + return prevState; + }, () => { + item.children[0].ref.current.focus(); + }); + } + else { + //TODO activate the item and close the (whole?) menu + } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + if(isParentMenuitem(item)) { + this.setState(prevState => { + item.isExpanded = true; + return prevState; + }, () => { + item.children[0].ref.current.focus(); + }); + } + else if(role === 'menuitemcheckbox') { + //TODO: change state without closing the menu + } + else if(role === 'menuitemradio') { + //TODO change state without closing the menu + } + else { + //TODO: activate the item and closes the (whole?) menu + } } else if(key === 'Home') { event.preventDefault(); + this.setState(prevState => { + nextIndex = 0; + + if(level === 0) { + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; + } + + subItems[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'End') { event.preventDefault(); + this.setState(prevState => { + nextIndex = subItems.length - 1; + + if(level === 0) { + subItems[index].isTabbable = false; + subItems[nextIndex].isTabbable = true; + } + + subItems[nextIndex].ref.current.focus(); + return prevState; + }); } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); + if(level > 0) { + this.setState(prevState => { + let _items = items; + let _item; + + position.forEach((pos, i) => { + const _pos = Number.parseInt(pos, 10); + _item = _items[_pos]; + _items = _item.children; + + if(i === position.length - 2) { + _item.isExpanded = false; + _item.ref.current.focus(); + } + }); + + return prevState; + }); + } } else if(key === 'Tab') { - + this.setState(prevState => { + let _items = items; + let _item; + + position.forEach(pos => { + const _pos = Number.parseInt(pos, 10); + _item = _items[_pos]; + _items = _item.children; + + //FIXME: broken in both directions. shift+tab goes to the + //root item rather than the element before the menubar. + //Tabbing forward seems to focus on some unknown "thing" + //rather than something on the browser (though that only + //seems to occur if the menubar is NOT the last element). + //This behavior does not show up on the WAI-ARIA example + //implementations so it's likely something we're doing. + //_item.isTabbable = i === 0; + _item.isExpanded = false; + }); + + return prevState; + }); } else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins //with that printable character. } - - + */ }; //---- Rendering ---- From 4a0794eb97ac6a8e7fe4aa54a7fb969491ce9127 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 21:55:50 -0500 Subject: [PATCH 084/286] start having the menu items be responsible for themselves? --- src/Menu/MenuBar.jsx | 7 ++--- src/Menu/ParentMenuItem.jsx | 55 ++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index bf2bacd0..814a4219 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -510,9 +510,8 @@ class MenuBar extends React.Component { key={ index } index={ index } ref={ this.itemRefs[index] } - onKeyDown={ this.onItemKeyDown } isDisabled={ isDisabled } - isTabbable={ index === tabbableIndex } + level={ 0 } > { node } @@ -525,11 +524,9 @@ class MenuBar extends React.Component { index={ index } items={ children } ref={ this.itemRefs[index] } - onKeyDown={ this.onItemKeyDown } orientation={ orientation } isDisabled={ isDisabled } - isExpanded={ index === expandedIndex } - isTabbable={ index === tabbableIndex } + level={ 0 } > { node } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 90528872..72ac4c91 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -13,11 +13,9 @@ class ParentMenuItem extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENU_ITEMS_PROPTYPE.isRequired, - onKeyDown: PropTypes.func.isRequired, index: PropTypes.number.isRequired, - isExpanded: PropTypes.bool, + level: PropTypes.number.isRequired, isDisabled: PropTypes.bool, - isTabbable: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -25,9 +23,7 @@ class ParentMenuItem extends React.Component { }; static defaultProps = { - isExpanded: false, isDisabled: false, - isTabbable: false, orientation: 'horizontal', renderItem, renderMenuItem, @@ -37,10 +33,11 @@ class ParentMenuItem extends React.Component { constructor(props) { super(props); - const { items } = props; + const { items, level, index } = props; this.state = { - expandedIndex: undefined, + isExpanded: false, + isTabbable: level === 0 && index === 0, }; this.itemRef = React.createRef(); @@ -48,26 +45,33 @@ class ParentMenuItem extends React.Component { } //---- Events ---- - onItemKeyDown = (event) => { - const { orientation, items } = this.props; + onKeyDown = (event) => { + const { orientation, items, level } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index); const item = items[index]; const { type } = item; - console.log(index, item, items); + console.log(index, item, items, level); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - this.focusChild(index === 0 ? items.length - 1 : index - 1); + + if(level > 0) { + } + else { + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - this.focusChild(index === items.length - 1 ? 0 : index + 1); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - //TODO + + if(level === 0) { + } + else { + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); @@ -76,16 +80,6 @@ class ParentMenuItem extends React.Component { else if(key === 'Enter') { event.preventDefault(); - if(type === 'parentmenuitem') { - this.setState({ - expandedIndex: index, - }, () => { - this.childItemRefs[index].current.focusFirstChild(); - }); - } - else { - //TODO: activate the item and close the (whole?) menu - } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); @@ -442,10 +436,8 @@ class ParentMenuItem extends React.Component { //---- Rendering ---- render() { - const { - children, items, isExpanded, isDisabled, isTabbable, - orientation, renderItem, onKeyDown, index - } = this.props; + const { children, items, isDisabled, orientation, renderItem, index } = this.props; + const { isExpanded, isTabbable } = this.state; const itemNodes = items.map(this.renderItem); return ( @@ -458,7 +450,7 @@ class ParentMenuItem extends React.Component { aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } ref={ this.itemRef } - onKeyDown={ onKeyDown } + onKeyDown={ this.onKeyDown } data-index={ index } > { children } @@ -471,8 +463,8 @@ class ParentMenuItem extends React.Component { } renderItem = (item, index, items) => { + const { level } = this.props; const { node, type, isDisabled, children, orientation } = item; - const { expandedIndex } = this.state; if(type === 'menuitem') { return ( @@ -480,8 +472,8 @@ class ParentMenuItem extends React.Component { key={ index } index={ index } ref={ this.childItemRefs[index] } - onKeyDown={ this.onItemKeyDown } isDisabled={ isDisabled } + level={ level + 1 } > { node } @@ -495,9 +487,8 @@ class ParentMenuItem extends React.Component { items={ children } orientation={ orientation } ref={ this.childItemRefs[index] } - onKeyDown={ this.onItemKeyDown } - isExpanded={ index === expandedIndex } isDisabled={ isDisabled } + level={ level + 1 } > { node } From f7df132ed20f0789b00cd26eefb08ec2e8631819 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 22:23:41 -0500 Subject: [PATCH 085/286] start having menuitem be responsible for its own events --- src/Menu/MenuItem.jsx | 116 ++++++++++++++++++++++++++++++++++++ src/Menu/ParentMenuItem.jsx | 5 ++ 2 files changed, 121 insertions(+) diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index ed4b7131..ee2dd806 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -2,6 +2,121 @@ import React from 'react'; import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? +class MenuItem extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + index: PropTypes.number.isRequired, + level: PropTypes.number.isRequired, + isDisabled: PropTypes.bool, + }; + + static defaultProps = { + isDisabled: false, + }; + + constructor(props) { + super(props); + + const { index, level } = props; + + this.state = { + isTabbable: index === 0 && level === 0, + }; + } + + //---- Events ---- + onKeyDown = (event) => { + const { level } = this.props; + const { key, target } = event; + const index = Number.parseInt(target.dataset.index); + + console.log(index, level); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + + if(level > 0) { + } + else { + } + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + + if(level === 0) { + } + else { + } + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + //TODO + } + else if(key === 'Enter') { + event.preventDefault(); + + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + + if(type === 'parentmenuitem') { + } + else if(type === 'menuitemcheckbox') { + //TODO change state without closing the menu + } + else if(type === 'menuitemradio') { + //TODO change state without closing the menu + } + else if(type === 'menuitem') { + //TODO activate the item and close the (whole?) menu + } + } + else if(key === 'Home') { + event.preventDefault(); + } + else if(key === 'End') { + event.preventDefault(); + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + + } + else if(key === 'Tab') { + + } + else { + //TODO: Any key that corresponds to a printable character (Optional): + //Move focus to the next menu item in the current menu whose label begins + //with that printable character. + } + + }; + + //---- Rendering ---- + render() { + const { children, isDisabled, index } = this.props; + const { isTabbable } = this.state; + + return ( +
                                    • + { children } +
                                    • + ); + } +} + +export default MenuItem; + +/* const MenuItem = React.forwardRef(function MenuItem(props, ref) { const { children, isDisabled, isTabbable, onKeyDown, index } = props; @@ -33,3 +148,4 @@ MenuItem.defaultProps = { }; export default MenuItem; +*/ diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 72ac4c91..6445e83e 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -60,6 +60,11 @@ class ParentMenuItem extends React.Component { if(level > 0) { } else { + this.setState({ + isExpanded: true, + }, () => { + this.childItemRefs[items.length - 1].current.focus(); + }); } } else if(key === 'ArrowDown' || key === 'Down') { From e2d1ac554954ae0bfbc52194aaf455c246594275 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 22:39:39 -0500 Subject: [PATCH 086/286] if the menu items are responsible for their own event handling, then orientation cant be accounted for at the root level --- src/Menu/MenuBar.jsx | 7 ++- src/Menu/MenuItem.jsx | 116 ------------------------------------ src/Menu/ParentMenuItem.jsx | 56 +++++++++++------ 3 files changed, 45 insertions(+), 134 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 814a4219..ff26eaf8 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -55,7 +55,7 @@ class MenuBar extends React.Component { } //---- Events ---- - onItemKeyDown = (event) => { + onChildKeyDown = (event) => { const { orientation, items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index); @@ -511,7 +511,9 @@ class MenuBar extends React.Component { index={ index } ref={ this.itemRefs[index] } isDisabled={ isDisabled } + isTabbable={ index === tabbableIndex } level={ 0 } + onKeyDown={ this.onChildKeyDown } > { node }
                                      @@ -526,7 +528,10 @@ class MenuBar extends React.Component { ref={ this.itemRefs[index] } orientation={ orientation } isDisabled={ isDisabled } + isExpanded={ index === expandedIndex } + isTabbable={ index === tabbableIndex } level={ 0 } + onKeyDown={ this.onChildKeyDown } > { node } diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index ee2dd806..ed4b7131 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -2,121 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? -class MenuItem extends React.Component { - static propTypes = { - children: PropTypes.node.isRequired, - index: PropTypes.number.isRequired, - level: PropTypes.number.isRequired, - isDisabled: PropTypes.bool, - }; - - static defaultProps = { - isDisabled: false, - }; - - constructor(props) { - super(props); - - const { index, level } = props; - - this.state = { - isTabbable: index === 0 && level === 0, - }; - } - - //---- Events ---- - onKeyDown = (event) => { - const { level } = this.props; - const { key, target } = event; - const index = Number.parseInt(target.dataset.index); - - console.log(index, level); - - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - - if(level > 0) { - } - else { - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - - if(level === 0) { - } - else { - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - //TODO - } - else if(key === 'Enter') { - event.preventDefault(); - - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - - if(type === 'parentmenuitem') { - } - else if(type === 'menuitemcheckbox') { - //TODO change state without closing the menu - } - else if(type === 'menuitemradio') { - //TODO change state without closing the menu - } - else if(type === 'menuitem') { - //TODO activate the item and close the (whole?) menu - } - } - else if(key === 'Home') { - event.preventDefault(); - } - else if(key === 'End') { - event.preventDefault(); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - - } - else if(key === 'Tab') { - - } - else { - //TODO: Any key that corresponds to a printable character (Optional): - //Move focus to the next menu item in the current menu whose label begins - //with that printable character. - } - - }; - - //---- Rendering ---- - render() { - const { children, isDisabled, index } = this.props; - const { isTabbable } = this.state; - - return ( -
                                    • - { children } -
                                    • - ); - } -} - -export default MenuItem; - -/* const MenuItem = React.forwardRef(function MenuItem(props, ref) { const { children, isDisabled, isTabbable, onKeyDown, index } = props; @@ -148,4 +33,3 @@ MenuItem.defaultProps = { }; export default MenuItem; -*/ diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 6445e83e..a953b6af 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -15,7 +15,10 @@ class ParentMenuItem extends React.Component { items: MENU_ITEMS_PROPTYPE.isRequired, index: PropTypes.number.isRequired, level: PropTypes.number.isRequired, + onKeyDown: PropTypes.func.isRequired, + isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, + isTabbable: PropTypes.bool, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), renderItem: PropTypes.func, renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types @@ -23,7 +26,9 @@ class ParentMenuItem extends React.Component { }; static defaultProps = { + isExpanded: false, isDisabled: false, + isTabbable: false, orientation: 'horizontal', renderItem, renderMenuItem, @@ -33,11 +38,10 @@ class ParentMenuItem extends React.Component { constructor(props) { super(props); - const { items, level, index } = props; + const { items } = props; this.state = { - isExpanded: false, - isTabbable: level === 0 && index === 0, + expandedIndex: undefined, }; this.itemRef = React.createRef(); @@ -45,7 +49,7 @@ class ParentMenuItem extends React.Component { } //---- Events ---- - onKeyDown = (event) => { + onChildKeyDown = (event) => { const { orientation, items, level } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index); @@ -56,19 +60,11 @@ class ParentMenuItem extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - - if(level > 0) { - } - else { - this.setState({ - isExpanded: true, - }, () => { - this.childItemRefs[items.length - 1].current.focus(); - }); - } + this.focusChild(index === 0 ? items.length - 1 : index - 1); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + this.focusChild(index === items.length - 1 ? 0 : index + 1); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); @@ -85,6 +81,16 @@ class ParentMenuItem extends React.Component { else if(key === 'Enter') { event.preventDefault(); + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.childItemRefs[index].current.focusFirstChild(); + }); + } + else { + //TODO: activate the item and close the (whole?) menu + } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); @@ -117,6 +123,7 @@ class ParentMenuItem extends React.Component { else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); + collapseParent(); } else if(key === 'Tab') { @@ -441,8 +448,10 @@ class ParentMenuItem extends React.Component { //---- Rendering ---- render() { - const { children, items, isDisabled, orientation, renderItem, index } = this.props; - const { isExpanded, isTabbable } = this.state; + const { + children, items, isExpanded, isDisabled, isTabbable, + orientation, renderItem, index, onKeyDown + } = this.props; const itemNodes = items.map(this.renderItem); return ( @@ -455,7 +464,7 @@ class ParentMenuItem extends React.Component { aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } ref={ this.itemRef } - onKeyDown={ this.onKeyDown } + onKeyDown={ onKeyDown } data-index={ index } > { children } @@ -470,6 +479,7 @@ class ParentMenuItem extends React.Component { renderItem = (item, index, items) => { const { level } = this.props; const { node, type, isDisabled, children, orientation } = item; + const { expandedIndex } = this.state; if(type === 'menuitem') { return ( @@ -479,6 +489,7 @@ class ParentMenuItem extends React.Component { ref={ this.childItemRefs[index] } isDisabled={ isDisabled } level={ level + 1 } + onKeyDown={ this.onChildKeyDown } > { node } @@ -492,8 +503,11 @@ class ParentMenuItem extends React.Component { items={ children } orientation={ orientation } ref={ this.childItemRefs[index] } + isExpanded={ index === expandedIndex } isDisabled={ isDisabled } + collapseParent={ this.collapseParent } level={ level + 1 } + onKeyDown={ this.onChildKeyDown } > { node } @@ -518,6 +532,14 @@ class ParentMenuItem extends React.Component { const { items } = this.props; this.focusChild(items.length - 1); }; + + collapseParent = () => { + this.setState({ + expandedIndex: undefined, + }, () => { + this.focus(); + }); + }; } export default ParentMenuItem; From 25768a776a2c11f8455eb28a4a2ea7c83fd38645 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 22:54:04 -0500 Subject: [PATCH 087/286] whole bunch of cleanup --- src/Menu/MenuBar.jsx | 109 +++++------------------------------- src/Menu/MenuItem.jsx | 10 ++-- src/Menu/ParentMenuItem.jsx | 108 ++++++----------------------------- 3 files changed, 35 insertions(+), 192 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index ff26eaf8..e7774487 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -7,7 +7,6 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; -import { renderItem, renderMenuItem, renderParentMenuItem, isParentMenuitem } from 'src/Menu/utils'; /* * Some notes on props: @@ -18,22 +17,16 @@ import { renderItem, renderMenuItem, renderParentMenuItem, isParentMenuitem } fr */ class MenuBar extends React.Component { static propTypes = { - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), items: MENU_ITEMS_PROPTYPE.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, - renderItem: PropTypes.func, - renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types - renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types }; static defaultProps = { orientation: 'horizontal', label: undefined, labelId: undefined, - renderItem, - renderMenuItem, - renderParentMenuItem, }; constructor(props) { @@ -46,7 +39,6 @@ class MenuBar extends React.Component { //- what if someone passes new props (e.g. change isDisabled for an item)? //- are we updating state properly? deep copy/nested weirdness? this.state = { - items: this.initializeItems(items), tabbableIndex: 0, expandedIndex: undefined, }; @@ -56,112 +48,37 @@ class MenuBar extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { orientation, items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index); - const item = items[index]; - const { type } = item; + const level = Number.parseInt(target.dataset.level); - console.log(index, item, items); + console.log(index, level); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - - this.setState({ - expandedIndex: index, - }, () => { - this.itemRefs[index].current.focusLastChild(); - }); - } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - - this.setState({ - expandedIndex: index, - }, () => { - this.itemRefs[index].current.focusFirstChild(); - }); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - const newIndex = index === 0 ? items.length - 1 : index - 1; - - this.setState({ - tabbableIndex: newIndex, - expandedIndex: undefined, //TODO: wai aria implementation maintains this if previously expanded - }, () => { - this.itemRefs[newIndex].current.focus(); - }); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - const newIndex = index === items.length - 1 ? 0 : index + 1; - - this.setState({ - tabbableIndex: newIndex, - expandedIndex: undefined, //TODO: wai aria implementation maintains this if previously expanded - }, () => { - this.itemRefs[newIndex].current.focus(); - }); - } else if(key === 'Enter') { event.preventDefault(); - - if(type === 'parentmenuitem') { - this.setState({ - expandedIndex: index, - }, () => { - this.itemRefs[index].current.focusFirstChild(); - }); - } - else { - //TODO: activate the item and close the (whole?) menu - } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); - - if(type === 'parentmenuitem') { - this.setState({ - expandedIndex: index, - }, () => { - this.itemRefs[index].current.focusFirstChild(); - }); - } - else if(role === 'menuitemcheckbox') { - //TODO: change state without closing the menu - } - else if(role === 'menuitemradio') { - //TODO: change state without closing the menu - } - else if(role === 'menuitem') { - //TODO: activate the item and close the (whole?) menu - } } else if(key === 'Home') { event.preventDefault(); - - this.setState({ - tabbableIndex: 0, - expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this if previous item was expanded - }, () => { - this.itemRefs[0].current.focus(); - }); } else if(key === 'End') { event.preventDefault(); - - this.setState({ - tabbableIndex: items.length - 1, - expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this if previous item was expanded - }, () => { - this.itemRefs[items.length - 1].current.focus(); - }); } else if(key === 'Tab') { - //TODO } else { //TODO: Any key that corresponds to a printable character (Optional): @@ -483,8 +400,8 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { - const { orientation, label, labelId, renderItem, items } = this.props; - const itemNodes = items.map(this.renderItems); + const { items, orientation, label, labelId } = this.props; + const itemNodes = items.map(this.renderItem); console.log(this.props, this.state); @@ -500,7 +417,7 @@ class MenuBar extends React.Component { ); } - renderItems = (item, index, items) => { + renderItem = (item, index, items) => { const { tabbableIndex, expandedIndex } = this.state const { type, node, children, isDisabled, orientation } = item; @@ -509,11 +426,11 @@ class MenuBar extends React.Component { { node } @@ -523,15 +440,15 @@ class MenuBar extends React.Component { return ( { node } diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index ed4b7131..17bbfa6e 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -3,16 +3,17 @@ import PropTypes from 'prop-types'; //TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, isDisabled, isTabbable, onKeyDown, index } = props; + const { children, index, level, onKeyDown, isDisabled, isTabbable } = props; return (
                                    • { children }
                                    • @@ -21,8 +22,9 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { MenuItem.propTypes = { children: PropTypes.node.isRequired, - onKeyDown: PropTypes.func.isRequired, index: PropTypes.number.isRequired, + level: PropTypes.number.isRequired, + onKeyDown: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index a953b6af..697d91df 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -7,7 +7,6 @@ import MenuItem from 'src/Menu/MenuItem'; //Misc. import { MENU_ITEMS_PROPTYPE, REF_PROPTYPE } from 'src/utils/propTypes'; -import { renderItem, renderMenuItem, renderParentMenuItem } from 'src/Menu/utils'; class ParentMenuItem extends React.Component { static propTypes = { @@ -16,23 +15,17 @@ class ParentMenuItem extends React.Component { index: PropTypes.number.isRequired, level: PropTypes.number.isRequired, onKeyDown: PropTypes.func.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - renderItem: PropTypes.func, - renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types - renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types }; static defaultProps = { + orientation: 'horizontal', isExpanded: false, isDisabled: false, isTabbable: false, - orientation: 'horizontal', - renderItem, - renderMenuItem, - renderParentMenuItem, }; constructor(props) { @@ -50,83 +43,40 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { orientation, items, level } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index); - const item = items[index]; - const { type } = item; + const level = Number.parseInt(target.dataset.level); - console.log(index, item, items, level); + console.log(index, level); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - this.focusChild(index === 0 ? items.length - 1 : index - 1); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - this.focusChild(index === items.length - 1 ? 0 : index + 1); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - - if(level === 0) { - } - else { - } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - //TODO } else if(key === 'Enter') { event.preventDefault(); - - if(type === 'parentmenuitem') { - this.setState({ - expandedIndex: index, - }, () => { - this.childItemRefs[index].current.focusFirstChild(); - }); - } - else { - //TODO: activate the item and close the (whole?) menu - } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); - - if(type === 'parentmenuitem') { - this.setState({ - expandedIndex: index, - }, () => { - this.childItemRefs[index].current.focusFirstChild(); - }); - } - else if(type === 'menuitemcheckbox') { - //TODO change state without closing the menu - } - else if(type === 'menuitemradio') { - //TODO change state without closing the menu - } - else if(type === 'menuitem') { - //TODO activate the item and close the (whole?) menu - } } else if(key === 'Home') { event.preventDefault(); - this.focusFirstChild(); } else if(key === 'End') { event.preventDefault(); - this.focusLastChild(); } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); - - collapseParent(); } else if(key === 'Tab') { - } else { //TODO: Any key that corresponds to a printable character (Optional): @@ -449,8 +399,8 @@ class ParentMenuItem extends React.Component { //---- Rendering ---- render() { const { - children, items, isExpanded, isDisabled, isTabbable, - orientation, renderItem, index, onKeyDown + children, items, index, level, onKeyDown, + orientation, isExpanded, isDisabled, isTabbable, } = this.props; const itemNodes = items.map(this.renderItem); @@ -460,12 +410,13 @@ class ParentMenuItem extends React.Component { href="#" role="menuitem" aria-haspopup="menu" + data-index={ index } + data-level={ level } + onKeyDown={ onKeyDown } aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } ref={ this.itemRef } - onKeyDown={ onKeyDown } - data-index={ index } > { children } @@ -478,7 +429,7 @@ class ParentMenuItem extends React.Component { renderItem = (item, index, items) => { const { level } = this.props; - const { node, type, isDisabled, children, orientation } = item; + const { node, type, children, orientation, isDisabled } = item; const { expandedIndex } = this.state; if(type === 'menuitem') { @@ -486,10 +437,10 @@ class ParentMenuItem extends React.Component { { node } @@ -499,47 +450,20 @@ class ParentMenuItem extends React.Component { return ( { node } ); } }; - - //---- Events ---- - focus = () => { - this.itemRef.current.focus(); - }; - - focusChild = (index) => { - this.childItemRefs[index].current.focus(); - }; - - focusFirstChild = () => { - this.focusChild(0); - }; - - focusLastChild = () => { - const { items } = this.props; - this.focusChild(items.length - 1); - }; - - collapseParent = () => { - this.setState({ - expandedIndex: undefined, - }, () => { - this.focus(); - }); - }; } export default ParentMenuItem; From 8060973bb261e8cc7f1190fce0e4b60e67d24018 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 22:55:41 -0500 Subject: [PATCH 088/286] resolve some linter complaints --- src/Menu/MenuBar.jsx | 6 +++--- src/Menu/ParentMenuItem.jsx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index e7774487..48956ca0 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -49,8 +49,8 @@ class MenuBar extends React.Component { //---- Events ---- onChildKeyDown = (event) => { const { key, target } = event; - const index = Number.parseInt(target.dataset.index); - const level = Number.parseInt(target.dataset.level); + const index = Number.parseInt(target.dataset.index, 10); + const level = Number.parseInt(target.dataset.level, 10); console.log(index, level); @@ -417,7 +417,7 @@ class MenuBar extends React.Component { ); } - renderItem = (item, index, items) => { + renderItem = (item, index) => { const { tabbableIndex, expandedIndex } = this.state const { type, node, children, isDisabled, orientation } = item; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 697d91df..8a1b3503 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -6,7 +6,7 @@ import Menu from 'src/Menu/Menu'; import MenuItem from 'src/Menu/MenuItem'; //Misc. -import { MENU_ITEMS_PROPTYPE, REF_PROPTYPE } from 'src/utils/propTypes'; +import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; class ParentMenuItem extends React.Component { static propTypes = { @@ -44,8 +44,8 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { const { key, target } = event; - const index = Number.parseInt(target.dataset.index); - const level = Number.parseInt(target.dataset.level); + const index = Number.parseInt(target.dataset.index, 10); + const level = Number.parseInt(target.dataset.level, 10); console.log(index, level); @@ -427,7 +427,7 @@ class ParentMenuItem extends React.Component { ); } - renderItem = (item, index, items) => { + renderItem = (item, index) => { const { level } = this.props; const { node, type, children, orientation, isDisabled } = item; const { expandedIndex } = this.state; From 58f7d4ce130c75edd3bb407adb1fee2cf484bf34 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 23:03:17 -0500 Subject: [PATCH 089/286] menubar up handling --- src/Menu/MenuBar.jsx | 14 ++++++++++++-- src/Menu/ParentMenuItem.jsx | 10 ++++++++++ src/utils/propTypes.js | 4 ---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 48956ca0..5bf2b8be 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -48,14 +48,24 @@ class MenuBar extends React.Component { //---- Events ---- onChildKeyDown = (event) => { + const { items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); - const level = Number.parseInt(target.dataset.level, 10); + const item = items[index]; + const { type } = item; - console.log(index, level); + console.log(index, item); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.itemRefs[index].current.focusLastChild(); + }); + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8a1b3503..7af99f65 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -464,6 +464,16 @@ class ParentMenuItem extends React.Component { ); } }; + + //---- Misc. --- + focusChild = (index) => { + this.childItemRefs[index].current.focus(); + }; + + focusLastChild = () => { + const { items } = this.props; + this.focusChild(items.length - 1); + }; } export default ParentMenuItem; diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index f9a4181f..ab7d50df 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -67,7 +67,3 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ }); export const MENU_ITEMS_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_PROPTYPE); - -export const REF_PROPTYPE = PropTypes.shape({ - current: PropTypes.object, -}); From b8d8e593f6596e370427d1db8419ef2f37eae5e1 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 23:04:43 -0500 Subject: [PATCH 090/286] menubar down handling --- src/Menu/MenuBar.jsx | 8 ++++++++ src/Menu/ParentMenuItem.jsx | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 5bf2b8be..64e22021 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -69,6 +69,14 @@ class MenuBar extends React.Component { } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.itemRefs[index].current.focusFirstChild(); + }); + } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 7af99f65..d22405e4 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -470,6 +470,10 @@ class ParentMenuItem extends React.Component { this.childItemRefs[index].current.focus(); }; + focusFirstChild = () => { + this.focusChild(0); + }; + focusLastChild = () => { const { items } = this.props; this.focusChild(items.length - 1); From bd1db87076cfe5dbde14499f04a30c15eba979ac Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 23:07:49 -0500 Subject: [PATCH 091/286] left/right handling at the menubar level --- src/Menu/MenuBar.jsx | 16 ++++++++++++++++ src/Menu/ParentMenuItem.jsx | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 64e22021..35ceded5 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -80,9 +80,25 @@ class MenuBar extends React.Component { } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); + const nextIndex = index === 0 ? items.length - 1 : index - 1; + + this.setState({ + tabbableIndex: nextIndex, + expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this + }, () => { + this.itemRefs[nextIndex].current.focus(); + }); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); + const nextIndex = index === items.length - 1 ? 0 : index + 1; + + this.setState({ + tabbableIndex: nextIndex, + expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this + }, () => { + this.itemRefs[nextIndex].current.focus(); + }); } else if(key === 'Enter') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index d22405e4..86019e6b 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -466,6 +466,10 @@ class ParentMenuItem extends React.Component { }; //---- Misc. --- + focus = () => { + this.itemRef.current.focus(); + }; + focusChild = (index) => { this.childItemRefs[index].current.focus(); }; From 0fd8bfabfe92505e9470a5d32412b86dbc8b2b08 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 23:13:16 -0500 Subject: [PATCH 092/286] enter and spacebar for the menubar level --- src/Menu/MenuBar.jsx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 35ceded5..c60c75be 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -102,9 +102,37 @@ class MenuBar extends React.Component { } else if(key === 'Enter') { event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.itemRefs[index].current.focusFirstChild(); + }); + } + else { + //TODO activate the item and close the (whole?) menu + } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.itemRefs[index].current.focusFirstChild(); + }); + } + else if(type === 'menuitemcheckbox') { + //TODO change state without closing the menu + } + else if(type === 'menuitemradio') { + //TODO change state without closing the menu + } + else if(type === 'menuitem') { + //TODO activate the item and close the (whole?) menu + } } else if(key === 'Home') { event.preventDefault(); From bd4f143124f613c14160fc59bbddef751f732161 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 4 Jan 2022 23:15:27 -0500 Subject: [PATCH 093/286] home and end implementation at the menubar level --- src/Menu/MenuBar.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index c60c75be..4c9f4f93 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -136,11 +136,26 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); + + this.setState({ + tabbableIndex: 0, + expandedIndex: undefined, //TODO: WAI-ARIA implementation maintains this + }, () => { + this.itemRefs[0].current.focus(); + }); } else if(key === 'End') { event.preventDefault(); + + this.setState({ + tabbableIndex: items.length - 1, + expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this + }, () => { + this.itemRefs[items.length - 1].current.focus(); + }); } else if(key === 'Tab') { + //TODO } else { //TODO: Any key that corresponds to a printable character (Optional): From 1598b5ea41300bcdee3ff34b0f49dc58e7a747b6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 11:16:58 -0500 Subject: [PATCH 094/286] up and down handling for parentmenuitem --- src/Menu/MenuBar.jsx | 3 +++ src/Menu/ParentMenuItem.jsx | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 4c9f4f93..d752769e 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -48,6 +48,9 @@ class MenuBar extends React.Component { //---- Events ---- onChildKeyDown = (event) => { + //TODO + //- orientation + //- abstract away setState calls? const { items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 86019e6b..74169787 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -43,6 +43,7 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { + const { items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); const level = Number.parseInt(target.dataset.level, 10); @@ -51,9 +52,13 @@ class ParentMenuItem extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); + const newIndex = index === 0 ? items.length - 1 : index - 1; + this.focusChild(newIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + const newIndex = index === items.length - 1 ? 0 : index + 1; + this.focusChild(newIndex); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); From 90b762e46f5f9d03928dd382f910a865b49d1ffb Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 12:16:35 -0500 Subject: [PATCH 095/286] implement enter and some of spacebar for parentmenuitem --- src/Menu/ParentMenuItem.jsx | 48 ++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 74169787..dd4d397b 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -15,6 +15,7 @@ class ParentMenuItem extends React.Component { index: PropTypes.number.isRequired, level: PropTypes.number.isRequired, onKeyDown: PropTypes.func.isRequired, + collapseParent: PropTypes.func.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, @@ -43,10 +44,12 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items } = this.props; + const { items, collapseParent } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); const level = Number.parseInt(target.dataset.level, 10); + const item = items[index]; + const { type } = item; console.log(index, level); @@ -62,15 +65,50 @@ class ParentMenuItem extends React.Component { } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); + + if(level === 1) { + + } + else { + //collapseParent(); + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); } else if(key === 'Enter') { event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.childItemRefs[index].current.focusFirstChild(); + }); + } + else { + //TODO activate the item and close the (whole?) menu + } } else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.childItemRefs[index].current.focusFirstChild(); + }); + } + else if(type === 'menuitemchecbox') { + //TODO change state without closing the menu + } + else if(type === 'menuitemradio') { + //TODO change state without closing the menu + } + else if(type === 'menuitem') { + //TODO activate the item and close the whole menu + } } else if(key === 'Home') { event.preventDefault(); @@ -444,6 +482,7 @@ class ParentMenuItem extends React.Component { index={ index } level={ level + 1 } onKeyDown={ this.onChildKeyDown } + collapseParent={ this.collapseMenu } isDisabled={ isDisabled } ref={ this.childItemRefs[index] } > @@ -459,6 +498,7 @@ class ParentMenuItem extends React.Component { index={ index } level={ level + 1 } onKeyDown={ this.onChildKeyDown } + collapseParent={ this.collapseMenu } orientation={ orientation } isExpanded={ index === expandedIndex } isDisabled={ isDisabled } @@ -487,6 +527,12 @@ class ParentMenuItem extends React.Component { const { items } = this.props; this.focusChild(items.length - 1); }; + + collapseMenu = () => { + this.setState({ + expandedIndex: undefined, + }); + }; } export default ParentMenuItem; From e01d1c3d7fdc43503577494aa71a777c9458e9dc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 12:46:46 -0500 Subject: [PATCH 096/286] arrow left handling for parent menu item --- src/Menu/MenuBar.jsx | 25 +++++++++++++++++++++++++ src/Menu/ParentMenuItem.jsx | 21 +++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d752769e..f17e668c 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -508,6 +508,7 @@ class MenuBar extends React.Component { index={ index } level={ 0 } onKeyDown={ this.onChildKeyDown } + collapseParent={ this.collapseMenu } isDisabled={ isDisabled } isTabbable={ index === tabbableIndex } ref={ this.itemRefs[index] } @@ -524,6 +525,8 @@ class MenuBar extends React.Component { index={ index } level={ 0 } onKeyDown={ this.onChildKeyDown } + collapseParent={ this.collapseMenu } + focusPrevSibling={ this.focusPrevSibling } orientation={ orientation } isDisabled={ isDisabled } isExpanded={ index === expandedIndex } @@ -559,6 +562,28 @@ class MenuBar extends React.Component { return _items; }; + + collapseMenu = (callback) => { + console.log('in menubar'); + this.setState({ + expandedIndex: undefined, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + + focusPrevSibling = (index, autoExpand) => { + const { items } = this.props; + const newIndex = index === 0 ? items.length - 1 : index - 1; + + this.setState({ + tabbableIndex: newIndex, + expandedIndex: autoExpand ? newIndex : undefined, + }, () => { + this.itemRefs[newIndex].current.focus(); + }); + }; } export default MenuBar; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index dd4d397b..cae7278d 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -44,7 +44,7 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, collapseParent } = this.props; + const { items, collapseParent, focusPrevSibling } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); const level = Number.parseInt(target.dataset.level, 10); @@ -67,10 +67,19 @@ class ParentMenuItem extends React.Component { event.preventDefault(); if(level === 1) { - + //TODO: naming is just all wrong... + //we're collapsing the parent of the menuitem executing this + //event, but we're not focusing the previous sibling of the + //menuitem executing this event. we're focusing that menuitem's + //parent's previous sibling + collapseParent(() => { + focusPrevSibling(this.props.index, true); + }); } else { - //collapseParent(); + collapseParent(() => { + this.focus(); + }); } } else if(key === 'ArrowRight' || key === 'Right') { @@ -528,9 +537,13 @@ class ParentMenuItem extends React.Component { this.focusChild(items.length - 1); }; - collapseMenu = () => { + collapseMenu = (callback) => { + console.log('in parentmenuitem', this.props.index, this.props.level); this.setState({ expandedIndex: undefined, + }, () => { + if(typeof callback === 'function') + callback(); }); }; } From c2e05a83a97d14cfadafa425eeb507ae272ea89c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 13:11:43 -0500 Subject: [PATCH 097/286] partial implementation of the arrow right for parentmenuitem --- src/Menu/ParentMenuItem.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index cae7278d..dc0b4b5e 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -84,6 +84,17 @@ class ParentMenuItem extends React.Component { } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); + + if(type === 'parentmenuitem') { + this.setState({ + expandedIndex: index, + }, () => { + this.childItemRefs[index].current.focusFirstChild(); + }); + } + else { + + } } else if(key === 'Enter') { event.preventDefault(); From 964f9af9ca79c601737020a058aca3cf85624e57 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 13:34:53 -0500 Subject: [PATCH 098/286] bad implementation of arrow right for parentmenuitem, implement home and end --- src/Menu/MenuBar.jsx | 20 +++++++++++++++++++- src/Menu/ParentMenuItem.jsx | 29 ++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f17e668c..efbfeebb 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -527,6 +527,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } focusPrevSibling={ this.focusPrevSibling } + focusNextMenubarItem={ this.focusNextSibling } orientation={ orientation } isDisabled={ isDisabled } isExpanded={ index === expandedIndex } @@ -563,7 +564,7 @@ class MenuBar extends React.Component { return _items; }; - collapseMenu = (callback) => { + collapseMenu = (collapseAll, callback) => { console.log('in menubar'); this.setState({ expandedIndex: undefined, @@ -584,6 +585,23 @@ class MenuBar extends React.Component { this.itemRefs[newIndex].current.focus(); }); }; + + //TODO not very flexible, assuming the current index is + //what is currently expanded... + focusNextSibling = () => { + const { items } = this.props; + const { expandedIndex } = this.state; + const newIndex = expandedIndex === items.length - 1 ? 0 : expandedIndex + 1; + + console.log(expandedIndex, newIndex); + + this.setState({ + tabbableIndex: newIndex, + expandedIndex: newIndex, + }, () => { + this.itemRefs[newIndex].current.focus(); + }); + }; } export default MenuBar; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index dc0b4b5e..b1d0e1a5 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -16,6 +16,8 @@ class ParentMenuItem extends React.Component { level: PropTypes.number.isRequired, onKeyDown: PropTypes.func.isRequired, collapseParent: PropTypes.func.isRequired, + focusPrevSibling: PropTypes.func.isRequired, + focusNextMenubarItem: PropTypes.func.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, @@ -44,7 +46,7 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, collapseParent, focusPrevSibling } = this.props; + const { items, collapseParent, focusPrevSibling, focusNextMenubarItem } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); const level = Number.parseInt(target.dataset.level, 10); @@ -72,12 +74,12 @@ class ParentMenuItem extends React.Component { //event, but we're not focusing the previous sibling of the //menuitem executing this event. we're focusing that menuitem's //parent's previous sibling - collapseParent(() => { + collapseParent(false, () => { focusPrevSibling(this.props.index, true); }); } else { - collapseParent(() => { + collapseParent(false, () => { this.focus(); }); } @@ -93,7 +95,8 @@ class ParentMenuItem extends React.Component { }); } else { - + focusNextMenubarItem(); + collapseParent(true); } } else if(key === 'Enter') { @@ -132,9 +135,11 @@ class ParentMenuItem extends React.Component { } else if(key === 'Home') { event.preventDefault(); + this.focusFirstChild(); } else if(key === 'End') { event.preventDefault(); + this.focusLastChild(); } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); @@ -467,6 +472,8 @@ class ParentMenuItem extends React.Component { } = this.props; const itemNodes = items.map(this.renderItem); + console.log(this.props, this.state); + return (
                                    • { - const { level } = this.props; + const { level, focusNextMenubarItem } = this.props; const { node, type, children, orientation, isDisabled } = item; const { expandedIndex } = this.state; @@ -519,6 +526,7 @@ class ParentMenuItem extends React.Component { level={ level + 1 } onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } + focusNextMenubarItem={ focusNextMenubarItem } orientation={ orientation } isExpanded={ index === expandedIndex } isDisabled={ isDisabled } @@ -548,12 +556,19 @@ class ParentMenuItem extends React.Component { this.focusChild(items.length - 1); }; - collapseMenu = (callback) => { + collapseMenu = (collapseAll, callback) => { + const { collapseParent } = this.props; + console.log('in parentmenuitem', this.props.index, this.props.level); + this.setState({ expandedIndex: undefined, }, () => { - if(typeof callback === 'function') + // if(typeof callback === 'function') + // callback(); + if(collapseAll) + collapseParent(true, callback); + else if(typeof callback === 'function') callback(); }); }; From 7976491194c1b705a4a3f8850ada2ba408f91b85 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 13:43:28 -0500 Subject: [PATCH 099/286] esc implementation --- src/Menu/ParentMenuItem.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index b1d0e1a5..4d6848e3 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -143,6 +143,10 @@ class ParentMenuItem extends React.Component { } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); + + collapseParent(false, () => { + this.focus(); + }); } else if(key === 'Tab') { } From 1d4cc2946a01fbe72500838b02627ce4705fb69f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 14:19:18 -0500 Subject: [PATCH 100/286] first attempt at tab implementation --- src/Menu/MenuBar.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index efbfeebb..cccd457f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -158,7 +158,7 @@ class MenuBar extends React.Component { }); } else if(key === 'Tab') { - //TODO + this.collapseMenu(); } else { //TODO: Any key that corresponds to a printable character (Optional): diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 4d6848e3..a958664c 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -149,6 +149,7 @@ class ParentMenuItem extends React.Component { }); } else if(key === 'Tab') { + collapseParent(true); } else { //TODO: Any key that corresponds to a printable character (Optional): From c34e423c031e003de2360cb1d308d618886a8c57 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:17:26 -0500 Subject: [PATCH 101/286] cleanup some proptypes --- src/Menu/MenuBar.jsx | 3 +++ src/utils/propTypes.js | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index cccd457f..cbc51c1a 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -14,6 +14,9 @@ import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; * - If the menubar has a visible label, a labelId prop that points towards * the labeling element should be provided. Otherwise, one should pass in * a label via the label prop. In other words, one XOR the other must be provided. + * + * TODO: + * - activate upon shift + F10? */ class MenuBar extends React.Component { static propTypes = { diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index ab7d50df..895042d7 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -59,11 +59,9 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ 'separator', ]).isRequired, node: PropTypes.node.isRequired, - items: MENU_ITEMS_PROPTYPE, //Only relevant to "parentmenuitem" + children: MENU_ITEMS_PROPTYPE, //Only relevant to "parentmenuitem" orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), //Only relevant to parentmenuitem" - isExpanded: PropTypes.bool, //Only relevant to "parentmenuitem" isDisabled: PropTypes.bool, - isTabbable: PropTypes.bool, }); export const MENU_ITEMS_PROPTYPE = PropTypes.arrayOf(MENU_ITEM_PROPTYPE); From d7b8812a571e9f41d1fae29365a2b7bece52c02c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:23:46 -0500 Subject: [PATCH 102/286] give menus a label and labelId prop --- src/Menu/Menu.jsx | 13 +++++++++++-- src/Menu/MenuBar.jsx | 6 ++++-- src/Menu/ParentMenuItem.jsx | 15 ++++++++++++--- src/utils/propTypes.js | 2 ++ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 72b88b95..b7edaf74 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -1,14 +1,21 @@ import React from 'react'; import PropTypes from 'prop-types'; -//TODO: needs a label/labelId prop +/* + * Note: + * + * - The menu should either have a labelId prop that points to the menuitem or + * button that controls its display XOR a label prop. + */ function Menu(props) { - const { children, orientation } = props; + const { children, orientation, label, labelId } = props; return (
                                        { children }
                                      @@ -18,6 +25,8 @@ function Menu(props) { Menu.propTypes = { children: PropTypes.node.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, + labelId: PropTypes.string, }; Menu.defaultProps = { diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index cbc51c1a..f0ad4b23 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -9,7 +9,7 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; /* - * Some notes on props: + * Note: * * - If the menubar has a visible label, a labelId prop that points towards * the labeling element should be provided. Otherwise, one should pass in @@ -502,7 +502,7 @@ class MenuBar extends React.Component { renderItem = (item, index) => { const { tabbableIndex, expandedIndex } = this.state - const { type, node, children, isDisabled, orientation } = item; + const { type, node, children, orientation, label, labelId, isDisabled } = item; if(type === 'menuitem') { return ( @@ -532,6 +532,8 @@ class MenuBar extends React.Component { focusPrevSibling={ this.focusPrevSibling } focusNextMenubarItem={ this.focusNextSibling } orientation={ orientation } + label={ label } + labelId={ labelId } isDisabled={ isDisabled } isExpanded={ index === expandedIndex } isTabbable={ index === tabbableIndex } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index a958664c..a6992bf6 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -19,6 +19,8 @@ class ParentMenuItem extends React.Component { focusPrevSibling: PropTypes.func.isRequired, focusNextMenubarItem: PropTypes.func.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, + labelId: PropTypes.string, isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, @@ -473,7 +475,8 @@ class ParentMenuItem extends React.Component { render() { const { children, items, index, level, onKeyDown, - orientation, isExpanded, isDisabled, isTabbable, + orientation, label, labelId, + isExpanded, isDisabled, isTabbable, } = this.props; const itemNodes = items.map(this.renderItem); @@ -495,7 +498,11 @@ class ParentMenuItem extends React.Component { > { children }
                                      - + { itemNodes }
                                    • @@ -504,7 +511,7 @@ class ParentMenuItem extends React.Component { renderItem = (item, index) => { const { level, focusNextMenubarItem } = this.props; - const { node, type, children, orientation, isDisabled } = item; + const { node, type, children, orientation, label, labelId, isDisabled } = item; const { expandedIndex } = this.state; if(type === 'menuitem') { @@ -533,6 +540,8 @@ class ParentMenuItem extends React.Component { collapseParent={ this.collapseMenu } focusNextMenubarItem={ focusNextMenubarItem } orientation={ orientation } + label={ label } + labelId={ labelId } isExpanded={ index === expandedIndex } isDisabled={ isDisabled } ref={ this.childItemRefs[index] } diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index 895042d7..377f627d 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -61,6 +61,8 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ node: PropTypes.node.isRequired, children: MENU_ITEMS_PROPTYPE, //Only relevant to "parentmenuitem" orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), //Only relevant to parentmenuitem" + label: PropTypes.string, //Only relevant to "parentmenuitem" + labelId: PropTypes.string, //Only relevant to "parentmenuitem" isDisabled: PropTypes.bool, }); From 3320668bb476f534adc7a9ed9716933ab1ff7f4e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:24:22 -0500 Subject: [PATCH 103/286] default props --- src/Menu/Menu.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index b7edaf74..e16f036c 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -31,6 +31,8 @@ Menu.propTypes = { Menu.defaultProps = { orientation: 'vertical', + label: undefined, + labelId: undefined, }; export default Menu; From 406134ef91d68ba3b263298911b011fcdfe21e7c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:26:06 -0500 Subject: [PATCH 104/286] default props for label and labelId --- src/Menu/ParentMenuItem.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index a6992bf6..d769b5e8 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -28,6 +28,8 @@ class ParentMenuItem extends React.Component { static defaultProps = { orientation: 'horizontal', + label: undefined, + labelId: undefined, isExpanded: false, isDisabled: false, isTabbable: false, From abde89613f5af1a8dff847ca7c563d356bb5900f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:30:19 -0500 Subject: [PATCH 105/286] get rid of some TODO comments --- src/Menu/MenuBar.jsx | 7 ------- src/Menu/MenuItem.jsx | 1 - 2 files changed, 8 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f0ad4b23..627b6fce 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -14,9 +14,6 @@ import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; * - If the menubar has a visible label, a labelId prop that points towards * the labeling element should be provided. Otherwise, one should pass in * a label via the label prop. In other words, one XOR the other must be provided. - * - * TODO: - * - activate upon shift + F10? */ class MenuBar extends React.Component { static propTypes = { @@ -37,10 +34,6 @@ class MenuBar extends React.Component { const { items } = props; - //TODO: feels incredibly awkward, e.g.: - //- refs in state? - //- what if someone passes new props (e.g. change isDisabled for an item)? - //- are we updating state properly? deep copy/nested weirdness? this.state = { tabbableIndex: 0, expandedIndex: undefined, diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 17bbfa6e..7661033d 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -1,7 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -//TODO: navigation-specific menu item? const MenuItem = React.forwardRef(function MenuItem(props, ref) { const { children, index, level, onKeyDown, isDisabled, isTabbable } = props; From 49cd0dca44fe5bd470641621f5ca0be8db693f6a Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:32:25 -0500 Subject: [PATCH 106/286] get rid of some commented-out code --- src/Menu/MenuBar.jsx | 311 ----------------------------- src/Menu/ParentMenuItem.jsx | 376 +----------------------------------- 2 files changed, 3 insertions(+), 684 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 627b6fce..347a39c9 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -161,317 +161,6 @@ class MenuBar extends React.Component { //Move focus to the next menu item in the current menu whose label begins //with that printable character. } - - /* - const { orientation } = this.props; - const { items } = this.state; - const { key, target } = event; - const role = target.getAttribute('role'); - const position = target.dataset.position.split(','); - const level = position.length - 1; - let index; - let nextIndex; - let item; - let subItems = items; //(sub-)menu that item belongs in - - //TODO this should probably be put inside the setState() calls - //so that subItems is referencing prevState rather than - //this.state - position.forEach((pos, i) => { - index = Number.parseInt(pos, 10); - item = subItems[index]; - - //Don't do this on the last iteration so we know - //the subset of items that item belongs in. Otherwise, - //subItems would be the children of item (assuming - //item is a parent menuitem). - if(i < position.length - 1) - subItems = item.children; - }); - - console.log(level, index, item, subItems); - - //TODO: - //- take into account orientation - //- consolidate logic and move into separate methods? - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - - if(level > 0) { - this.setState(prevState => { - nextIndex = index === 0 ? subItems.length - 1 : index - 1; - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[item.children.length - 1].ref.current.focus(); - }); - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - - if(level > 0) { - this.setState(prevState => { - nextIndex = index === subItems.length - 1 ? 0 : index + 1; - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - - if(level === 0) { - //TODO Like with arrow right, also maintain - //"root expand state"? - this.setState(prevState => { - nextIndex = index === 0 ? subItems.length - 1 : index - 1; - subItems[index].isTabbable = false; - subItems[index].isExpanded = false; - subItems[nextIndex].isTabbable = true; - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(level === 1) { - this.setState(prevState => { - const pos1 = Number.parseInt(position[0], 10); - const nextIndex = pos1 === 0 ? items.length - 1 : pos1 - 1; - const nextItem = items[nextIndex]; - const parentMenuitem = items[pos1]; - - parentMenuitem.isTabbable = false; - parentMenuitem.isExpanded = false; - nextItem.isTabbable = true; - nextItem.ref.current.focus(); - - if(isParentMenuitem(nextItem)) - nextItem.isExpanded = true; - - return prevState; - }); - } - else { - this.setState(prevState => { - let _items = items; - let _item; - - position.forEach((pos, i) => { - const _pos = Number.parseInt(pos, 10); - _item = _items[_pos]; - _items = _item.children; - - if(i === position.length - 2) { - _item.isExpanded = false; - _item.ref.current.focus(); - } - }); - - return prevState; - }); - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - - if(level === 0) { - this.setState(prevState => { - //Const wasExpanded = subItems[index].isExpanded; - - nextIndex = index === subItems.length - 1 ? 0 : index + 1; - subItems[index].isTabbable = false; - subItems[index].isExpanded = false; - subItems[nextIndex].isTabbable = true; - subItems[nextIndex].ref.current.focus(); - - //This behavior isn't defined in the authoring prac - //but is exhibited in the example implementations - //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html - //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-2/menubar-2.html - //FIXME: currently broken - what if subItems[nextIndex] isn't a parent menuitem, - //but the subsequent item is? we lose the "root expanded" state. - //if(isParentMenu(subItems[nextIndex]) && wasExpanded) - // subItems[nextIndex].isExpanded = true; - - return prevState; - }); - } - else if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - else { - this.setState(prevState => { - //Close the submenu and any parent menus - let _items = items; - let _menuitem; - - position.forEach((pos, i) => { - const _pos = Number.parseInt(pos, 10); - position[i] = _pos; - _menuitem = _items[_pos]; - _items = _menuitem.children; - - if(isParentMenuitem(_menuitem)) - _menuitem.isExpanded = false; - }); - - //Move focus to the next menuitem in the menubar, - //and if it's a parent menuitem, open the submenu - //without changing focus - const index = position[0]; - const nextIndex = index === items.length - 1 ? 0 : index + 1; - const nextItem = items[nextIndex]; - const item = items[index]; - - item.isTabbable = false; - nextItem.isTabbable = true; - nextItem.ref.current.focus(); - - if(isParentMenuitem(nextItem)) - nextItem.isExpanded = true; - - return prevState; - }); - } - } - else if(key === 'Enter') { - event.preventDefault(); - - if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - else { - //TODO activate the item and close the (whole?) menu - } - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - - if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - else if(role === 'menuitemcheckbox') { - //TODO: change state without closing the menu - } - else if(role === 'menuitemradio') { - //TODO change state without closing the menu - } - else { - //TODO: activate the item and closes the (whole?) menu - } - } - else if(key === 'Home') { - event.preventDefault(); - - this.setState(prevState => { - nextIndex = 0; - - if(level === 0) { - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; - } - - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(key === 'End') { - event.preventDefault(); - - this.setState(prevState => { - nextIndex = subItems.length - 1; - - if(level === 0) { - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; - } - - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - - if(level > 0) { - this.setState(prevState => { - let _items = items; - let _item; - - position.forEach((pos, i) => { - const _pos = Number.parseInt(pos, 10); - _item = _items[_pos]; - _items = _item.children; - - if(i === position.length - 2) { - _item.isExpanded = false; - _item.ref.current.focus(); - } - }); - - return prevState; - }); - } - } - else if(key === 'Tab') { - this.setState(prevState => { - let _items = items; - let _item; - - position.forEach(pos => { - const _pos = Number.parseInt(pos, 10); - _item = _items[_pos]; - _items = _item.children; - - //FIXME: broken in both directions. shift+tab goes to the - //root item rather than the element before the menubar. - //Tabbing forward seems to focus on some unknown "thing" - //rather than something on the browser (though that only - //seems to occur if the menubar is NOT the last element). - //This behavior does not show up on the WAI-ARIA example - //implementations so it's likely something we're doing. - //_item.isTabbable = i === 0; - _item.isExpanded = false; - }); - - return prevState; - }); - } - else { - //TODO: Any key that corresponds to a printable character (Optional): - //Move focus to the next menu item in the current menu whose label begins - //with that printable character. - } - */ }; //---- Rendering ---- diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index d769b5e8..7bc9540d 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -50,6 +50,9 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { + //TODO + //- handle orientation + //- improve abstractions for state/focus management? const { items, collapseParent, focusPrevSibling, focusNextMenubarItem } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); @@ -160,317 +163,6 @@ class ParentMenuItem extends React.Component { //Move focus to the next menu item in the current menu whose label begins //with that printable character. } - - /* - const { orientation } = this.props; - const { items } = this.state; - const { key, target } = event; - const role = target.getAttribute('role'); - const position = target.dataset.position.split(','); - const level = position.length - 1; - let index; - let nextIndex; - let item; - let subItems = items; //(sub-)menu that item belongs in - - //TODO this should probably be put inside the setState() calls - //so that subItems is referencing prevState rather than - //this.state - position.forEach((pos, i) => { - index = Number.parseInt(pos, 10); - item = subItems[index]; - - //Don't do this on the last iteration so we know - //the subset of items that item belongs in. Otherwise, - //subItems would be the children of item (assuming - //item is a parent menuitem). - if(i < position.length - 1) - subItems = item.children; - }); - - console.log(level, index, item, subItems); - - //TODO: - //- take into account orientation - //- consolidate logic and move into separate methods? - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - - if(level > 0) { - this.setState(prevState => { - nextIndex = index === 0 ? subItems.length - 1 : index - 1; - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[item.children.length - 1].ref.current.focus(); - }); - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - - if(level > 0) { - this.setState(prevState => { - nextIndex = index === subItems.length - 1 ? 0 : index + 1; - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - - if(level === 0) { - //TODO Like with arrow right, also maintain - //"root expand state"? - this.setState(prevState => { - nextIndex = index === 0 ? subItems.length - 1 : index - 1; - subItems[index].isTabbable = false; - subItems[index].isExpanded = false; - subItems[nextIndex].isTabbable = true; - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(level === 1) { - this.setState(prevState => { - const pos1 = Number.parseInt(position[0], 10); - const nextIndex = pos1 === 0 ? items.length - 1 : pos1 - 1; - const nextItem = items[nextIndex]; - const parentMenuitem = items[pos1]; - - parentMenuitem.isTabbable = false; - parentMenuitem.isExpanded = false; - nextItem.isTabbable = true; - nextItem.ref.current.focus(); - - if(isParentMenuitem(nextItem)) - nextItem.isExpanded = true; - - return prevState; - }); - } - else { - this.setState(prevState => { - let _items = items; - let _item; - - position.forEach((pos, i) => { - const _pos = Number.parseInt(pos, 10); - _item = _items[_pos]; - _items = _item.children; - - if(i === position.length - 2) { - _item.isExpanded = false; - _item.ref.current.focus(); - } - }); - - return prevState; - }); - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - - if(level === 0) { - this.setState(prevState => { - //Const wasExpanded = subItems[index].isExpanded; - - nextIndex = index === subItems.length - 1 ? 0 : index + 1; - subItems[index].isTabbable = false; - subItems[index].isExpanded = false; - subItems[nextIndex].isTabbable = true; - subItems[nextIndex].ref.current.focus(); - - //This behavior isn't defined in the authoring prac - //but is exhibited in the example implementations - //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-1/menubar-1.html - //https://www.w3.org/TR/wai-aria-practices-1.1/examples/menubar/menubar-2/menubar-2.html - //FIXME: currently broken - what if subItems[nextIndex] isn't a parent menuitem, - //but the subsequent item is? we lose the "root expanded" state. - //if(isParentMenu(subItems[nextIndex]) && wasExpanded) - // subItems[nextIndex].isExpanded = true; - - return prevState; - }); - } - else if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - else { - this.setState(prevState => { - //Close the submenu and any parent menus - let _items = items; - let _menuitem; - - position.forEach((pos, i) => { - const _pos = Number.parseInt(pos, 10); - position[i] = _pos; - _menuitem = _items[_pos]; - _items = _menuitem.children; - - if(isParentMenuitem(_menuitem)) - _menuitem.isExpanded = false; - }); - - //Move focus to the next menuitem in the menubar, - //and if it's a parent menuitem, open the submenu - //without changing focus - const index = position[0]; - const nextIndex = index === items.length - 1 ? 0 : index + 1; - const nextItem = items[nextIndex]; - const item = items[index]; - - item.isTabbable = false; - nextItem.isTabbable = true; - nextItem.ref.current.focus(); - - if(isParentMenuitem(nextItem)) - nextItem.isExpanded = true; - - return prevState; - }); - } - } - else if(key === 'Enter') { - event.preventDefault(); - - if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - else { - //TODO activate the item and close the (whole?) menu - } - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - - if(isParentMenuitem(item)) { - this.setState(prevState => { - item.isExpanded = true; - return prevState; - }, () => { - item.children[0].ref.current.focus(); - }); - } - else if(role === 'menuitemcheckbox') { - //TODO: change state without closing the menu - } - else if(role === 'menuitemradio') { - //TODO change state without closing the menu - } - else { - //TODO: activate the item and closes the (whole?) menu - } - } - else if(key === 'Home') { - event.preventDefault(); - - this.setState(prevState => { - nextIndex = 0; - - if(level === 0) { - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; - } - - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(key === 'End') { - event.preventDefault(); - - this.setState(prevState => { - nextIndex = subItems.length - 1; - - if(level === 0) { - subItems[index].isTabbable = false; - subItems[nextIndex].isTabbable = true; - } - - subItems[nextIndex].ref.current.focus(); - return prevState; - }); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - - if(level > 0) { - this.setState(prevState => { - let _items = items; - let _item; - - position.forEach((pos, i) => { - const _pos = Number.parseInt(pos, 10); - _item = _items[_pos]; - _items = _item.children; - - if(i === position.length - 2) { - _item.isExpanded = false; - _item.ref.current.focus(); - } - }); - - return prevState; - }); - } - } - else if(key === 'Tab') { - this.setState(prevState => { - let _items = items; - let _item; - - position.forEach(pos => { - const _pos = Number.parseInt(pos, 10); - _item = _items[_pos]; - _items = _item.children; - - //FIXME: broken in both directions. shift+tab goes to the - //root item rather than the element before the menubar. - //Tabbing forward seems to focus on some unknown "thing" - //rather than something on the browser (though that only - //seems to occur if the menubar is NOT the last element). - //This behavior does not show up on the WAI-ARIA example - //implementations so it's likely something we're doing. - //_item.isTabbable = i === 0; - _item.isExpanded = false; - }); - - return prevState; - }); - } - else { - //TODO: Any key that corresponds to a printable character (Optional): - //Move focus to the next menu item in the current menu whose label begins - //with that printable character. - } - */ }; //---- Rendering ---- @@ -591,65 +283,3 @@ class ParentMenuItem extends React.Component { } export default ParentMenuItem; - -//TODO: this is straying further and further away -//from the idea of a "base" component - might be a good -//idea to separate the opinionated stuff I'm adding on? -/* -const ParentMenuItem = React.forwardRef(function ParentMenuItem(props, ref) { - const { - children, items, isExpanded, isDisabled, isTabbable, - orientation, renderItem, onKeyDown, position, - } = props; - const itemNodes = items.map((item, index, _items) => { - return renderItem(item, index, _items, props, onKeyDown); - }); - - return ( -
                                    • - - { children } - - - { itemNodes } - -
                                    • - ); -}); - -ParentMenuItem.propTypes = { - children: PropTypes.node.isRequired, - items: MENU_ITEMS_PROPTYPE.isRequired, - onKeyDown: PropTypes.func.isRequired, - position: PropTypes.arrayOf(PropTypes.number).isRequired, - isExpanded: PropTypes.bool, - isDisabled: PropTypes.bool, - isTabbable: PropTypes.bool, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - renderItem: PropTypes.func, - renderMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types - renderParentMenuItem: PropTypes.func, //eslint-disable-line react/no-unused-prop-types -}; - -ParentMenuItem.defaultProps = { - isExpanded: false, - isDisabled: false, - isTabbable: false, - orientation: 'horizontal', - renderItem, - renderMenuItem, - renderParentMenuItem, -}; - -export default ParentMenuItem; -*/ From e86e333af99dc283217a85f72a88847ddff13fef Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:34:20 -0500 Subject: [PATCH 107/286] irrelevant comment --- src/Accordion/index.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Accordion/index.jsx b/src/Accordion/index.jsx index c51b48cf..ebdf0aaa 100644 --- a/src/Accordion/index.jsx +++ b/src/Accordion/index.jsx @@ -45,8 +45,6 @@ class Accordion extends React.Component { }; onTriggerKeyDown = (event) => { - //TODO: should we be using a data-index here? feels like a potentially - //unnecessary prop being passed down to the menu items const { sections } = this.props; const { key } = event; const index = Number.parseInt(event.target.dataset.index, 10); From 5cd161b74330093826d8cda2544f384688840cfc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:35:32 -0500 Subject: [PATCH 108/286] not using utils.jsx anymore --- src/Menu/utils.jsx | 104 --------------------------------------------- 1 file changed, 104 deletions(-) delete mode 100644 src/Menu/utils.jsx diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx deleted file mode 100644 index 8c9955d7..00000000 --- a/src/Menu/utils.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; - -//Components and Styles -import MenuItem from 'src/Menu/MenuItem'; -import ParentMenuItem from 'src/Menu/ParentMenuItem'; - -/** - * Renders an item in a (sub-)menu. - * - * Note that this function only receives data for the current level of the - * overall menu, i.e. if this function is being run of a sub-menu, then - * arguments like parentMenuProps can only see data for this sub-menu and below. - * - * @param {object} item Descriptor for the item - * @param {number} index Location of the item within the current (sub-)menu - * @param {Array} items Array of descriptors representing the items in the current (sub-)menu - * @param {object} menuProps Props for the current (sub-)menu - * @param {function} onKeyDown - */ -export function renderItem(item, index, items, menuProps, onKeyDown) { - const { renderMenuItem, renderParentMenuItem } = menuProps; - const { type } = item; - let node; - - if(type === 'menuitem') - node = renderMenuItem(item, index, items, menuProps, onKeyDown); - else if(type === 'parentmenuitem') - node = renderParentMenuItem(item, index, items, menuProps, onKeyDown); - - return node; -} - -/** - * Renders a menuitem. - * - * @param {object} item Descriptor for the menuitem - * @param {number} index Location of the menuitem within the current (sub-)menu - * @param {Array} items Array of descriptors representing the items in the current (sub-)menu - * @param {object} menuProps Props for the current (sub-)menu - * @param {function} onKeyDown - */ -export function renderMenuItem(menuItem, index, menuItems, menuProps, onKeyDown) { - const { node, isDisabled, isTabbable, ref, position } = menuItem; - - return ( - - { node } - - ); -} - -/** - * Renders a parent menuitem. - * - * Note that a "parent menuitem" is a menuitem that acts as a parent - * for a sub-menu. The term "parent menuitem" is NOT being used to - * communicate "parent of the current item", though that parent would - * be a "parent menuitem". - * - * @param {object} item Descriptor for the parent menuitem - * @param {number} index Location of the parent menuitem within the current (sub-)menu - * @param {Array} items Array of descriptors representing the items in the current (sub-)menu - * @param {object} menuProps Props for the current (sub-)menu - * @param {function} onKeyDown - */ -export function renderParentMenuItem(item, index, items, menuProps, onKeyDown) { - const { node, children, isDisabled, isTabbable, orientation, isExpanded, ref, position } = item; - - return ( - - { node } - - ); -} - -/** - * Checks if the item is a "parent menuitem". - * - * @param {object} item - * @return {boolean} - */ -export function isParentMenuitem(item) { - if(item === undefined || item === null) - return false; - else - return item.type === 'parentmenuitem'; -} From e3a6b633b4e1be3318f1a89ad06519df835d5974 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:42:40 -0500 Subject: [PATCH 109/286] get rid of more todos --- src/Menu/MenuBar.jsx | 3 --- src/Menu/ParentMenuItem.jsx | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 347a39c9..e9256685 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -44,9 +44,6 @@ class MenuBar extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - //TODO - //- orientation - //- abstract away setState calls? const { items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 7bc9540d..ee26aaaf 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -50,9 +50,6 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - //TODO - //- handle orientation - //- improve abstractions for state/focus management? const { items, collapseParent, focusPrevSibling, focusNextMenubarItem } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); From ad56ae707bc0af000d741bbd1c250d70feeea933 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:51:47 -0500 Subject: [PATCH 110/286] get rid of commented code --- src/Menu/ParentMenuItem.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index ee26aaaf..f0251ee1 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -269,8 +269,6 @@ class ParentMenuItem extends React.Component { this.setState({ expandedIndex: undefined, }, () => { - // if(typeof callback === 'function') - // callback(); if(collapseAll) collapseParent(true, callback); else if(typeof callback === 'function') From 755f323164027e247da142bac5b9f2195c780577 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 16:54:50 -0500 Subject: [PATCH 111/286] get rid of initializeItems() --- src/Menu/MenuBar.jsx | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index e9256685..3f4933bd 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -225,29 +225,6 @@ class MenuBar extends React.Component { }; //---- Misc. ---- - initializeItems = (items, level = 0, position = []) => { - const _items = []; - - items.forEach((item, i) => { - const { type, children } = item; - - position = position.slice(0); - position[level] = i; - - //We can't modify the props being passed in here, - //so let's create a copy of items with some extra - //info attached. - _items.push(Object.assign({}, item, { - position, - })); - - if(type === 'parentmenuitem') - _items[i].children = this.initializeItems(children, level + 1, position); - }); - - return _items; - }; - collapseMenu = (collapseAll, callback) => { console.log('in menubar'); this.setState({ From 65a89a037fcdfb7cdc300702897f8b6520bc9d37 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 19:11:58 -0500 Subject: [PATCH 112/286] menubar maintains isExpanded state in its event --- src/Menu/MenuBar.jsx | 55 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 3f4933bd..79cf317f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -77,10 +77,17 @@ class MenuBar extends React.Component { else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); const nextIndex = index === 0 ? items.length - 1 : index - 1; + const nextItem = items[nextIndex]; + const { type: nextType } = nextItem; - this.setState({ - tabbableIndex: nextIndex, - expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this + this.setState(prevState => { + const { expandedIndex } = prevState; + const isExpanded = expandedIndex !== undefined && expandedIndex !== null; + + return { + tabbableIndex: nextIndex, + expandedIndex: isExpanded && nextType === 'parentmenuitem' ? nextIndex : undefined, + }; }, () => { this.itemRefs[nextIndex].current.focus(); }); @@ -88,10 +95,17 @@ class MenuBar extends React.Component { else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); const nextIndex = index === items.length - 1 ? 0 : index + 1; + const nextItem = items[nextIndex]; + const { type: nextType } = nextItem; + + this.setState(prevState => { + const { expandedIndex } = prevState; + const isExpanded = expandedIndex !== undefined && expandedIndex !== null; - this.setState({ - tabbableIndex: nextIndex, - expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this + return { + tabbableIndex: nextIndex, + expandedIndex: isExpanded && nextType === 'parentmenuitem' ? nextIndex : undefined, + }; }, () => { this.itemRefs[nextIndex].current.focus(); }); @@ -132,20 +146,36 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); + const firstIndex = 0; + const firstItem = items[firstIndex]; + const { type: firstType } = firstItem; + + this.setState(prevState => { + const { expandedIndex } = prevState; + const isExpanded = expandedIndex !== undefined && expandedIndex !== null; - this.setState({ - tabbableIndex: 0, - expandedIndex: undefined, //TODO: WAI-ARIA implementation maintains this + return { + tabbableIndex: firstIndex, + expandedIndex: isExpanded && firstType === 'parentmenuitem' ? firstIndex : undefined, + }; }, () => { this.itemRefs[0].current.focus(); }); } else if(key === 'End') { event.preventDefault(); + const lastIndex = items.length - 1; + const lastItem = items[lastIndex]; + const { type: lastType } = lastItem; - this.setState({ - tabbableIndex: items.length - 1, - expandedIndex: undefined, //TODO WAI-ARIA implementation maintains this + this.setState(prevState => { + const { expandedIndex } = prevState; + const isExpanded = expandedIndex !== undefined && expandedIndex !== null; + + return { + tabbableIndex: items.length - 1, + expandedIndex: isExpanded && lastType === 'parentmenuitem' ? lastIndex : undefined, + }; }, () => { this.itemRefs[items.length - 1].current.focus(); }); @@ -227,6 +257,7 @@ class MenuBar extends React.Component { //---- Misc. ---- collapseMenu = (collapseAll, callback) => { console.log('in menubar'); + this.setState({ expandedIndex: undefined, }, () => { From 34274e060ac9456e9cfdd021977272940a051b42 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 19:14:37 -0500 Subject: [PATCH 113/286] minor naming change --- src/Menu/MenuBar.jsx | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 79cf317f..aa198c70 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -76,20 +76,20 @@ class MenuBar extends React.Component { } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - const nextIndex = index === 0 ? items.length - 1 : index - 1; - const nextItem = items[nextIndex]; - const { type: nextType } = nextItem; + const prevIndex = index === 0 ? items.length - 1 : index - 1; + const prevItem = items[prevIndex]; + const { type: prevType } = prevItem; this.setState(prevState => { const { expandedIndex } = prevState; const isExpanded = expandedIndex !== undefined && expandedIndex !== null; return { - tabbableIndex: nextIndex, - expandedIndex: isExpanded && nextType === 'parentmenuitem' ? nextIndex : undefined, + tabbableIndex: prevIndex, + expandedIndex: isExpanded && prevType === 'parentmenuitem' ? prevIndex : undefined, }; }, () => { - this.itemRefs[nextIndex].current.focus(); + this.itemRefs[prevIndex].current.focus(); }); } else if(key === 'ArrowRight' || key === 'Right') { @@ -268,13 +268,13 @@ class MenuBar extends React.Component { focusPrevSibling = (index, autoExpand) => { const { items } = this.props; - const newIndex = index === 0 ? items.length - 1 : index - 1; + const prevIndex = index === 0 ? items.length - 1 : index - 1; this.setState({ - tabbableIndex: newIndex, - expandedIndex: autoExpand ? newIndex : undefined, + tabbableIndex: prevIndex, + expandedIndex: autoExpand ? prevIndex : undefined, }, () => { - this.itemRefs[newIndex].current.focus(); + this.itemRefs[prevIndex].current.focus(); }); }; @@ -283,15 +283,15 @@ class MenuBar extends React.Component { focusNextSibling = () => { const { items } = this.props; const { expandedIndex } = this.state; - const newIndex = expandedIndex === items.length - 1 ? 0 : expandedIndex + 1; + const nextIndex = expandedIndex === items.length - 1 ? 0 : expandedIndex + 1; - console.log(expandedIndex, newIndex); + console.log(expandedIndex, nextIndex); this.setState({ - tabbableIndex: newIndex, - expandedIndex: newIndex, + tabbableIndex: nextIndex, + expandedIndex: nextIndex, }, () => { - this.itemRefs[newIndex].current.focus(); + this.itemRefs[nextIndex].current.focus(); }); }; } From 7aab80cdc9547263c1d1b1d7f52b1631a6398199 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 5 Jan 2022 19:18:05 -0500 Subject: [PATCH 114/286] add a fixme comment --- src/Menu/ParentMenuItem.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index f0251ee1..6fb67146 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -99,6 +99,12 @@ class ParentMenuItem extends React.Component { }); } else { + //FIXME: this is broken - the next menubar item should be focused, + //but won't be because we immediately collapse everything after + //focusing on it. we're currently using the menubar's expandedIndex + //to determine which sibling to focus on next, so we currently + //cannot collapse everything first (otherwise we'd lose the expandedIndex + //state); focusNextMenubarItem(); collapseParent(true); } From 11e2b4779cbada4468e8feef65d01c67cac4d5e2 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 12:06:33 -0500 Subject: [PATCH 115/286] add a menuitemcheckbox component --- src/Menu/MenuItemCheckbox.jsx | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/Menu/MenuItemCheckbox.jsx diff --git a/src/Menu/MenuItemCheckbox.jsx b/src/Menu/MenuItemCheckbox.jsx new file mode 100644 index 00000000..80108473 --- /dev/null +++ b/src/Menu/MenuItemCheckbox.jsx @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const MenuItemCheckbox = React.forwardRef(function MenuItem(props, ref) { + const { children, index, level, onKeyDown, isDisabled, isTabbable, checked } = props; + + return ( +
                                    • + { children } +
                                    • + ); +}); + +MenuItemCheckbox.propTypes = { + children: PropTypes.node.isRequired, + index: PropTypes.number.isRequired, + level: PropTypes.number.isRequired, + onKeyDown: PropTypes.func.isRequired, + isDisabled: PropTypes.bool, + isTabbable: PropTypes.bool, + checked: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.oneOf([ 'true', 'false', 'mixed' ]), + ]).isRequired, +}; + +MenuItemCheckbox.defaultProps = { + isDisabled: false, + isTabbable: false, + checked: false, +}; + +export default MenuItemCheckbox; From 63890c7c1874edf35e4e968a69a2bf0ab55f0fb5 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 13:49:26 -0500 Subject: [PATCH 116/286] render some menuitem checkboxes --- src/App.jsx | 8 ++++++++ src/Menu/MenuBar.jsx | 17 ++++++++++++++++- src/Menu/MenuItemCheckbox.jsx | 10 +++++----- src/Menu/ParentMenuItem.jsx | 16 +++++++++++++++- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 7bd3b5e0..b9fb5144 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -52,6 +52,14 @@ const MENU_ITEMS = [ type: 'menuitem', node: 'Hello world!', }, + { + type: 'menuitemcheckbox', + node: 'Checkbox 1', + }, + { + type: 'menuitemcheckbox', + node: 'Checkbox 2', + }, ], }, { diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index aa198c70..66d246ee 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; //Components and Styles import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; +import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -220,7 +221,6 @@ class MenuBar extends React.Component { index={ index } level={ 0 } onKeyDown={ this.onChildKeyDown } - collapseParent={ this.collapseMenu } isDisabled={ isDisabled } isTabbable={ index === tabbableIndex } ref={ this.itemRefs[index] } @@ -252,6 +252,21 @@ class MenuBar extends React.Component { ); } + else if(type === 'menuitemcheckbox') { + return ( + + { node } + + ); + } }; //---- Misc. ---- diff --git a/src/Menu/MenuItemCheckbox.jsx b/src/Menu/MenuItemCheckbox.jsx index 80108473..ec6e374e 100644 --- a/src/Menu/MenuItemCheckbox.jsx +++ b/src/Menu/MenuItemCheckbox.jsx @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -const MenuItemCheckbox = React.forwardRef(function MenuItem(props, ref) { - const { children, index, level, onKeyDown, isDisabled, isTabbable, checked } = props; +const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox(props, ref) { + const { children, index, level, onKeyDown, isDisabled, isTabbable, isChecked } = props; return (
                                    • { children } @@ -27,7 +27,7 @@ MenuItemCheckbox.propTypes = { onKeyDown: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, - checked: PropTypes.oneOfType([ + isChecked: PropTypes.oneOfType([ PropTypes.bool, PropTypes.oneOf([ 'true', 'false', 'mixed' ]), ]).isRequired, @@ -36,7 +36,7 @@ MenuItemCheckbox.propTypes = { MenuItemCheckbox.defaultProps = { isDisabled: false, isTabbable: false, - checked: false, + isChecked: false, }; export default MenuItemCheckbox; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 6fb67146..67d3d735 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; //Components and Styles import Menu from 'src/Menu/Menu'; import MenuItem from 'src/Menu/MenuItem'; +import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -218,7 +219,6 @@ class ParentMenuItem extends React.Component { index={ index } level={ level + 1 } onKeyDown={ this.onChildKeyDown } - collapseParent={ this.collapseMenu } isDisabled={ isDisabled } ref={ this.childItemRefs[index] } > @@ -247,6 +247,20 @@ class ParentMenuItem extends React.Component { ); } + else if(type === 'menuitemcheckbox') { + return ( + + { node } + + ); + } }; //---- Misc. --- From a5b2e03678d75a3beb4b00e4417aaeac78f06999 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 13:49:36 -0500 Subject: [PATCH 117/286] menuitemradio implementation --- src/Menu/MenuItemRadio.jsx | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/Menu/MenuItemRadio.jsx diff --git a/src/Menu/MenuItemRadio.jsx b/src/Menu/MenuItemRadio.jsx new file mode 100644 index 00000000..0c16f8cb --- /dev/null +++ b/src/Menu/MenuItemRadio.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const MenuItemRadio = React.forwardRef(function MenuItemRadio(props, ref) { + const { children, index, level, onKeyDown, isDisabled, isTabbable, isChecked } = props; + + return ( +
                                    • + { children } +
                                    • + ); +}); + +MenuItemRadio.propTypes = { + children: PropTypes.node.isRequired, + index: PropTypes.number.isRequired, + level: PropTypes.number.isRequired, + onKeyDown: PropTypes.func.isRequired, + isDisabled: PropTypes.bool, + isTabbable: PropTypes.bool, + isChecked: PropTypes.bool, +}; + +MenuItemRadio.defaultProps = { + isDisabled: false, + isTabbable: false, + isChecked: false, +}; + +export default MenuItemRadio; From c347fc2b149c2881f95028795fda33d0d4ec3973 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 14:53:15 -0500 Subject: [PATCH 118/286] radio group implementation --- src/Menu/RadioGroup.jsx | 29 +++++++++++++++++++++++++++++ src/utils/propTypes.js | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Menu/RadioGroup.jsx diff --git a/src/Menu/RadioGroup.jsx b/src/Menu/RadioGroup.jsx new file mode 100644 index 00000000..1c011d47 --- /dev/null +++ b/src/Menu/RadioGroup.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +function MenuItemRadioGroup(props) { + const { children, label, labelId } = props; + + return ( +
                                        + { children } +
                                      + ); +} + +RadioGroup.propTypes = { + children: PropTypes.node.isRequired, + label: PropTypes.string, + labelId: PropTypes.string, +}; + +RadioGroup.defaultProps = { + label: undefined, + labelId: undefined, +}; + +export default RadioGroup; diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index 377f627d..006fd606 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -55,7 +55,7 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ 'menuitem', 'parentmenuitem', 'menuitemcheckbox', - 'menuitemreadio', + 'menuitemradio', 'separator', ]).isRequired, node: PropTypes.node.isRequired, From 765384bc3e7d7c8f800fd7ec1d8d69e5dd6b887e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 14:54:06 -0500 Subject: [PATCH 119/286] rename file --- src/Menu/{RadioGroup.jsx => MenuItemRadioGroup.jsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Menu/{RadioGroup.jsx => MenuItemRadioGroup.jsx} (100%) diff --git a/src/Menu/RadioGroup.jsx b/src/Menu/MenuItemRadioGroup.jsx similarity index 100% rename from src/Menu/RadioGroup.jsx rename to src/Menu/MenuItemRadioGroup.jsx From 9e2e34ff7e078a76298c60b5f4128c5b7882b022 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 15:07:04 -0500 Subject: [PATCH 120/286] implement a separator component --- src/App.jsx | 3 +++ src/Menu/MenuBar.jsx | 8 ++++++++ src/Menu/MenuItemSeparator.jsx | 22 ++++++++++++++++++++++ src/Menu/ParentMenuItem.jsx | 8 ++++++++ 4 files changed, 41 insertions(+) create mode 100644 src/Menu/MenuItemSeparator.jsx diff --git a/src/App.jsx b/src/App.jsx index b9fb5144..e53efc77 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -60,6 +60,9 @@ const MENU_ITEMS = [ type: 'menuitemcheckbox', node: 'Checkbox 2', }, + { + type: 'separator', + }, ], }, { diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 66d246ee..baeb20a1 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; +import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -267,6 +268,13 @@ class MenuBar extends React.Component { ); } + else if(type === 'separator') { + return ( + + { node } + + ); + } }; //---- Misc. ---- diff --git a/src/Menu/MenuItemSeparator.jsx b/src/Menu/MenuItemSeparator.jsx new file mode 100644 index 00000000..4ab44791 --- /dev/null +++ b/src/Menu/MenuItemSeparator.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +function MenuItemSeparator(props) { + const { children } = props; + + return ( +
                                    • + { children } +
                                    • + ); +} + +MenuItemSeparator.propTypes = { + children: PropTypes.node, +}; + +MenuItemSeparator.defaultProps = { + children: undefined, +}; + +export default MenuItemSeparator; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 67d3d735..61f2a6c6 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import Menu from 'src/Menu/Menu'; import MenuItem from 'src/Menu/MenuItem'; import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; +import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; //Misc. import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -261,6 +262,13 @@ class ParentMenuItem extends React.Component { ); } + else if(type === 'separator') { + return ( + + { node } + + ); + } }; //---- Misc. --- From f0e405779ff29d428803a27417996c90ad6996c1 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 15:19:19 -0500 Subject: [PATCH 121/286] add an orientation for MenuItemSeparator --- src/Menu/MenuBar.jsx | 2 +- src/Menu/MenuItemSeparator.jsx | 22 ++++++++++++++++++++-- src/Menu/ParentMenuItem.jsx | 2 +- src/utils/propTypes.js | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index baeb20a1..c39b0bc9 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -270,7 +270,7 @@ class MenuBar extends React.Component { } else if(type === 'separator') { return ( - + { node } ); diff --git a/src/Menu/MenuItemSeparator.jsx b/src/Menu/MenuItemSeparator.jsx index 4ab44791..183bf48f 100644 --- a/src/Menu/MenuItemSeparator.jsx +++ b/src/Menu/MenuItemSeparator.jsx @@ -1,11 +1,27 @@ import React from 'react'; import PropTypes from 'prop-types'; +/* + * In the authoring practices guide, they say, + * "All separators should have aria-orientation consistent + * with the separator's orientation." + * + * That statement doesn't make much sense to me because it's + * tautological. At first glance, it seems like a typo and + * that the correct wording should be "consistent with the + * containing menu or menubar's orientation", but that also + * doesn't make sense. If a menu or menubar is oriented + * vertically, then the separator should be horizontal, + * and vice-versa, right? + * + * See: + * https://www.w3.org/TR/wai-aria-practices-1.1/#menu + */ function MenuItemSeparator(props) { - const { children } = props; + const { children, orientation } = props; return ( -
                                    • +
                                    • { children }
                                    • ); @@ -13,10 +29,12 @@ function MenuItemSeparator(props) { MenuItemSeparator.propTypes = { children: PropTypes.node, + orientation: PropTypes.oneOf([ 'horizontal', 'vertical' ]), }; MenuItemSeparator.defaultProps = { children: undefined, + orientation: 'horizontal', }; export default MenuItemSeparator; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 61f2a6c6..14253067 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -264,7 +264,7 @@ class ParentMenuItem extends React.Component { } else if(type === 'separator') { return ( - + { node } ); diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index 006fd606..8369ce42 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -60,7 +60,7 @@ export const MENU_ITEM_PROPTYPE = PropTypes.shape({ ]).isRequired, node: PropTypes.node.isRequired, children: MENU_ITEMS_PROPTYPE, //Only relevant to "parentmenuitem" - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), //Only relevant to parentmenuitem" + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), //Only relevant to parentmenuitem" or "separator" label: PropTypes.string, //Only relevant to "parentmenuitem" labelId: PropTypes.string, //Only relevant to "parentmenuitem" isDisabled: PropTypes.bool, From 1b260920598427f00110fb27d8a706265cd40ff8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 15:27:32 -0500 Subject: [PATCH 122/286] wrap menuitemradiogroup in an
                                    • --- src/Menu/MenuItemRadioGroup.jsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Menu/MenuItemRadioGroup.jsx b/src/Menu/MenuItemRadioGroup.jsx index 1c011d47..4ff0c4dc 100644 --- a/src/Menu/MenuItemRadioGroup.jsx +++ b/src/Menu/MenuItemRadioGroup.jsx @@ -5,13 +5,15 @@ function MenuItemRadioGroup(props) { const { children, label, labelId } = props; return ( -
                                        - { children } -
                                      +
                                    • +
                                        + { children } +
                                      +
                                    • ); } From d73471d576621ea9c5a7fc8d9285106565920a13 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 6 Jan 2022 15:31:07 -0500 Subject: [PATCH 123/286] add a comment for menuitemradiogroup --- src/Menu/MenuItemRadioGroup.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Menu/MenuItemRadioGroup.jsx b/src/Menu/MenuItemRadioGroup.jsx index 4ff0c4dc..f1a536e2 100644 --- a/src/Menu/MenuItemRadioGroup.jsx +++ b/src/Menu/MenuItemRadioGroup.jsx @@ -1,6 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; +/* + * Note that if a menu or menubar contains more than one group of + * menuitemradio elements or if the menu has a group of menuitemradio + * elements alongside other unrelated menu items: + * + * - the menuitemradio elements should be nested in an element with the "group" role + * - menuitemradio groups should be delimited with an element with the "separator" role + * + * See: + * https://www.w3.org/TR/wai-aria-1.1/#menuitemradio + */ function MenuItemRadioGroup(props) { const { children, label, labelId } = props; From dcd85a88d386e9aa89f1ee03da7c3681d5ce61d5 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Sat, 8 Jan 2022 14:25:22 -0500 Subject: [PATCH 124/286] give each menuitem type their own proptype --- src/App.jsx | 76 +++++++++++++++++++++-------------- src/Menu/MenuBar.jsx | 32 +++++++-------- src/Menu/MenuItemCheckbox.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 22 +++++----- src/utils/propTypes.js | 64 +++++++++++++++++++++++------ 5 files changed, 124 insertions(+), 72 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index e53efc77..b7edab07 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -35,84 +35,98 @@ const DUMMY_ACCORDION_SECTIONS = [ }, ]; -const MENU_ITEMS = [ +const MENUITEMS = [ { - type: 'parentmenuitem', + type: 'menu', node: 'Parent Menuitem 1', children: [ { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitemcheckbox', + type: 'checkbox', node: 'Checkbox 1', }, { - type: 'menuitemcheckbox', + type: 'checkbox', node: 'Checkbox 2', }, - { - type: 'separator', - }, +// { +// type: 'separator', +// }, +// { +// type: 'radiogroup', +// children: [ +// { +// node: 'Radio Option 1', +// }, +// { +// node: 'Radio Option 2', +// }, +// { +// node: 'Radio Option 3', +// }, +// ], +// }, ], }, { - type: 'parentmenuitem', + type: 'menu', node: 'Parent Menuitem 2', children: [ { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'parentmenuitem', + type: 'menu', node: 'Nested Parent Menuitem', children: [ { - type: 'parentmenuitem', + type: 'menu', node: 'Nested Parent Menuitem', children: [ { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, ], }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'parentmenuitem', + type: 'menu', node: 'Nested Parent Menuitem', children: [ { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, ], @@ -120,37 +134,37 @@ const MENU_ITEMS = [ ], }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, ], }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'parentmenuitem', + type: 'menu', node: 'Parent Menuitem 3', children: [ { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'parentmenuitem', + type: 'menu', node: 'Nested Parent Menuitem', children: [ { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, { - type: 'menuitem', + type: 'item', node: 'Hello world!', }, ], @@ -168,7 +182,7 @@ function App() { Menu Button - + ); } diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index c39b0bc9..c3ca293f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -8,7 +8,7 @@ import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; //Misc. -import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; /* * Note: @@ -19,7 +19,7 @@ import { MENU_ITEMS_PROPTYPE } from 'src/utils/propTypes'; */ class MenuBar extends React.Component { static propTypes = { - items: MENU_ITEMS_PROPTYPE.isRequired, + items: MENUITEMS_PROPTYPE.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, @@ -57,7 +57,7 @@ class MenuBar extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - if(type === 'parentmenuitem') { + if(type === 'menu') { this.setState({ expandedIndex: index, }, () => { @@ -68,7 +68,7 @@ class MenuBar extends React.Component { else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - if(type === 'parentmenuitem') { + if(type === 'menu') { this.setState({ expandedIndex: index, }, () => { @@ -88,7 +88,7 @@ class MenuBar extends React.Component { return { tabbableIndex: prevIndex, - expandedIndex: isExpanded && prevType === 'parentmenuitem' ? prevIndex : undefined, + expandedIndex: isExpanded && prevType === 'menu' ? prevIndex : undefined, }; }, () => { this.itemRefs[prevIndex].current.focus(); @@ -106,7 +106,7 @@ class MenuBar extends React.Component { return { tabbableIndex: nextIndex, - expandedIndex: isExpanded && nextType === 'parentmenuitem' ? nextIndex : undefined, + expandedIndex: isExpanded && nextType === 'menu' ? nextIndex : undefined, }; }, () => { this.itemRefs[nextIndex].current.focus(); @@ -115,7 +115,7 @@ class MenuBar extends React.Component { else if(key === 'Enter') { event.preventDefault(); - if(type === 'parentmenuitem') { + if(type === 'menu') { this.setState({ expandedIndex: index, }, () => { @@ -129,20 +129,20 @@ class MenuBar extends React.Component { else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); - if(type === 'parentmenuitem') { + if(type === 'menu') { this.setState({ expandedIndex: index, }, () => { this.itemRefs[index].current.focusFirstChild(); }); } - else if(type === 'menuitemcheckbox') { + else if(type === 'checkbox') { //TODO change state without closing the menu } - else if(type === 'menuitemradio') { + else if(type === 'radiogroup') { //TODO change state without closing the menu } - else if(type === 'menuitem') { + else if(type === 'item') { //TODO activate the item and close the (whole?) menu } } @@ -158,7 +158,7 @@ class MenuBar extends React.Component { return { tabbableIndex: firstIndex, - expandedIndex: isExpanded && firstType === 'parentmenuitem' ? firstIndex : undefined, + expandedIndex: isExpanded && firstType === 'menu' ? firstIndex : undefined, }; }, () => { this.itemRefs[0].current.focus(); @@ -176,7 +176,7 @@ class MenuBar extends React.Component { return { tabbableIndex: items.length - 1, - expandedIndex: isExpanded && lastType === 'parentmenuitem' ? lastIndex : undefined, + expandedIndex: isExpanded && lastType === 'menu' ? lastIndex : undefined, }; }, () => { this.itemRefs[items.length - 1].current.focus(); @@ -215,7 +215,7 @@ class MenuBar extends React.Component { const { tabbableIndex, expandedIndex } = this.state const { type, node, children, orientation, label, labelId, isDisabled } = item; - if(type === 'menuitem') { + if(type === 'item') { return ( ); } - else if(type === 'parentmenuitem') { + else if(type === 'menu') { return ( ); } - else if(type === 'menuitemcheckbox') { + else if(type === 'checkbox') { return ( { @@ -114,7 +114,7 @@ class ParentMenuItem extends React.Component { else if(key === 'Enter') { event.preventDefault(); - if(type === 'parentmenuitem') { + if(type === 'menu') { this.setState({ expandedIndex: index, }, () => { @@ -128,20 +128,20 @@ class ParentMenuItem extends React.Component { else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); - if(type === 'parentmenuitem') { + if(type === 'menu') { this.setState({ expandedIndex: index, }, () => { this.childItemRefs[index].current.focusFirstChild(); }); } - else if(type === 'menuitemchecbox') { + else if(type === 'checbox') { //TODO change state without closing the menu } - else if(type === 'menuitemradio') { + else if(type === 'radiogroup') { //TODO change state without closing the menu } - else if(type === 'menuitem') { + else if(type === 'item') { //TODO activate the item and close the whole menu } } @@ -213,7 +213,7 @@ class ParentMenuItem extends React.Component { const { node, type, children, orientation, label, labelId, isDisabled } = item; const { expandedIndex } = this.state; - if(type === 'menuitem') { + if(type === 'item') { return ( ); } - else if(type === 'parentmenuitem') { + else if(type === 'menu') { return ( ); } - else if(type === 'menuitemcheckbox') { + else if(type === 'checkbox') { return ( Date: Sat, 8 Jan 2022 14:31:32 -0500 Subject: [PATCH 125/286] fix some linter complaints --- .eslintrc.js | 8 -------- src/Menu/MenuBar.jsx | 12 ++++++------ src/Menu/MenuItemRadioGroup.jsx | 6 +++--- src/Menu/ParentMenuItem.jsx | 8 ++++---- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fb48c019..068fff78 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -74,14 +74,6 @@ module.exports = { camelcase: [ 'error', ], - 'capitalized-comments': [ - 'error', - 'always', - { - ignoreInlineComments: true, - ignoreConsecutiveComments: true, - }, - ], curly: [ 'error', 'multi-or-nest', diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index c3ca293f..f3b6e169 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -81,7 +81,7 @@ class MenuBar extends React.Component { const prevIndex = index === 0 ? items.length - 1 : index - 1; const prevItem = items[prevIndex]; const { type: prevType } = prevItem; - + this.setState(prevState => { const { expandedIndex } = prevState; const isExpanded = expandedIndex !== undefined && expandedIndex !== null; @@ -182,9 +182,9 @@ class MenuBar extends React.Component { this.itemRefs[items.length - 1].current.focus(); }); } - else if(key === 'Tab') { + else if(key === 'Tab') this.collapseMenu(); - } + else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -212,7 +212,7 @@ class MenuBar extends React.Component { } renderItem = (item, index) => { - const { tabbableIndex, expandedIndex } = this.state + const { tabbableIndex, expandedIndex } = this.state; const { type, node, children, orientation, label, labelId, isDisabled } = item; if(type === 'item') { @@ -261,7 +261,7 @@ class MenuBar extends React.Component { level={ 0 } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - isTabbable={ isTabbable } + isTabbable={ index === tabbableIndex } ref={ this.itemRefs[index] } > { node } @@ -300,7 +300,7 @@ class MenuBar extends React.Component { this.itemRefs[prevIndex].current.focus(); }); }; - + //TODO not very flexible, assuming the current index is //what is currently expanded... focusNextSibling = () => { diff --git a/src/Menu/MenuItemRadioGroup.jsx b/src/Menu/MenuItemRadioGroup.jsx index f1a536e2..c903f3ea 100644 --- a/src/Menu/MenuItemRadioGroup.jsx +++ b/src/Menu/MenuItemRadioGroup.jsx @@ -28,15 +28,15 @@ function MenuItemRadioGroup(props) { ); } -RadioGroup.propTypes = { +MenuItemRadioGroup.propTypes = { children: PropTypes.node.isRequired, label: PropTypes.string, labelId: PropTypes.string, }; -RadioGroup.defaultProps = { +MenuItemRadioGroup.defaultProps = { label: undefined, labelId: undefined, }; -export default RadioGroup; +export default MenuItemRadioGroup; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 9be2b245..3927dc6b 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -45,7 +45,7 @@ class ParentMenuItem extends React.Component { this.state = { expandedIndex: undefined, }; - + this.itemRef = React.createRef(); this.childItemRefs = items.map(() => React.createRef()); } @@ -160,9 +160,9 @@ class ParentMenuItem extends React.Component { this.focus(); }); } - else if(key === 'Tab') { + else if(key === 'Tab') collapseParent(true); - } + else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -273,7 +273,7 @@ class ParentMenuItem extends React.Component { //---- Misc. --- focus = () => { - this.itemRef.current.focus(); + this.itemRef.current.focus(); }; focusChild = (index) => { From 7f355ef46370ec20b5690b4b5493da5ea7383ace Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Sat, 8 Jan 2022 17:33:12 -0500 Subject: [PATCH 126/286] give the radio options their own proptype --- src/App.jsx | 34 +++++++++++++++++----------------- src/utils/propTypes.js | 14 ++++++++------ 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index b7edab07..91ccb166 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -60,23 +60,23 @@ const MENUITEMS = [ type: 'checkbox', node: 'Checkbox 2', }, -// { -// type: 'separator', -// }, -// { -// type: 'radiogroup', -// children: [ -// { -// node: 'Radio Option 1', -// }, -// { -// node: 'Radio Option 2', -// }, -// { -// node: 'Radio Option 3', -// }, -// ], -// }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, ], }, { diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index d73864da..6182a40f 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -80,18 +80,20 @@ export const MENUITEM_CHECKBOX_PROPTYPE = PropTypes.shape({ export const MENUITEM_RADIOGROUP_PROPTYPE = PropTypes.shape({ type: PropTypes.oneOf([ 'radiogroup' ]).isRequired, - children: PropTypes.arrayOf(PropTypes.shape({ - node: PropTypes.node.isRequired, - //onKeyDown: PropTypes.func.isRequired, //TODO "onActivate"? who "owns" the event? - isDisabled: PropTypes.bool, - isChecked: PropTypes.bool, - })).isRequired, + children: PropTypes.arrayOf(MENUITEM_RADIO_PROPTYPE).isRequired, label: PropTypes.string, labelId: PropTypes.string, //TODO default checked index? //TODO function to automatically handle which radio is checked? }); +export const MENUITEM_RADIO_PROPTYPE = PropTypes.shape({ + node: PropTypes.node.isRequired, + //onKeyDown: PropTypes.func.isRequired, //TODO "onActivate"? who "owns" the event? + isDisabled: PropTypes.bool, + isChecked: PropTypes.bool, +}); + export const MENUITEM_SEPARATOR_PROPTYPE = PropTypes.shape({ type: PropTypes.oneOf([ 'separator' ]).isRequired, node: PropTypes.node, From 8dd1340d6b279f52a067881fa67d37cf3d11b7fb Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Sat, 8 Jan 2022 18:09:33 -0500 Subject: [PATCH 127/286] begin rendering menuitemradios --- src/Menu/MenuBar.jsx | 11 +++++++++++ src/Menu/MenuItemRadio.jsx | 8 ++++---- src/Menu/MenuItemRadioGroup.jsx | 24 +++++++++++++++++++++--- src/Menu/ParentMenuItem.jsx | 11 +++++++++++ src/utils/propTypes.js | 14 +++++++------- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f3b6e169..11b071ec 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -6,6 +6,7 @@ import MenuItem from 'src/Menu/MenuItem'; import ParentMenuItem from 'src/Menu/ParentMenuItem'; import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; +import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -275,6 +276,16 @@ class MenuBar extends React.Component {
                                      ); } + else if(type === 'radiogroup') { + return ( + + ); + } }; //---- Misc. ---- diff --git a/src/Menu/MenuItemRadio.jsx b/src/Menu/MenuItemRadio.jsx index 0c16f8cb..35c0572c 100644 --- a/src/Menu/MenuItemRadio.jsx +++ b/src/Menu/MenuItemRadio.jsx @@ -6,7 +6,7 @@ const MenuItemRadio = React.forwardRef(function MenuItemRadio(props, ref) { return (
                                    • { + //TODO: flags should come from react state? + const { node, isDisabled, isChecked } = option; + + return ( + + { node } + + ); + }); return (
                                    • @@ -22,14 +40,14 @@ function MenuItemRadioGroup(props) { aria-label={ label } aria-labelledby={ labelId } > - { children } + { optionNodes }
                                    ); } MenuItemRadioGroup.propTypes = { - children: PropTypes.node.isRequired, + options: PropTypes.arrayOf(MENUITEM_RADIO_PROPTYPE).isRequired, label: PropTypes.string, labelId: PropTypes.string, }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 3927dc6b..55da4e55 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -6,6 +6,7 @@ import Menu from 'src/Menu/Menu'; import MenuItem from 'src/Menu/MenuItem'; import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; +import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -269,6 +270,16 @@ class ParentMenuItem extends React.Component { ); } + else if(type === 'radiogroup') { + return ( + + ); + } }; //---- Misc. --- diff --git a/src/utils/propTypes.js b/src/utils/propTypes.js index 6182a40f..752c6d2a 100644 --- a/src/utils/propTypes.js +++ b/src/utils/propTypes.js @@ -78,6 +78,13 @@ export const MENUITEM_CHECKBOX_PROPTYPE = PropTypes.shape({ ]), }); +export const MENUITEM_RADIO_PROPTYPE = PropTypes.shape({ + node: PropTypes.node.isRequired, + //onKeyDown: PropTypes.func.isRequired, //TODO "onActivate"? who "owns" the event? + isDisabled: PropTypes.bool, + isChecked: PropTypes.bool, +}); + export const MENUITEM_RADIOGROUP_PROPTYPE = PropTypes.shape({ type: PropTypes.oneOf([ 'radiogroup' ]).isRequired, children: PropTypes.arrayOf(MENUITEM_RADIO_PROPTYPE).isRequired, @@ -87,13 +94,6 @@ export const MENUITEM_RADIOGROUP_PROPTYPE = PropTypes.shape({ //TODO function to automatically handle which radio is checked? }); -export const MENUITEM_RADIO_PROPTYPE = PropTypes.shape({ - node: PropTypes.node.isRequired, - //onKeyDown: PropTypes.func.isRequired, //TODO "onActivate"? who "owns" the event? - isDisabled: PropTypes.bool, - isChecked: PropTypes.bool, -}); - export const MENUITEM_SEPARATOR_PROPTYPE = PropTypes.shape({ type: PropTypes.oneOf([ 'separator' ]).isRequired, node: PropTypes.node, From bd2329cdedde29e9859d0e7199aef60f69b12703 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Sat, 8 Jan 2022 18:12:33 -0500 Subject: [PATCH 128/286] begin passing down index, level, and onKeyDown to radio options --- src/Menu/MenuItemRadio.jsx | 6 +++--- src/Menu/MenuItemRadioGroup.jsx | 7 ++++++- src/Menu/ParentMenuItem.jsx | 3 +++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuItemRadio.jsx b/src/Menu/MenuItemRadio.jsx index 35c0572c..b09621d6 100644 --- a/src/Menu/MenuItemRadio.jsx +++ b/src/Menu/MenuItemRadio.jsx @@ -22,9 +22,9 @@ const MenuItemRadio = React.forwardRef(function MenuItemRadio(props, ref) { MenuItemRadio.propTypes = { children: PropTypes.node.isRequired, -// index: PropTypes.number.isRequired, -// level: PropTypes.number.isRequired, -// onKeyDown: PropTypes.func.isRequired, + index: PropTypes.number.isRequired, + level: PropTypes.number.isRequired, + onKeyDown: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, isChecked: PropTypes.bool, diff --git a/src/Menu/MenuItemRadioGroup.jsx b/src/Menu/MenuItemRadioGroup.jsx index 748afec4..24c734c6 100644 --- a/src/Menu/MenuItemRadioGroup.jsx +++ b/src/Menu/MenuItemRadioGroup.jsx @@ -19,7 +19,7 @@ import { MENUITEM_RADIO_PROPTYPE } from 'src/utils/propTypes'; * https://www.w3.org/TR/wai-aria-1.1/#menuitemradio */ function MenuItemRadioGroup(props) { - const { options, label, labelId } = props; + const { options, index, level, onKeyDown, label, labelId } = props; const optionNodes = options.map((option, i) => { //TODO: flags should come from react state? const { node, isDisabled, isChecked } = option; @@ -27,6 +27,9 @@ function MenuItemRadioGroup(props) { return ( { node } @@ -48,6 +51,8 @@ function MenuItemRadioGroup(props) { MenuItemRadioGroup.propTypes = { options: PropTypes.arrayOf(MENUITEM_RADIO_PROPTYPE).isRequired, + index: PropTypes.number.isRequired, + level: PropTypes.number.isRequired, label: PropTypes.string, labelId: PropTypes.string, }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 55da4e55..834fb855 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -275,6 +275,9 @@ class ParentMenuItem extends React.Component { From 2891f9758f41d76ace53ec27b1f8030e8c974520 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 10 Jan 2022 13:32:24 -0500 Subject: [PATCH 129/286] first attempt at passing down index, level, and ref to separators and radios --- src/Menu/MenuBar.jsx | 23 ++++++++++++++++++++--- src/Menu/MenuItemRadioGroup.jsx | 16 +++++++++++++++- src/Menu/MenuItemSeparator.jsx | 18 ++++++++++++++---- src/Menu/ParentMenuItem.jsx | 23 +++++++++++++++++++---- 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 11b071ec..b8134197 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -42,7 +42,16 @@ class MenuBar extends React.Component { expandedIndex: undefined, }; - this.itemRefs = items.map(() => React.createRef()); + this.itemRefs = []; + + items.forEach(item => { + const { type, children } = item; + + if(type === 'radiogroup') + this.itemRefs.push(children.map(() => React.createRef())); + else + this.itemRefs.push(React.createRef()); + }); } //---- Events ---- @@ -198,7 +207,7 @@ class MenuBar extends React.Component { const { items, orientation, label, labelId } = this.props; const itemNodes = items.map(this.renderItem); - console.log(this.props, this.state); + console.log(this.props, this.state, this.itemRefs); return (
                                      + { node } ); @@ -280,6 +295,8 @@ class MenuBar extends React.Component { return ( diff --git a/src/Menu/MenuItemSeparator.jsx b/src/Menu/MenuItemSeparator.jsx index 183bf48f..359e1c75 100644 --- a/src/Menu/MenuItemSeparator.jsx +++ b/src/Menu/MenuItemSeparator.jsx @@ -16,19 +16,29 @@ import PropTypes from 'prop-types'; * * See: * https://www.w3.org/TR/wai-aria-practices-1.1/#menu + * + * TODO: is it necessary to attach a ref to these? */ -function MenuItemSeparator(props) { - const { children, orientation } = props; +const MenuItemSeparator = React.forwardRef(function MenuItemSeparator(props, ref) { + const { children, index, level, orientation } = props; return ( -
                                    • +
                                    • { children }
                                    • ); -} +}); MenuItemSeparator.propTypes = { children: PropTypes.node, + index: PropTypes.number.isRequired, //TODO is this necessary? + level: PropTypes.number.isRequired, //TODO is this necessary? orientation: PropTypes.oneOf([ 'horizontal', 'vertical' ]), }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 834fb855..aff20c6b 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -48,7 +48,16 @@ class ParentMenuItem extends React.Component { }; this.itemRef = React.createRef(); - this.childItemRefs = items.map(() => React.createRef()); + this.childItemRefs = []; + + items.forEach(item => { + const { type, children } = item; + + if(type === 'radiogroup') + this.childItemRefs.push(children.map(() => React.createRef())); + else + this.childItemRefs.push(React.createRef()); + }); } //---- Events ---- @@ -180,7 +189,7 @@ class ParentMenuItem extends React.Component { } = this.props; const itemNodes = items.map(this.renderItem); - console.log(this.props, this.state); + console.log(this.props, this.state, this.itemRef, this.childItemRefs); return (
                                    • @@ -265,7 +274,13 @@ class ParentMenuItem extends React.Component { } else if(type === 'separator') { return ( - + { node } ); @@ -276,8 +291,8 @@ class ParentMenuItem extends React.Component { key={ index } options={ children } index={ index } - onKeyDown={ this.onChildKeyDown } level={ level + 1 } + onKeyDown={ this.onChildKeyDown } label={ label } labelId={ labelId } /> From 75cc7ea5a78a0a430013bc6324a1ce52977c7bdb Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 10 Jan 2022 14:52:31 -0500 Subject: [PATCH 130/286] try a slightly different way of rendering items --- src/Menu/MenuBar.jsx | 218 +++++++++++++++++++------------- src/Menu/MenuItemRadioGroup.jsx | 40 +----- src/Menu/ParentMenuItem.jsx | 216 ++++++++++++++++++------------- 3 files changed, 264 insertions(+), 210 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index b8134197..222b987a 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -7,6 +7,7 @@ import ParentMenuItem from 'src/Menu/ParentMenuItem'; import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; +import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -47,8 +48,11 @@ class MenuBar extends React.Component { items.forEach(item => { const { type, children } = item; - if(type === 'radiogroup') - this.itemRefs.push(children.map(() => React.createRef())); + if(type === 'radiogroup') { + children.forEach(() => { + this.itemRefs.push(React.createRef()); + }); + } else this.itemRefs.push(React.createRef()); }); @@ -63,7 +67,8 @@ class MenuBar extends React.Component { const { type } = item; console.log(index, item); - + + //TODO separators shouldn't be focusable if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); @@ -204,8 +209,7 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { - const { items, orientation, label, labelId } = this.props; - const itemNodes = items.map(this.renderItem); + const { orientation, label, labelId } = this.props; console.log(this.props, this.state, this.itemRefs); @@ -216,93 +220,133 @@ class MenuBar extends React.Component { aria-labelledby={ labelId } aria-label={ label } > - { itemNodes } + { this.renderItems() }
                                    ); } - renderItem = (item, index) => { + renderItems = () => { + const { items } = this.props; const { tabbableIndex, expandedIndex } = this.state; - const { type, node, children, orientation, label, labelId, isDisabled } = item; - - if(type === 'item') { - return ( - - { node } - - ); - } - else if(type === 'menu') { - return ( - - { node } - - ); - } - else if(type === 'checkbox') { - return ( - - { node } - - ); - } - else if(type === 'separator') { - return ( - - { node } - - ); - } - else if(type === 'radiogroup') { - return ( - - ); - } + const itemNodes = []; + let refIndex = 0; + + items.forEach((item, i) => { + const { type, node, children, orientation, label, labelId, isDisabled } = item; + + if(type === 'item') { + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'menu') { + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'checkbox') { + //TODO isChecked? + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'separator') { + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'radiogroup') { + const radioNodes = []; + + children.forEach((radioItem, j) => { + const { node, isDisabled } = radioItem; + + //TODO isChecked? + radioNodes.push( + + { node } + + ); + + refIndex++; + }); + + itemNodes.push( + + { radioNodes } + + ); + } + }); + + return itemNodes; }; //---- Misc. ---- diff --git a/src/Menu/MenuItemRadioGroup.jsx b/src/Menu/MenuItemRadioGroup.jsx index c977c482..930a5384 100644 --- a/src/Menu/MenuItemRadioGroup.jsx +++ b/src/Menu/MenuItemRadioGroup.jsx @@ -4,9 +4,6 @@ import PropTypes from 'prop-types'; //Components and Styles import MenuItemRadio from 'src/Menu/MenuItemRadio'; -//Misc. -import { MENUITEM_RADIO_PROPTYPE } from 'src/utils/propTypes'; - /* * Note that if a menu or menubar contains more than one group of * menuitemradio elements or if the menu has a group of menuitemradio @@ -17,38 +14,9 @@ import { MENUITEM_RADIO_PROPTYPE } from 'src/utils/propTypes'; * * See: * https://www.w3.org/TR/wai-aria-1.1/#menuitemradio - * - * TODO: how should this component accept refs? as it currently stands, - * the renderItem functions that map items to components can't just - * blindly accept something from this.itemRefs or this.childItemRefs - * - items that follow a radio group will need to know the existence of - * preceding radio groups and the number of elements they possess - * because radio refs are currently given their own array of refs - * so they can be all passed into a radio group at once. - * - * if the radio refs were "flattened", then we can't just use an Array.map - * to render out items because there isn't a one-to-one relation - * between elements in the items array and components - radiogroups - * can possess one or more radio options, and radiogroups are currently - * considered a single "item" in the items array */ function MenuItemRadioGroup(props) { - const { options, index, level, onKeyDown, label, labelId } = props; - const optionNodes = options.map((option, i) => { - //TODO: flags should come from react state? - const { node, isDisabled, isChecked } = option; - - return ( - - { node } - - ); - }); + const { children, label, labelId } = props; return (
                                  • @@ -57,16 +25,14 @@ function MenuItemRadioGroup(props) { aria-label={ label } aria-labelledby={ labelId } > - { optionNodes } + { children }
                                  ); } MenuItemRadioGroup.propTypes = { - options: PropTypes.arrayOf(MENUITEM_RADIO_PROPTYPE).isRequired, - index: PropTypes.number.isRequired, - level: PropTypes.number.isRequired, + children: PropTypes.node.isRequired, label: PropTypes.string, labelId: PropTypes.string, }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index aff20c6b..30f487c8 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -7,6 +7,7 @@ import MenuItem from 'src/Menu/MenuItem'; import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; +import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -53,8 +54,11 @@ class ParentMenuItem extends React.Component { items.forEach(item => { const { type, children } = item; - if(type === 'radiogroup') - this.childItemRefs.push(children.map(() => React.createRef())); + if(type === 'radiogroup') { + children.forEach(() => { + this.childItemRefs.push(React.createRef()); + }); + } else this.childItemRefs.push(React.createRef()); }); @@ -70,7 +74,8 @@ class ParentMenuItem extends React.Component { const { type } = item; console.log(index, level); - + + //TODO separators shouldn't be focusable if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); const newIndex = index === 0 ? items.length - 1 : index - 1; @@ -183,11 +188,10 @@ class ParentMenuItem extends React.Component { //---- Rendering ---- render() { const { - children, items, index, level, onKeyDown, + children, index, level, onKeyDown, orientation, label, labelId, isExpanded, isDisabled, isTabbable, } = this.props; - const itemNodes = items.map(this.renderItem); console.log(this.props, this.state, this.itemRef, this.childItemRefs); @@ -212,94 +216,134 @@ class ParentMenuItem extends React.Component { label={ label } labelId={ labelId } > - { itemNodes } + { this.renderItems() } ); } - renderItem = (item, index) => { - const { level, focusNextMenubarItem } = this.props; - const { node, type, children, orientation, label, labelId, isDisabled } = item; - const { expandedIndex } = this.state; - - if(type === 'item') { - return ( - - { node } - - ); - } - else if(type === 'menu') { - return ( - - { node } - - ); - } - else if(type === 'checkbox') { - return ( - - { node } - - ); - } - else if(type === 'separator') { - return ( - - { node } - - ); - } - else if(type === 'radiogroup') { - return ( - - ); - } + renderItems = () => { + const { items, level, focusNextMenubarItem } = this.props; + const { tabbableIndex, expandedIndex } = this.state; + const itemNodes = []; + let refIndex = 0; + + items.forEach((item, i) => { + const { type, node, children, orientation, label, labelId, isDisabled } = item; + + if(type === 'item') { + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'menu') { + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'checkbox') { + //TODO isChecked? + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'separator') { + itemNodes.push( + + { node } + + ); + + refIndex++; + } + else if(type === 'radiogroup') { + const radioNodes = []; + + children.forEach((radioItem, j) => { + const { node, isDisabled } = radioItem; + + //TODO isChecked? + radioNodes.push( + + { node } + + ); + + refIndex++; + }); + + console.log(radioNodes, children); + + itemNodes.push( + + { radioNodes } + + ); + } + }); + + return itemNodes; }; + //---- Misc. --- focus = () => { this.itemRef.current.focus(); From 81964876d746e8fb209d67c05850c4f6e4bfa945 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 11 Jan 2022 12:37:56 -0500 Subject: [PATCH 131/286] begin storing two separate indices for items - one for the items prop and one for refs --- src/App.jsx | 40 ++++++++++++++++++++++++++++++++++ src/Menu/MenuBar.jsx | 18 ++++++++++----- src/Menu/MenuItem.jsx | 4 +++- src/Menu/MenuItemCheckbox.jsx | 4 +++- src/Menu/MenuItemRadio.jsx | 6 ++++- src/Menu/MenuItemSeparator.jsx | 4 +++- src/Menu/ParentMenuItem.jsx | 25 ++++++++++++--------- 7 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 91ccb166..df833e40 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -77,6 +77,46 @@ const MENUITEMS = [ }, ], }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, ], }, { diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 222b987a..7885f422 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -66,7 +66,7 @@ class MenuBar extends React.Component { const item = items[index]; const { type } = item; - console.log(index, item); + console.log(index, item, target.dataset); //TODO separators shouldn't be focusable if(key === 'ArrowUp' || key === 'Up') { @@ -238,7 +238,8 @@ class MenuBar extends React.Component { itemNodes.push( { this.itemRef.current.focus(); From 3dbd501f875d4180ffb377ccd91ac17982556466 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 13:44:34 -0500 Subject: [PATCH 132/286] begin skipping separators in parentmenuitem --- src/Menu/MenuBar.jsx | 3 ++- src/Menu/ParentMenuItem.jsx | 38 ++++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 7885f422..8b2cfe8b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -63,10 +63,11 @@ class MenuBar extends React.Component { const { items } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); + const refIndex = Number.parseInt(target.dataset.refindex, 10); const item = items[index]; const { type } = item; - console.log(index, item, target.dataset); + console.log(index, refIndex, item); //TODO separators shouldn't be focusable if(key === 'ArrowUp' || key === 'Up') { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 43e35a2c..34e280cb 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -70,22 +70,21 @@ class ParentMenuItem extends React.Component { const { items, collapseParent, focusPrevSibling, focusNextMenubarItem } = this.props; const { key, target } = event; const index = Number.parseInt(target.dataset.index, 10); + const refIndex = Number.parseInt(target.dataset.refindex, 10); const level = Number.parseInt(target.dataset.level, 10); const item = items[index]; const { type } = item; - console.log(index, level, target.dataset); + console.log(index, refIndex, level, item); //TODO separators shouldn't be focusable if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - const newIndex = index === 0 ? items.length - 1 : index - 1; - this.focusChild(newIndex); + this.focusPrevChild(refIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - const newIndex = index === items.length - 1 ? 0 : index + 1; - this.focusChild(newIndex); + this.focusNextChild(refIndex); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); @@ -358,6 +357,30 @@ class ParentMenuItem extends React.Component { this.childItemRefs[index].current.focus(); }; + focusPrevChild = (refIndex) => { + let prevIndex = refIndex === 0 ? this.childItemRefs.length - 1 : refIndex - 1; + let prevRef = this.childItemRefs[prevIndex]; + + while(this.isSeparatorRef(prevRef) && prevIndex !== refIndex) { + prevIndex = prevIndex === 0 ? this.childItemRefs.length - 1 : prevIndex - 1; + prevRef = this.childItemRefs[prevIndex]; + }; + + prevRef.current.focus(); + }; + + focusNextChild = (refIndex) => { + let nextIndex = refIndex === this.childItemRefs.length - 1 ? 0 : refIndex + 1; + let nextRef = this.childItemRefs[nextIndex]; + + while(this.isSeparatorRef(nextRef) && nextIndex !== refIndex) { + nextIndex = nextIndex === this.childItemRefs.length - 1 ? 0 : nextIndex + 1; + nextRef = this.childItemRefs[nextIndex]; + } + + nextRef.current.focus(); + }; + focusFirstChild = () => { this.focusChild(0); }; @@ -381,6 +404,11 @@ class ParentMenuItem extends React.Component { callback(); }); }; + + isSeparatorRef = (ref) => { + const { current } = ref; + return current instanceof HTMLElement && current.getAttribute('role') === 'separator'; + }; } export default ParentMenuItem; From f922aef81f8460293756333e72a94804a73490a6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 14:19:17 -0500 Subject: [PATCH 133/286] use a much more complicated menubar for testing --- src/App.jsx | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/src/App.jsx b/src/App.jsx index df833e40..d3b571cf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -40,6 +40,26 @@ const MENUITEMS = [ type: 'menu', node: 'Parent Menuitem 1', children: [ + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, { type: 'item', node: 'Hello world!', @@ -117,6 +137,26 @@ const MENUITEMS = [ type: 'checkbox', node: 'Checkbox 2', }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, ], }, { @@ -147,6 +187,41 @@ const MENUITEMS = [ type: 'item', node: 'Hello world!', }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + ], }, { @@ -177,6 +252,71 @@ const MENUITEMS = [ type: 'item', node: 'Hello world!', }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, ], }, { @@ -211,6 +351,74 @@ const MENUITEMS = [ }, ], }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'menu', + node: 'Parent Menuitem 4', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + ], + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, ]; function App() { From 52467cb5220ba076327d0eb92a16c6c127c62b80 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 14:47:30 -0500 Subject: [PATCH 134/286] new next/prev focus functions for MenuBar --- src/Menu/MenuBar.jsx | 81 +++++++++++++++++++++++-------------- src/Menu/ParentMenuItem.jsx | 10 ++--- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 8b2cfe8b..415b7b4b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -11,6 +11,7 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { isSeparatorRef } from 'src/Menu/utils'; /* * Note: @@ -94,39 +95,11 @@ class MenuBar extends React.Component { } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - const prevIndex = index === 0 ? items.length - 1 : index - 1; - const prevItem = items[prevIndex]; - const { type: prevType } = prevItem; - - this.setState(prevState => { - const { expandedIndex } = prevState; - const isExpanded = expandedIndex !== undefined && expandedIndex !== null; - - return { - tabbableIndex: prevIndex, - expandedIndex: isExpanded && prevType === 'menu' ? prevIndex : undefined, - }; - }, () => { - this.itemRefs[prevIndex].current.focus(); - }); + this.focusPrevChild(refIndex); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - const nextIndex = index === items.length - 1 ? 0 : index + 1; - const nextItem = items[nextIndex]; - const { type: nextType } = nextItem; - - this.setState(prevState => { - const { expandedIndex } = prevState; - const isExpanded = expandedIndex !== undefined && expandedIndex !== null; - - return { - tabbableIndex: nextIndex, - expandedIndex: isExpanded && nextType === 'menu' ? nextIndex : undefined, - }; - }, () => { - this.itemRefs[nextIndex].current.focus(); - }); + this.focusNextChild(refIndex); } else if(key === 'Enter') { event.preventDefault(); @@ -368,6 +341,54 @@ class MenuBar extends React.Component { }); }; + focusPrevChild = (refIndex) => { + let prevIndex = refIndex === 0 ? this.itemRefs.length - 1 : refIndex - 1; + let prevRef = this.itemRefs[prevIndex]; + + //TODO test edge cases, e.g. single-element separator and single-element non-separator? + while(isSeparatorRef(prevRef) && prevIndex !== refIndex) { + prevIndex = prevIndex === 0 ? this.itemRefs.length - 1 : prevIndex - 1; + prevRef = this.itemRefs[prevIndex]; + } + + this.setState(state => { + const { expandedIndex } = state; + const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; + + //TODO would be nice if there was a better way to map ref indices to item indices + //and vice-versa, checking instanceof ParentMenuItem almost feels sort of abusive + //wrt using refs + return { + tabbableIndex: prevIndex, + expandedIndex: prevRef.current instanceof ParentMenuItem && wasExpanded ? prevIndex : undefined, + }; + }, () => { + prevRef.current.focus(); + }); + }; + + focusNextChild = (refIndex) => { + let nextIndex = refIndex === this.itemRefs.length - 1 ? 0 : refIndex + 1; + let nextRef = this.itemRefs[nextIndex]; + + while(isSeparatorRef(nextRef) && nextIndex !== refIndex) { + nextIndex = nextIndex === this.itemRefs.length - 1 ? 0 : nextIndex + 1; + nextRef = this.itemRefs[nextIndex]; + } + + this.setState(state => { + const { expandedIndex } = state; + const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; + + return { + tabbableIndex: nextIndex, + expandedIndex: nextRef.current instanceof ParentMenuItem && wasExpanded ? nextIndex : undefined, + }; + }, () => { + nextRef.current.focus(); + }); + }; + focusPrevSibling = (index, autoExpand) => { const { items } = this.props; const prevIndex = index === 0 ? items.length - 1 : index - 1; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 34e280cb..10faea5f 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -11,6 +11,7 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { isSeparatorRef } from 'src/Menu/utils'; class ParentMenuItem extends React.Component { static propTypes = { @@ -361,7 +362,7 @@ class ParentMenuItem extends React.Component { let prevIndex = refIndex === 0 ? this.childItemRefs.length - 1 : refIndex - 1; let prevRef = this.childItemRefs[prevIndex]; - while(this.isSeparatorRef(prevRef) && prevIndex !== refIndex) { + while(isSeparatorRef(prevRef) && prevIndex !== refIndex) { prevIndex = prevIndex === 0 ? this.childItemRefs.length - 1 : prevIndex - 1; prevRef = this.childItemRefs[prevIndex]; }; @@ -373,7 +374,7 @@ class ParentMenuItem extends React.Component { let nextIndex = refIndex === this.childItemRefs.length - 1 ? 0 : refIndex + 1; let nextRef = this.childItemRefs[nextIndex]; - while(this.isSeparatorRef(nextRef) && nextIndex !== refIndex) { + while(isSeparatorRef(nextRef) && nextIndex !== refIndex) { nextIndex = nextIndex === this.childItemRefs.length - 1 ? 0 : nextIndex + 1; nextRef = this.childItemRefs[nextIndex]; } @@ -404,11 +405,6 @@ class ParentMenuItem extends React.Component { callback(); }); }; - - isSeparatorRef = (ref) => { - const { current } = ref; - return current instanceof HTMLElement && current.getAttribute('role') === 'separator'; - }; } export default ParentMenuItem; From d2bc681e8d812398ce89321ce771011016a2c2f8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 15:38:03 -0500 Subject: [PATCH 135/286] use refIndex for menubar"s up and down arrow --- src/Menu/MenuBar.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 415b7b4b..76c5b011 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -76,9 +76,9 @@ class MenuBar extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: index, + expandedIndex: refIndex, }, () => { - this.itemRefs[index].current.focusLastChild(); + this.itemRefs[refIndex].current.focusLastChild(); }); } } @@ -87,9 +87,9 @@ class MenuBar extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: index, + expandedIndex: refIndex, }, () => { - this.itemRefs[index].current.focusFirstChild(); + this.itemRefs[refIndex].current.focusFirstChild(); }); } } From c74443915b2fe35a8c447d74a0e1df89fcc28609 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 15:42:25 -0500 Subject: [PATCH 136/286] parentmenuitem"s focus first and last methods now account for separators --- src/Menu/ParentMenuItem.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 10faea5f..fb79ad09 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -383,12 +383,11 @@ class ParentMenuItem extends React.Component { }; focusFirstChild = () => { - this.focusChild(0); + this.focusNextChild(this.childItemRefs.length - 1); }; focusLastChild = () => { - const { items } = this.props; - this.focusChild(items.length - 1); + this.focusPrevChild(0); }; collapseMenu = (collapseAll, callback) => { From a17d6c2dd667687a453b6338c7ee077df9952143 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 15:47:51 -0500 Subject: [PATCH 137/286] menubar space and enter now use refIndex --- src/Menu/MenuBar.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 76c5b011..f113a65a 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -106,9 +106,9 @@ class MenuBar extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: index, + expandedIndex: refIndex, }, () => { - this.itemRefs[index].current.focusFirstChild(); + this.itemRefs[refIndex].current.focusFirstChild(); }); } else { @@ -120,9 +120,9 @@ class MenuBar extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: index, + expandedIndex: refIndex, }, () => { - this.itemRefs[index].current.focusFirstChild(); + this.itemRefs[refIndex].current.focusFirstChild(); }); } else if(type === 'checkbox') { From b6a510a811976cf104a61b005e2c8c790ffb761d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 15:53:10 -0500 Subject: [PATCH 138/286] menubar home and end now account for separators and radios --- src/Menu/MenuBar.jsx | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f113a65a..9e3e36e0 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -137,39 +137,11 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); - const firstIndex = 0; - const firstItem = items[firstIndex]; - const { type: firstType } = firstItem; - - this.setState(prevState => { - const { expandedIndex } = prevState; - const isExpanded = expandedIndex !== undefined && expandedIndex !== null; - - return { - tabbableIndex: firstIndex, - expandedIndex: isExpanded && firstType === 'menu' ? firstIndex : undefined, - }; - }, () => { - this.itemRefs[0].current.focus(); - }); + this.focusFirstChild(); } else if(key === 'End') { event.preventDefault(); - const lastIndex = items.length - 1; - const lastItem = items[lastIndex]; - const { type: lastType } = lastItem; - - this.setState(prevState => { - const { expandedIndex } = prevState; - const isExpanded = expandedIndex !== undefined && expandedIndex !== null; - - return { - tabbableIndex: items.length - 1, - expandedIndex: isExpanded && lastType === 'menu' ? lastIndex : undefined, - }; - }, () => { - this.itemRefs[items.length - 1].current.focus(); - }); + this.focusLastChild(); } else if(key === 'Tab') this.collapseMenu(); @@ -389,6 +361,14 @@ class MenuBar extends React.Component { }); }; + focusFirstChild = () => { + this.focusNextChild(this.itemRefs.length - 1); + }; + + focusLastChild = () => { + this.focusPrevChild(0); + }; + focusPrevSibling = (index, autoExpand) => { const { items } = this.props; const prevIndex = index === 0 ? items.length - 1 : index - 1; From eedafa9d7a61e80f62455eb5e545df8e7ab713bc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 16:05:54 -0500 Subject: [PATCH 139/286] parentmenuitem arrow right takes into account separators and radios --- src/App.jsx | 20 ++++++++++++++++++++ src/Menu/MenuBar.jsx | 4 ++++ src/Menu/ParentMenuItem.jsx | 5 +++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index d3b571cf..8d6a36a9 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -383,6 +383,26 @@ const MENUITEMS = [ type: 'item', node: 'Hello world!', }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, { type: 'menu', node: 'Nested Parent Menuitem', diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 9e3e36e0..6fd08da0 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -384,6 +384,9 @@ class MenuBar extends React.Component { //TODO not very flexible, assuming the current index is //what is currently expanded... focusNextSibling = () => { + const { expandedIndex } = this.state; + this.focusNextChild(expandedIndex); + /* const { items } = this.props; const { expandedIndex } = this.state; const nextIndex = expandedIndex === items.length - 1 ? 0 : expandedIndex + 1; @@ -396,6 +399,7 @@ class MenuBar extends React.Component { }, () => { this.itemRefs[nextIndex].current.focus(); }); + */ }; } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index fb79ad09..8baeaaba 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -96,6 +96,7 @@ class ParentMenuItem extends React.Component { //event, but we're not focusing the previous sibling of the //menuitem executing this event. we're focusing that menuitem's //parent's previous sibling + //FIXME: broken, we don't know the item's parent's refIndex collapseParent(false, () => { focusPrevSibling(this.props.index, true); }); @@ -111,9 +112,9 @@ class ParentMenuItem extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: index, + expandedIndex: refIndex, }, () => { - this.childItemRefs[index].current.focusFirstChild(); + this.childItemRefs[refIndex].current.focusFirstChild(); }); } else { From 48295ae146f0f277162745b63809769ea14f587a Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 16:07:09 -0500 Subject: [PATCH 140/286] parentmenuitem enter and space take into account separators and radios --- src/Menu/ParentMenuItem.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8baeaaba..99c16416 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -133,9 +133,9 @@ class ParentMenuItem extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: index, + expandedIndex: refIndex, }, () => { - this.childItemRefs[index].current.focusFirstChild(); + this.childItemRefs[refIndex].current.focusFirstChild(); }); } else { @@ -147,9 +147,9 @@ class ParentMenuItem extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: index, + expandedIndex: refIndex, }, () => { - this.childItemRefs[index].current.focusFirstChild(); + this.childItemRefs[refIndex].current.focusFirstChild(); }); } else if(type === 'checbox') { @@ -179,7 +179,6 @@ class ParentMenuItem extends React.Component { } else if(key === 'Tab') collapseParent(true); - else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins From 36ef050e1c09090d20b3769666cd8354b2b9c2b4 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 16:13:13 -0500 Subject: [PATCH 141/286] nobody uses this anymore --- src/Menu/ParentMenuItem.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 99c16416..a05fe3c4 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -354,10 +354,6 @@ class ParentMenuItem extends React.Component { this.itemRef.current.focus(); }; - focusChild = (index) => { - this.childItemRefs[index].current.focus(); - }; - focusPrevChild = (refIndex) => { let prevIndex = refIndex === 0 ? this.childItemRefs.length - 1 : refIndex - 1; let prevRef = this.childItemRefs[prevIndex]; From 04b2933d50db746b87e649e4e2b959adc1d35ce3 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 12 Jan 2022 19:10:25 -0500 Subject: [PATCH 142/286] fix parentmenuitem arrow left --- src/Menu/MenuBar.jsx | 19 ++++--------------- src/Menu/ParentMenuItem.jsx | 3 +-- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 6fd08da0..14d7f683 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -208,7 +208,7 @@ class MenuBar extends React.Component { level={ 0 } onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } - focusPrevSibling={ this.focusPrevSibling } + focusPrevSibling={ this.focusPrevChild } focusNextMenubarItem={ this.focusNextSibling } orientation={ orientation } label={ label } @@ -313,7 +313,7 @@ class MenuBar extends React.Component { }); }; - focusPrevChild = (refIndex) => { + focusPrevChild = (refIndex, autoExpand = false) => { let prevIndex = refIndex === 0 ? this.itemRefs.length - 1 : refIndex - 1; let prevRef = this.itemRefs[prevIndex]; @@ -326,13 +326,14 @@ class MenuBar extends React.Component { this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; + const _autoExpand = prevRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); //TODO would be nice if there was a better way to map ref indices to item indices //and vice-versa, checking instanceof ParentMenuItem almost feels sort of abusive //wrt using refs return { tabbableIndex: prevIndex, - expandedIndex: prevRef.current instanceof ParentMenuItem && wasExpanded ? prevIndex : undefined, + expandedIndex: _autoExpand ? prevIndex : undefined, }; }, () => { prevRef.current.focus(); @@ -369,18 +370,6 @@ class MenuBar extends React.Component { this.focusPrevChild(0); }; - focusPrevSibling = (index, autoExpand) => { - const { items } = this.props; - const prevIndex = index === 0 ? items.length - 1 : index - 1; - - this.setState({ - tabbableIndex: prevIndex, - expandedIndex: autoExpand ? prevIndex : undefined, - }, () => { - this.itemRefs[prevIndex].current.focus(); - }); - }; - //TODO not very flexible, assuming the current index is //what is currently expanded... focusNextSibling = () => { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index a05fe3c4..22bb04d7 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -96,9 +96,8 @@ class ParentMenuItem extends React.Component { //event, but we're not focusing the previous sibling of the //menuitem executing this event. we're focusing that menuitem's //parent's previous sibling - //FIXME: broken, we don't know the item's parent's refIndex collapseParent(false, () => { - focusPrevSibling(this.props.index, true); + focusPrevSibling(this.props.refIndex, true); }); } else { From d1593d6a04060a2ed98dfe823391bf837bce6edc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 13 Jan 2022 13:25:39 -0500 Subject: [PATCH 143/286] try giving menu items their full position --- src/Menu/MenuBar.jsx | 25 +++++++++++++++++++++++-- src/Menu/ParentMenuItem.jsx | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 14d7f683..5bc34798 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -175,18 +175,23 @@ class MenuBar extends React.Component { const { items } = this.props; const { tabbableIndex, expandedIndex } = this.state; const itemNodes = []; + let position = []; let refIndex = 0; items.forEach((item, i) => { const { type, node, children, orientation, label, labelId, isDisabled } = item; - + if(type === 'item') { + position = position.slice(0); + position[0] = refIndex; + itemNodes.push( { const { node, isDisabled } = radioItem; - + + position = position.slice(0); + position[0] = refIndex; + //TODO isChecked? radioNodes.push( { const { type, node, children, orientation, label, labelId, isDisabled } = item; if(type === 'item') { + position = position.slice(0); + position[level + 1] = refIndex; + itemNodes.push( { const { node, isDisabled } = radioItem; + + position = position.slice(0); + position[level + 1] = refIndex; //TODO isChecked? radioNodes.push( @@ -322,6 +346,7 @@ class ParentMenuItem extends React.Component { refIndex={ refIndex } subIndex={ j } level={ level + 1 } + position={ position } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } ref={ this.childItemRefs[refIndex] } From 8fa3627937ed05da79ca7010d25518f1ecca7eac Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 13 Jan 2022 14:05:20 -0500 Subject: [PATCH 144/286] put the position on the elems as data attributes --- src/Menu/MenuItem.jsx | 4 +++- src/Menu/MenuItemCheckbox.jsx | 4 +++- src/Menu/MenuItemRadio.jsx | 4 +++- src/Menu/ParentMenuItem.jsx | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 28a1ba3f..b446e539 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, index, refIndex, level, onKeyDown, isDisabled, isTabbable } = props; + const { children, index, refIndex, level, position, onKeyDown, isDisabled, isTabbable } = props; return (
                                • Date: Thu, 13 Jan 2022 15:44:15 -0500 Subject: [PATCH 145/286] get rid of index/refIndex/level and start storing a full flattened position) --- src/Menu/MenuBar.jsx | 117 +++++++++++++-------------- src/Menu/MenuItem.jsx | 10 +-- src/Menu/MenuItemCheckbox.jsx | 10 +-- src/Menu/MenuItemRadio.jsx | 10 +-- src/Menu/MenuItemSeparator.jsx | 8 +- src/Menu/ParentMenuItem.jsx | 143 +++++++++++++++------------------ 6 files changed, 133 insertions(+), 165 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 5bc34798..3f887f8b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -63,22 +63,23 @@ class MenuBar extends React.Component { onChildKeyDown = (event) => { const { items } = this.props; const { key, target } = event; - const index = Number.parseInt(target.dataset.index, 10); - const refIndex = Number.parseInt(target.dataset.refindex, 10); + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); const item = items[index]; const { type } = item; - console.log(index, refIndex, item); - - //TODO separators shouldn't be focusable + console.log(position, flattenedPosition, index, flattenedIndex, item); + if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); if(type === 'menu') { this.setState({ - expandedIndex: refIndex, + expandedIndex: flattenedIndex, }, () => { - this.itemRefs[refIndex].current.focusLastChild(); + this.itemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -87,28 +88,28 @@ class MenuBar extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: refIndex, + expandedIndex: flattenedIndex, }, () => { - this.itemRefs[refIndex].current.focusFirstChild(); + this.itemRefs[flattenedIndex].current.focusFirstChild(); }); } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - this.focusPrevChild(refIndex); + this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - this.focusNextChild(refIndex); + this.focusNextChild(flattenedIndex); } else if(key === 'Enter') { event.preventDefault(); if(type === 'menu') { this.setState({ - expandedIndex: refIndex, + expandedIndex: flattenedIndex, }, () => { - this.itemRefs[refIndex].current.focusFirstChild(); + this.itemRefs[flattenedIndex].current.focusFirstChild(); }); } else { @@ -120,9 +121,9 @@ class MenuBar extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: refIndex, + expandedIndex: flattenedIndex, }, () => { - this.itemRefs[refIndex].current.focusFirstChild(); + this.itemRefs[flattenedIndex].current.focusFirstChild(); }); } else if(type === 'checkbox') { @@ -157,7 +158,7 @@ class MenuBar extends React.Component { render() { const { orientation, label, labelId } = this.props; - console.log(this.props, this.state, this.itemRefs); + //console.log(this.props, this.state, this.itemRefs); return (
                                    { const { type, node, children, orientation, label, labelId, isDisabled } = item; if(type === 'item') { position = position.slice(0); - position[0] = refIndex; + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'menu') { position = position.slice(0); - position[0] = refIndex; + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'checkbox') { position = position.slice(0); - position[0] = refIndex; + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; //TODO isChecked? itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'separator') { - position = position.slice(0); - position[0] = refIndex; - itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'radiogroup') { const radioNodes = []; @@ -284,27 +279,27 @@ class MenuBar extends React.Component { const { node, isDisabled } = radioItem; position = position.slice(0); - position[0] = refIndex; + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; //TODO isChecked? radioNodes.push( { node } ); - refIndex++; + flattenedIndex++; }); itemNodes.push( @@ -334,12 +329,12 @@ class MenuBar extends React.Component { }); }; - focusPrevChild = (refIndex, autoExpand = false) => { - let prevIndex = refIndex === 0 ? this.itemRefs.length - 1 : refIndex - 1; + focusPrevChild = (flattenedIndex, autoExpand = false) => { + let prevIndex = flattenedIndex === 0 ? this.itemRefs.length - 1 : flattenedIndex - 1; let prevRef = this.itemRefs[prevIndex]; //TODO test edge cases, e.g. single-element separator and single-element non-separator? - while(isSeparatorRef(prevRef) && prevIndex !== refIndex) { + while(isSeparatorRef(prevRef) && prevIndex !== flattenedIndex) { prevIndex = prevIndex === 0 ? this.itemRefs.length - 1 : prevIndex - 1; prevRef = this.itemRefs[prevIndex]; } @@ -361,11 +356,11 @@ class MenuBar extends React.Component { }); }; - focusNextChild = (refIndex) => { - let nextIndex = refIndex === this.itemRefs.length - 1 ? 0 : refIndex + 1; + focusNextChild = (flattenedIndex) => { + let nextIndex = flattenedIndex === this.itemRefs.length - 1 ? 0 : flattenedIndex + 1; let nextRef = this.itemRefs[nextIndex]; - while(isSeparatorRef(nextRef) && nextIndex !== refIndex) { + while(isSeparatorRef(nextRef) && nextIndex !== flattenedIndex) { nextIndex = nextIndex === this.itemRefs.length - 1 ? 0 : nextIndex + 1; nextRef = this.itemRefs[nextIndex]; } diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index b446e539..71964b21 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -2,15 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, index, refIndex, level, position, onKeyDown, isDisabled, isTabbable } = props; + const { children, position, flattenedPosition, onKeyDown, isDisabled, isTabbable } = props; return (
                                  • @@ -38,9 +35,6 @@ const MenuItemSeparator = React.forwardRef(function MenuItemSeparator(props, ref MenuItemSeparator.propTypes = { children: PropTypes.node, - index: PropTypes.number.isRequired, //TODO is this necessary? - refIndex: PropTypes.number.isRequired, //TODO is this necessary? - level: PropTypes.number.isRequired, //TODO is this necessary? orientation: PropTypes.oneOf([ 'horizontal', 'vertical' ]), }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 3604ae3c..29156eaf 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -17,10 +17,8 @@ class ParentMenuItem extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENUITEMS_PROPTYPE.isRequired, - index: PropTypes.number.isRequired, - refIndex: PropTypes.number.isRequired, - level: PropTypes.number.isRequired, position: PropTypes.arrayOf(PropTypes.number).isRequired, + flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, collapseParent: PropTypes.func.isRequired, focusPrevSibling: PropTypes.func.isRequired, @@ -71,22 +69,23 @@ class ParentMenuItem extends React.Component { onChildKeyDown = (event) => { const { items, collapseParent, focusPrevSibling, focusNextMenubarItem } = this.props; const { key, target } = event; - const index = Number.parseInt(target.dataset.index, 10); - const refIndex = Number.parseInt(target.dataset.refindex, 10); - const level = Number.parseInt(target.dataset.level, 10); + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const level = position.length - 1; const item = items[index]; const { type } = item; - console.log(index, refIndex, level, item); + console.log(position, flattenedPosition, index, flattenedIndex, level, item); - //TODO separators shouldn't be focusable if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - this.focusPrevChild(refIndex); + this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - this.focusNextChild(refIndex); + this.focusNextChild(flattenedIndex); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); @@ -98,7 +97,8 @@ class ParentMenuItem extends React.Component { //menuitem executing this event. we're focusing that menuitem's //parent's previous sibling collapseParent(false, () => { - focusPrevSibling(this.props.refIndex, true); + const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); + focusPrevSibling(flatParentIndex, true); }); } else { @@ -112,9 +112,9 @@ class ParentMenuItem extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: refIndex, + expandedIndex: flattenedIndex, }, () => { - this.childItemRefs[refIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else { @@ -137,9 +137,9 @@ class ParentMenuItem extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: refIndex, + expandedIndex: flattenedIndex, }, () => { - this.childItemRefs[refIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else { @@ -151,9 +151,9 @@ class ParentMenuItem extends React.Component { if(type === 'menu') { this.setState({ - expandedIndex: refIndex, + expandedIndex: flattenedIndex, }, () => { - this.childItemRefs[refIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else if(type === 'checbox') { @@ -193,12 +193,12 @@ class ParentMenuItem extends React.Component { //---- Rendering ---- render() { const { - children, index, refIndex, level, position, onKeyDown, + children, position, flattenedPosition, onKeyDown, orientation, label, labelId, isExpanded, isDisabled, isTabbable, } = this.props; - console.log(this.props, this.state, this.itemRef, this.childItemRefs); + //console.log(this.props, this.state, this.itemRef, this.childItemRefs); return (
                                  • @@ -206,10 +206,8 @@ class ParentMenuItem extends React.Component { href="#" role="menuitem" aria-haspopup="menu" - data-index={ index } - data-refindex={ refIndex } - data-level={ level } data-position={ position } + data-flattenedposition={ flattenedPosition } onKeyDown={ onKeyDown } aria-expanded={ isExpanded } aria-disabled={ isDisabled } @@ -230,134 +228,129 @@ class ParentMenuItem extends React.Component { } renderItems = () => { - const { items, level, focusNextMenubarItem } = this.props; + const { items, focusNextMenubarItem, position, flattenedPosition } = this.props; const { tabbableIndex, expandedIndex } = this.state; + const level = position.length; const itemNodes = []; - let { position } = this.props; - let refIndex = 0; + let _position = []; + let _flattenedPosition = []; + let flattenedIndex = 0; items.forEach((item, i) => { const { type, node, children, orientation, label, labelId, isDisabled } = item; if(type === 'item') { - position = position.slice(0); - position[level + 1] = refIndex; + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'menu') { - position = position.slice(0); - position[level + 1] = refIndex; + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'checkbox') { - position = position.slice(0); - position[level + 1] = refIndex; + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; //TODO isChecked? itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'separator') { - position = position.slice(0); - position[level + 1] = refIndex; - itemNodes.push( { node } ); - refIndex++; + flattenedIndex++; } else if(type === 'radiogroup') { const radioNodes = []; children.forEach((radioItem, j) => { const { node, isDisabled } = radioItem; + + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; - position = position.slice(0); - position[level + 1] = refIndex; - //TODO isChecked? radioNodes.push( { node } ); - refIndex++; + flattenedIndex++; }); itemNodes.push( @@ -380,11 +373,11 @@ class ParentMenuItem extends React.Component { this.itemRef.current.focus(); }; - focusPrevChild = (refIndex) => { - let prevIndex = refIndex === 0 ? this.childItemRefs.length - 1 : refIndex - 1; + focusPrevChild = (flattenedIndex) => { + let prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; let prevRef = this.childItemRefs[prevIndex]; - while(isSeparatorRef(prevRef) && prevIndex !== refIndex) { + while(isSeparatorRef(prevRef) && prevIndex !== flattenedIndex) { prevIndex = prevIndex === 0 ? this.childItemRefs.length - 1 : prevIndex - 1; prevRef = this.childItemRefs[prevIndex]; }; @@ -392,11 +385,11 @@ class ParentMenuItem extends React.Component { prevRef.current.focus(); }; - focusNextChild = (refIndex) => { - let nextIndex = refIndex === this.childItemRefs.length - 1 ? 0 : refIndex + 1; + focusNextChild = (flattenedIndex) => { + let nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; let nextRef = this.childItemRefs[nextIndex]; - while(isSeparatorRef(nextRef) && nextIndex !== refIndex) { + while(isSeparatorRef(nextRef) && nextIndex !== flattenedIndex) { nextIndex = nextIndex === this.childItemRefs.length - 1 ? 0 : nextIndex + 1; nextRef = this.childItemRefs[nextIndex]; } @@ -415,8 +408,6 @@ class ParentMenuItem extends React.Component { collapseMenu = (collapseAll, callback) => { const { collapseParent } = this.props; - console.log('in parentmenuitem', this.props.index, this.props.level); - this.setState({ expandedIndex: undefined, }, () => { From a572f93f8c3fe7e59087d187d046e5380ad36650 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 13 Jan 2022 15:48:11 -0500 Subject: [PATCH 146/286] this should fix arrow right for parentmenuitem --- src/Menu/MenuBar.jsx | 28 ++++------------------------ src/Menu/ParentMenuItem.jsx | 16 ++++------------ 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 3f887f8b..5b9dfe0d 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -220,7 +220,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } focusPrevSibling={ this.focusPrevChild } - focusNextMenubarItem={ this.focusNextSibling } + focusNextMenubarItem={ this.focusNextChild } orientation={ orientation } label={ label } labelId={ labelId } @@ -356,7 +356,7 @@ class MenuBar extends React.Component { }); }; - focusNextChild = (flattenedIndex) => { + focusNextChild = (flattenedIndex, autoExpand = false) => { let nextIndex = flattenedIndex === this.itemRefs.length - 1 ? 0 : flattenedIndex + 1; let nextRef = this.itemRefs[nextIndex]; @@ -368,10 +368,11 @@ class MenuBar extends React.Component { this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; + const _autoExpand = nextRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); return { tabbableIndex: nextIndex, - expandedIndex: nextRef.current instanceof ParentMenuItem && wasExpanded ? nextIndex : undefined, + expandedIndex: _autoExpand ? nextIndex : undefined, }; }, () => { nextRef.current.focus(); @@ -385,27 +386,6 @@ class MenuBar extends React.Component { focusLastChild = () => { this.focusPrevChild(0); }; - - //TODO not very flexible, assuming the current index is - //what is currently expanded... - focusNextSibling = () => { - const { expandedIndex } = this.state; - this.focusNextChild(expandedIndex); - /* - const { items } = this.props; - const { expandedIndex } = this.state; - const nextIndex = expandedIndex === items.length - 1 ? 0 : expandedIndex + 1; - - console.log(expandedIndex, nextIndex); - - this.setState({ - tabbableIndex: nextIndex, - expandedIndex: nextIndex, - }, () => { - this.itemRefs[nextIndex].current.focus(); - }); - */ - }; } export default MenuBar; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 29156eaf..5d964781 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -118,18 +118,10 @@ class ParentMenuItem extends React.Component { }); } else { - //FIXME: this is broken - the next menubar item should be focused, - //but won't be because we immediately collapse everything after - //focusing on it. we're currently using the menubar's expandedIndex - //to determine which sibling to focus on next, so we currently - //cannot collapse everything first (otherwise we'd lose the expandedIndex - //state); - // - //hmm, should we just let everyone know their full position? - //would they have to know both the full position and a full "flattened" - //version? - focusNextMenubarItem(); - collapseParent(true); + collapseParent(true, () => { + const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); + focusNextMenubarItem(flatMenubarIndex, true); + }); } } else if(key === 'Enter') { From 5fa24b5f7071c82177688a41a2848e1d822ce65b Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 12:01:16 -0500 Subject: [PATCH 147/286] give menubar an expandMenu() method --- src/Menu/MenuBar.jsx | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 5b9dfe0d..7d16015b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -76,9 +76,7 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.itemRefs[flattenedIndex].current.focusLastChild(); }); } @@ -87,9 +85,7 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.itemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -106,9 +102,7 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.itemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -120,9 +114,7 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.itemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -146,7 +138,6 @@ class MenuBar extends React.Component { } else if(key === 'Tab') this.collapseMenu(); - else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -220,6 +211,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } focusPrevSibling={ this.focusPrevChild } + focusNextSibling={ this.focusNextChild } focusNextMenubarItem={ this.focusNextChild } orientation={ orientation } label={ label } @@ -329,6 +321,15 @@ class MenuBar extends React.Component { }); }; + expandMenu = (flattenedIndex, callback) => { + this.setState({ + expandedIndex: flattenedIndex, + }, () => { + if(callback && typeof callback === 'function') + callback(); + }); + }; + focusPrevChild = (flattenedIndex, autoExpand = false) => { let prevIndex = flattenedIndex === 0 ? this.itemRefs.length - 1 : flattenedIndex - 1; let prevRef = this.itemRefs[prevIndex]; @@ -357,6 +358,8 @@ class MenuBar extends React.Component { }; focusNextChild = (flattenedIndex, autoExpand = false) => { + console.log(' - focusNextChild()'); + let nextIndex = flattenedIndex === this.itemRefs.length - 1 ? 0 : flattenedIndex + 1; let nextRef = this.itemRefs[nextIndex]; From abbf376432e30019dab44216d88738e96dd14c01 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 12:54:08 -0500 Subject: [PATCH 148/286] add a new menubar with flipped orientations --- src/App.jsx | 417 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 417 insertions(+) diff --git a/src/App.jsx b/src/App.jsx index 8d6a36a9..40b8de64 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -441,6 +441,422 @@ const MENUITEMS = [ }, ]; +const MENUITEMS_2 = [ + { + type: 'menu', + node: 'Parent Menuitem 1', + orientation: 'horizontal', + children: [ + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + ], + }, + { + type: 'menu', + node: 'Parent Menuitem 2', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + + ], + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + ], + }, + ], + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + ], + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Parent Menuitem 3', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + ], + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'menu', + node: 'Parent Menuitem 4', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + ], + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, +]; + + function App() { return (
                                    @@ -451,6 +867,7 @@ function App() { Menu Button +
                                    ); } From 84a5029dfb531e8f1dc5713447e4055a3f039580 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 13:28:19 -0500 Subject: [PATCH 149/286] account for orientation in menubar --- src/Menu/MenuBar.jsx | 50 +++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 7d16015b..26fc6ad1 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -61,7 +61,7 @@ class MenuBar extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items } = this.props; + const { items, orientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -75,28 +75,54 @@ class MenuBar extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusLastChild(); - }); + if(orientation === 'horizontal') { + if(type === 'menu') { + this.expandMenu(flattenedIndex, () => { + this.itemRefs[flattenedIndex].current.focusLastChild(); + }); + } } + else + this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - - if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusFirstChild(); - }); + + if(orientation === 'horizontal') { + if(type === 'menu') { + this.expandMenu(flattenedIndex, () => { + this.itemRefs[flattenedIndex].current.focusFirstChild(); + }); + } } + else + this.focusNextChild(flattenedIndex); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - this.focusPrevChild(flattenedIndex); + + if(orientation === 'horizontal') + this.focusPrevChild(flattenedIndex); + else { + if(type === 'menu') { + this.expandMenu(flattenedIndex, () => { + this.itemRefs[flattenedIndex].current.focusLastChild(); + }); + } + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - this.focusNextChild(flattenedIndex); + + if(orientation === 'horizontal') + this.focusNextChild(flattenedIndex); + else { + if(type === 'menu') { + this.expandMenu(flattenedIndex, () => { + this.itemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + } } else if(key === 'Enter') { event.preventDefault(); From 4bc03d2c8344e85134eaf8ab206c174234a25e32 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 13:42:20 -0500 Subject: [PATCH 150/286] account for orientation in parentmenuitem --- src/Menu/ParentMenuItem.jsx | 109 +++++++++++++++++++++++++----------- src/styles.scss | 10 ++-- 2 files changed, 83 insertions(+), 36 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 5d964781..85978ac0 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -67,7 +67,7 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, collapseParent, focusPrevSibling, focusNextMenubarItem } = this.props; + const { items, collapseParent, focusPrevSibling, focusNextMenubarItem, orientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -81,48 +81,93 @@ class ParentMenuItem extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - this.focusPrevChild(flattenedIndex); + + if(orientation === 'vertical') + this.focusPrevChild(flattenedIndex); + else { + if(level === 1) { + //TODO: naming is just all wrong... + //we're collapsing the parent of the menuitem executing this + //event, but we're not focusing the previous sibling of the + //menuitem executing this event. we're focusing that menuitem's + //parent's previous sibling + collapseParent(false, () => { + const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); + focusPrevSibling(flatParentIndex, true); + }); + } + else { + collapseParent(false, () => { + this.focus(); + }); + } + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - this.focusNextChild(flattenedIndex); + + if(orientation === 'vertical') + this.focusNextChild(flattenedIndex); + else { + if(type === 'menu') { + this.setState({ + expandedIndex: flattenedIndex, + }, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + else { + collapseParent(true, () => { + const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); + focusNextMenubarItem(flatMenubarIndex, true); + }); + } + } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - - if(level === 1) { - //TODO: naming is just all wrong... - //we're collapsing the parent of the menuitem executing this - //event, but we're not focusing the previous sibling of the - //menuitem executing this event. we're focusing that menuitem's - //parent's previous sibling - collapseParent(false, () => { - const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); - focusPrevSibling(flatParentIndex, true); - }); - } - else { - collapseParent(false, () => { - this.focus(); - }); + + if(orientation === 'vertical') { + if(level === 1) { + //TODO: naming is just all wrong... + //we're collapsing the parent of the menuitem executing this + //event, but we're not focusing the previous sibling of the + //menuitem executing this event. we're focusing that menuitem's + //parent's previous sibling + collapseParent(false, () => { + const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); + focusPrevSibling(flatParentIndex, true); + }); + } + else { + collapseParent(false, () => { + this.focus(); + }); + } } + else + this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - - if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); - }); - } - else { - collapseParent(true, () => { - const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); - focusNextMenubarItem(flatMenubarIndex, true); - }); + + if(orientation === 'vertical') { + if(type === 'menu') { + this.setState({ + expandedIndex: flattenedIndex, + }, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + else { + collapseParent(true, () => { + const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); + focusNextMenubarItem(flatMenubarIndex, true); + }); + } } + else + this.focusNextChild(flattenedIndex); } else if(key === 'Enter') { event.preventDefault(); diff --git a/src/styles.scss b/src/styles.scss index 23bcdbed..3ea60303 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -10,11 +10,13 @@ display: none; } -[role="menubar"][aria-orientation="horizontal"] { - display: flex; +[role="menubar"], [role="menu"] { + &[aria-orientation="horizontal"] { + display: flex; - > li { - flex: 1 1 auto; + > li { + flex: 1 1 auto; + } } } From b8a0f510772b399c32f1790da454225689d24d8e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 13:58:30 -0500 Subject: [PATCH 151/286] skeleton for having items use their own events --- src/Menu/ParentMenuItem.jsx | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 85978ac0..4ad028ec 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -227,6 +227,40 @@ class ParentMenuItem extends React.Component { } }; + onKeyDown = (event) => { + const { key } = event; + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + } + else if(key === 'Enter') { + event.preventDefault(); + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + } + else if(key === 'Home') { + event.preventDefault(); + } + else if(key === 'End') { + event.preventDefault(); + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + } + else if(key === 'Tab') { + } + }; + //---- Rendering ---- render() { const { From 6b064d2520aaee1e4a49430e25622e1a048c2caa Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 14:25:09 -0500 Subject: [PATCH 152/286] more skeletoning --- src/App.jsx | 415 +++++++++++++++++++++++++++++++++++- src/Menu/MenuBar.jsx | 3 +- src/Menu/ParentMenuItem.jsx | 63 +++++- 3 files changed, 477 insertions(+), 4 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 40b8de64..79941d8f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -856,6 +856,418 @@ const MENUITEMS_2 = [ }, ]; +const MENUITEMS_3 = [ + { + type: 'menu', + node: 'Parent Menuitem 1', + orientation: 'horizontal', + children: [ + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + ], + }, + { + type: 'menu', + node: 'Parent Menuitem 2', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + + ], + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + ], + }, + ], + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + }, + { + type: 'checkbox', + node: 'Checkbox 2', + }, + ], + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Parent Menuitem 3', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + ], + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'menu', + node: 'Parent Menuitem 4', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + }, + { + type: 'item', + node: 'Hello world!', + }, + ], + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + children: [ + { + node: 'Radio Option 1', + }, + { + node: 'Radio Option 2', + }, + { + node: 'Radio Option 3', + }, + ], + }, + { + type: 'separator', + }, +]; function App() { return ( @@ -867,7 +1279,8 @@ function App() { Menu Button - + + ); } diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 26fc6ad1..c5da4527 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -190,7 +190,7 @@ class MenuBar extends React.Component { } renderItems = () => { - const { items } = this.props; + const { items, orientation: parentOrientation } = this.props; const { tabbableIndex, expandedIndex } = this.state; const itemNodes = []; let position = []; @@ -239,6 +239,7 @@ class MenuBar extends React.Component { focusPrevSibling={ this.focusPrevChild } focusNextSibling={ this.focusNextChild } focusNextMenubarItem={ this.focusNextChild } + parentOrientation={ parentOrientation } orientation={ orientation } label={ label } labelId={ labelId } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 4ad028ec..4a5bd57b 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -23,6 +23,7 @@ class ParentMenuItem extends React.Component { collapseParent: PropTypes.func.isRequired, focusPrevSibling: PropTypes.func.isRequired, focusNextMenubarItem: PropTypes.func.isRequired, + parentOrientation: PropTypes.oneOf([ 'vertical', 'horizontal']).isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, @@ -32,7 +33,7 @@ class ParentMenuItem extends React.Component { }; static defaultProps = { - orientation: 'horizontal', + orientation: 'vertical', label: undefined, labelId: undefined, isExpanded: false, @@ -228,19 +229,77 @@ class ParentMenuItem extends React.Component { }; onKeyDown = (event) => { - const { key } = event; + const { parentOrientation } = this.props; + const { key, target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const level = position.length - 1; if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); + + if(level === 0) { + if(parentOrientation === 'horizontal') { + } + else { + } + } + else { + if(parentOrientation === 'vertical') { + } + else { + } + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + + if(level === 0) { + if(parentOrientation === 'horizontal') { + } + else { + } + } + else { + if(parentOrientation === 'vertical') { + } + else { + } + } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); + + if(level === 0) { + if(parentOrientation === 'horizontal') { + } + else { + } + } + else { + if(parentOrientation === 'vertical') { + } + else { + } + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); + + if(level === 0) { + if(parentOrientation === 'horizontal') { + } + else { + } + } + else { + if(parentOrientation === 'vertical') { + } + else { + } + } } else if(key === 'Enter') { event.preventDefault(); From 0b9326c65b96c90f77d0e29bcbe67863d7007620 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 15:05:11 -0500 Subject: [PATCH 153/286] lets try reworking the current api first --- src/Menu/MenuBar.jsx | 5 +++-- src/Menu/ParentMenuItem.jsx | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index c5da4527..23bedefb 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -190,7 +190,7 @@ class MenuBar extends React.Component { } renderItems = () => { - const { items, orientation: parentOrientation } = this.props; + const { items, /*orientation: parentOrientation*/ } = this.props; const { tabbableIndex, expandedIndex } = this.state; const itemNodes = []; let position = []; @@ -239,7 +239,8 @@ class MenuBar extends React.Component { focusPrevSibling={ this.focusPrevChild } focusNextSibling={ this.focusNextChild } focusNextMenubarItem={ this.focusNextChild } - parentOrientation={ parentOrientation } + expandMenu={ this.expandMenu } + parentOrientation={ undefined /* parentOrientation */ } orientation={ orientation } label={ label } labelId={ labelId } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 4a5bd57b..962aecc4 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -23,7 +23,7 @@ class ParentMenuItem extends React.Component { collapseParent: PropTypes.func.isRequired, focusPrevSibling: PropTypes.func.isRequired, focusNextMenubarItem: PropTypes.func.isRequired, - parentOrientation: PropTypes.oneOf([ 'vertical', 'horizontal']).isRequired, + //parentOrientation: PropTypes.oneOf([ 'vertical', 'horizontal']).isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, @@ -227,9 +227,10 @@ class ParentMenuItem extends React.Component { //with that printable character. } }; - + + /* onKeyDown = (event) => { - const { parentOrientation } = this.props; + const { expandMenu, focusPrevSibling, parentOrientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -242,14 +243,25 @@ class ParentMenuItem extends React.Component { if(level === 0) { if(parentOrientation === 'horizontal') { + expandMenu(flattenedIndex, () => { + this.focusLastChild(); + }); } else { + focusPrevSibling(flattenedIndex); } } else { if(parentOrientation === 'vertical') { + focusPrevSibling(flattenedIndex); } else { + if(level === 1) { + //collapseParent, then go to parent's previous sibling + } + else { + //collapse parent and focus on parent + } } } } @@ -319,6 +331,7 @@ class ParentMenuItem extends React.Component { else if(key === 'Tab') { } }; + */ //---- Rendering ---- render() { From 5163f6aba99fa114528aeed6f48936deca619f8f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 15:30:45 -0500 Subject: [PATCH 154/286] nobody is using this right now --- src/Menu/MenuBar.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 23bedefb..79580256 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -237,7 +237,6 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } focusPrevSibling={ this.focusPrevChild } - focusNextSibling={ this.focusNextChild } focusNextMenubarItem={ this.focusNextChild } expandMenu={ this.expandMenu } parentOrientation={ undefined /* parentOrientation */ } From cc47995e8fc12a35a50ae144a475b3541b5c80a0 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 15:46:34 -0500 Subject: [PATCH 155/286] this should hopefully make naming a bit more consistent --- src/Menu/MenuBar.jsx | 3 +-- src/Menu/ParentMenuItem.jsx | 18 ++++-------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 79580256..7b30a44f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -236,9 +236,8 @@ class MenuBar extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } - focusPrevSibling={ this.focusPrevChild } + focusPrevParentSibling={ this.focusPrevChild } focusNextMenubarItem={ this.focusNextChild } - expandMenu={ this.expandMenu } parentOrientation={ undefined /* parentOrientation */ } orientation={ orientation } label={ label } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 962aecc4..f05d9e03 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -21,7 +21,7 @@ class ParentMenuItem extends React.Component { flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, collapseParent: PropTypes.func.isRequired, - focusPrevSibling: PropTypes.func.isRequired, + focusPrevParentSibling: PropTypes.func.isRequired, focusNextMenubarItem: PropTypes.func.isRequired, //parentOrientation: PropTypes.oneOf([ 'vertical', 'horizontal']).isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), @@ -68,7 +68,7 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, collapseParent, focusPrevSibling, focusNextMenubarItem, orientation } = this.props; + const { items, collapseParent, focusPrevParentSibling, focusNextMenubarItem, orientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -87,14 +87,9 @@ class ParentMenuItem extends React.Component { this.focusPrevChild(flattenedIndex); else { if(level === 1) { - //TODO: naming is just all wrong... - //we're collapsing the parent of the menuitem executing this - //event, but we're not focusing the previous sibling of the - //menuitem executing this event. we're focusing that menuitem's - //parent's previous sibling collapseParent(false, () => { const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); - focusPrevSibling(flatParentIndex, true); + focusPrevParentSibling(flatParentIndex, true); }); } else { @@ -130,14 +125,9 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') { if(level === 1) { - //TODO: naming is just all wrong... - //we're collapsing the parent of the menuitem executing this - //event, but we're not focusing the previous sibling of the - //menuitem executing this event. we're focusing that menuitem's - //parent's previous sibling collapseParent(false, () => { const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); - focusPrevSibling(flatParentIndex, true); + focusPrevParentSibling(flatParentIndex, true); }); } else { From f52977ebf6de70e17dfa6dba1571219c06a3a762 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 15:49:55 -0500 Subject: [PATCH 156/286] get rid of this (for now?) --- src/Menu/ParentMenuItem.jsx | 105 ------------------------------------ 1 file changed, 105 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index f05d9e03..e2189939 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -217,111 +217,6 @@ class ParentMenuItem extends React.Component { //with that printable character. } }; - - /* - onKeyDown = (event) => { - const { expandMenu, focusPrevSibling, parentOrientation } = this.props; - const { key, target } = event; - const position = target.dataset.position.split(','); - const flattenedPosition = target.dataset.flattenedposition.split(','); - const index = Number.parseInt(position[position.length - 1], 10); - const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); - const level = position.length - 1; - - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - - if(level === 0) { - if(parentOrientation === 'horizontal') { - expandMenu(flattenedIndex, () => { - this.focusLastChild(); - }); - } - else { - focusPrevSibling(flattenedIndex); - } - } - else { - if(parentOrientation === 'vertical') { - focusPrevSibling(flattenedIndex); - } - else { - if(level === 1) { - //collapseParent, then go to parent's previous sibling - } - else { - //collapse parent and focus on parent - } - } - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - - if(level === 0) { - if(parentOrientation === 'horizontal') { - } - else { - } - } - else { - if(parentOrientation === 'vertical') { - } - else { - } - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - - if(level === 0) { - if(parentOrientation === 'horizontal') { - } - else { - } - } - else { - if(parentOrientation === 'vertical') { - } - else { - } - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - - if(level === 0) { - if(parentOrientation === 'horizontal') { - } - else { - } - } - else { - if(parentOrientation === 'vertical') { - } - else { - } - } - } - else if(key === 'Enter') { - event.preventDefault(); - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - } - else if(key === 'Home') { - event.preventDefault(); - } - else if(key === 'End') { - event.preventDefault(); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - } - else if(key === 'Tab') { - } - }; - */ //---- Rendering ---- render() { From 29a97774f91e3428664ad311e4dec180f54acc02 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 15:56:58 -0500 Subject: [PATCH 157/286] satisfy proptype complaint --- src/Menu/ParentMenuItem.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index e2189939..03099663 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -23,7 +23,6 @@ class ParentMenuItem extends React.Component { collapseParent: PropTypes.func.isRequired, focusPrevParentSibling: PropTypes.func.isRequired, focusNextMenubarItem: PropTypes.func.isRequired, - //parentOrientation: PropTypes.oneOf([ 'vertical', 'horizontal']).isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, @@ -302,6 +301,7 @@ class ParentMenuItem extends React.Component { flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } collapseParent={ this.collapseMenu } + focusPrevParentSibling={ this.focusPrevChild } focusNextMenubarItem={ focusNextMenubarItem } orientation={ orientation } label={ label } From 9e27826eb5158976b57b7beedbeabbfb037dceed Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 16:03:08 -0500 Subject: [PATCH 158/286] rearrange method --- src/Menu/MenuBar.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 7b30a44f..edb873d9 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -351,7 +351,7 @@ class MenuBar extends React.Component { this.setState({ expandedIndex: flattenedIndex, }, () => { - if(callback && typeof callback === 'function') + if(typeof callback === 'function') callback(); }); }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 03099663..42012e5c 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -397,6 +397,19 @@ class ParentMenuItem extends React.Component { }; //---- Misc. --- + collapseMenu = (collapseAll, callback) => { + const { collapseParent } = this.props; + + this.setState({ + expandedIndex: undefined, + }, () => { + if(collapseAll) + collapseParent(true, callback); + else if(typeof callback === 'function') + callback(); + }); + }; + focus = () => { this.itemRef.current.focus(); }; @@ -432,19 +445,6 @@ class ParentMenuItem extends React.Component { focusLastChild = () => { this.focusPrevChild(0); }; - - collapseMenu = (collapseAll, callback) => { - const { collapseParent } = this.props; - - this.setState({ - expandedIndex: undefined, - }, () => { - if(collapseAll) - collapseParent(true, callback); - else if(typeof callback === 'function') - callback(); - }); - }; } export default ParentMenuItem; From e9b5f0383fbef6ee4c4e495c5f08658e54676521 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 16:08:18 -0500 Subject: [PATCH 159/286] give parentmenuitem an expandmenu method --- src/Menu/ParentMenuItem.jsx | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 42012e5c..d93eb41c 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -105,9 +105,7 @@ class ParentMenuItem extends React.Component { this.focusNextChild(flattenedIndex); else { if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -143,9 +141,7 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') { if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -163,9 +159,7 @@ class ParentMenuItem extends React.Component { event.preventDefault(); if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -177,9 +171,7 @@ class ParentMenuItem extends React.Component { event.preventDefault(); if(type === 'menu') { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { + this.expandMenu(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -410,6 +402,15 @@ class ParentMenuItem extends React.Component { }); }; + expandMenu = (flattenedIndex, callback) => { + this.setState({ + expandedIndex: flattenedIndex, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + focus = () => { this.itemRef.current.focus(); }; From e908631cba62736693a190e5a02dfb6845081313 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 16:09:35 -0500 Subject: [PATCH 160/286] get rid of some console logs --- src/Menu/MenuBar.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index edb873d9..4427f3b8 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -337,8 +337,6 @@ class MenuBar extends React.Component { //---- Misc. ---- collapseMenu = (collapseAll, callback) => { - console.log('in menubar'); - this.setState({ expandedIndex: undefined, }, () => { @@ -384,8 +382,6 @@ class MenuBar extends React.Component { }; focusNextChild = (flattenedIndex, autoExpand = false) => { - console.log(' - focusNextChild()'); - let nextIndex = flattenedIndex === this.itemRefs.length - 1 ? 0 : flattenedIndex + 1; let nextRef = this.itemRefs[nextIndex]; From a59d0557da90eae1cbf8efc6275ccc8c8deb6c84 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 16:14:21 -0500 Subject: [PATCH 161/286] rename itemRefs to childItemRefs in menubar --- src/Menu/MenuBar.jsx | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 4427f3b8..94aaf742 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -44,18 +44,18 @@ class MenuBar extends React.Component { expandedIndex: undefined, }; - this.itemRefs = []; + this.childItemRefs = []; items.forEach(item => { const { type, children } = item; if(type === 'radiogroup') { children.forEach(() => { - this.itemRefs.push(React.createRef()); + this.childItemRefs.push(React.createRef()); }); } else - this.itemRefs.push(React.createRef()); + this.childItemRefs.push(React.createRef()); }); } @@ -78,7 +78,7 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusLastChild(); + this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -91,7 +91,7 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } } @@ -106,7 +106,7 @@ class MenuBar extends React.Component { else { if(type === 'menu') { this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusLastChild(); + this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -119,7 +119,7 @@ class MenuBar extends React.Component { else { if(type === 'menu') { this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } } @@ -129,7 +129,7 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else { @@ -141,7 +141,7 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandMenu(flattenedIndex, () => { - this.itemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else if(type === 'checkbox') { @@ -175,7 +175,7 @@ class MenuBar extends React.Component { render() { const { orientation, label, labelId } = this.props; - //console.log(this.props, this.state, this.itemRefs); + //console.log(this.props, this.state, this.childItemRefs); return (
                                      { node } @@ -245,7 +245,7 @@ class MenuBar extends React.Component { isDisabled={ isDisabled } isExpanded={ flattenedIndex === expandedIndex } isTabbable={ flattenedIndex === tabbableIndex } - ref={ this.itemRefs[flattenedIndex] } + ref={ this.childItemRefs[flattenedIndex] } > { node } @@ -268,7 +268,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ this.itemRefs[flattenedIndex] } + ref={ this.childItemRefs[flattenedIndex] } > { node } @@ -282,7 +282,7 @@ class MenuBar extends React.Component { key={ i } onKeyDown={ this.onChildKeyDown } orientation={ orientation } - ref={ this.itemRefs[flattenedIndex] } + ref={ this.childItemRefs[flattenedIndex] } > { node } @@ -311,7 +311,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ this.itemRefs[flattenedIndex] } + ref={ this.childItemRefs[flattenedIndex] } > { node } @@ -355,13 +355,13 @@ class MenuBar extends React.Component { }; focusPrevChild = (flattenedIndex, autoExpand = false) => { - let prevIndex = flattenedIndex === 0 ? this.itemRefs.length - 1 : flattenedIndex - 1; - let prevRef = this.itemRefs[prevIndex]; + let prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; + let prevRef = this.childItemRefs[prevIndex]; //TODO test edge cases, e.g. single-element separator and single-element non-separator? while(isSeparatorRef(prevRef) && prevIndex !== flattenedIndex) { - prevIndex = prevIndex === 0 ? this.itemRefs.length - 1 : prevIndex - 1; - prevRef = this.itemRefs[prevIndex]; + prevIndex = prevIndex === 0 ? this.childItemRefs.length - 1 : prevIndex - 1; + prevRef = this.childItemRefs[prevIndex]; } this.setState(state => { @@ -382,12 +382,12 @@ class MenuBar extends React.Component { }; focusNextChild = (flattenedIndex, autoExpand = false) => { - let nextIndex = flattenedIndex === this.itemRefs.length - 1 ? 0 : flattenedIndex + 1; - let nextRef = this.itemRefs[nextIndex]; + let nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; + let nextRef = this.childItemRefs[nextIndex]; while(isSeparatorRef(nextRef) && nextIndex !== flattenedIndex) { - nextIndex = nextIndex === this.itemRefs.length - 1 ? 0 : nextIndex + 1; - nextRef = this.itemRefs[nextIndex]; + nextIndex = nextIndex === this.childItemRefs.length - 1 ? 0 : nextIndex + 1; + nextRef = this.childItemRefs[nextIndex]; } this.setState(state => { @@ -405,7 +405,7 @@ class MenuBar extends React.Component { }; focusFirstChild = () => { - this.focusNextChild(this.itemRefs.length - 1); + this.focusNextChild(this.childItemRefs.length - 1); }; focusLastChild = () => { From 446c053bf4d975a043d193b1a056d1dfbfc63e0a Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 14 Jan 2022 16:20:04 -0500 Subject: [PATCH 162/286] rename expand/collapse in menubar --- src/Menu/MenuBar.jsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 94aaf742..f762fa12 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -77,7 +77,7 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } @@ -90,7 +90,7 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -105,7 +105,7 @@ class MenuBar extends React.Component { this.focusPrevChild(flattenedIndex); else { if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } @@ -118,7 +118,7 @@ class MenuBar extends React.Component { this.focusNextChild(flattenedIndex); else { if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -128,7 +128,7 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -140,7 +140,7 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -163,7 +163,7 @@ class MenuBar extends React.Component { this.focusLastChild(); } else if(key === 'Tab') - this.collapseMenu(); + this.collapseChild(); else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -235,7 +235,7 @@ class MenuBar extends React.Component { position={ position } flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapseParent={ this.collapseMenu } + collapseParent={ this.collapseChild } focusPrevParentSibling={ this.focusPrevChild } focusNextMenubarItem={ this.focusNextChild } parentOrientation={ undefined /* parentOrientation */ } @@ -336,7 +336,7 @@ class MenuBar extends React.Component { }; //---- Misc. ---- - collapseMenu = (collapseAll, callback) => { + collapseChild = (collapseAll, callback) => { this.setState({ expandedIndex: undefined, }, () => { @@ -345,7 +345,7 @@ class MenuBar extends React.Component { }); }; - expandMenu = (flattenedIndex, callback) => { + expandChild = (flattenedIndex, callback) => { this.setState({ expandedIndex: flattenedIndex, }, () => { From 6dca299cf3f155d0e1bd477f468f8aeb2e4d384b Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 15:50:18 -0500 Subject: [PATCH 163/286] get rid of an unused prop, move a method in parentmenuitem --- src/Menu/MenuBar.jsx | 3 +-- src/Menu/ParentMenuItem.jsx | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index f762fa12..b8c826fa 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -190,7 +190,7 @@ class MenuBar extends React.Component { } renderItems = () => { - const { items, /*orientation: parentOrientation*/ } = this.props; + const { items } = this.props; const { tabbableIndex, expandedIndex } = this.state; const itemNodes = []; let position = []; @@ -238,7 +238,6 @@ class MenuBar extends React.Component { collapseParent={ this.collapseChild } focusPrevParentSibling={ this.focusPrevChild } focusNextMenubarItem={ this.focusNextChild } - parentOrientation={ undefined /* parentOrientation */ } orientation={ orientation } label={ label } labelId={ labelId } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index d93eb41c..bdafba3f 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -411,10 +411,6 @@ class ParentMenuItem extends React.Component { }); }; - focus = () => { - this.itemRef.current.focus(); - }; - focusPrevChild = (flattenedIndex) => { let prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; let prevRef = this.childItemRefs[prevIndex]; @@ -446,6 +442,10 @@ class ParentMenuItem extends React.Component { focusLastChild = () => { this.focusPrevChild(0); }; + + focus = () => { + this.itemRef.current.focus(); + }; } export default ParentMenuItem; From c0135bd079b3be0a67583ae28e84588675041d69 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 16:00:08 -0500 Subject: [PATCH 164/286] rename expand/collapse methods in parentmenuitem to hopefully clarify the affected level --- src/Menu/ParentMenuItem.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index bdafba3f..3ee82159 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -105,7 +105,7 @@ class ParentMenuItem extends React.Component { this.focusNextChild(flattenedIndex); else { if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -141,7 +141,7 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') { if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -159,7 +159,7 @@ class ParentMenuItem extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -171,7 +171,7 @@ class ParentMenuItem extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandMenu(flattenedIndex, () => { + this.expandChild(flattenedIndex, () => { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } @@ -292,7 +292,7 @@ class ParentMenuItem extends React.Component { position={ _position } flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapseParent={ this.collapseMenu } + collapseParent={ this.collapseChild } focusPrevParentSibling={ this.focusPrevChild } focusNextMenubarItem={ focusNextMenubarItem } orientation={ orientation } @@ -389,7 +389,7 @@ class ParentMenuItem extends React.Component { }; //---- Misc. --- - collapseMenu = (collapseAll, callback) => { + collapseChild = (collapseAll, callback) => { const { collapseParent } = this.props; this.setState({ @@ -402,7 +402,7 @@ class ParentMenuItem extends React.Component { }); }; - expandMenu = (flattenedIndex, callback) => { + expandChild = (flattenedIndex, callback) => { this.setState({ expandedIndex: flattenedIndex, }, () => { From f0d3bf0fd26ee4e69be6524aa4a1fa4211ac2ca3 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 16:02:26 -0500 Subject: [PATCH 165/286] rearrange prop for consistency --- src/Menu/MenuBar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index b8c826fa..52d291fc 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -241,8 +241,8 @@ class MenuBar extends React.Component { orientation={ orientation } label={ label } labelId={ labelId } - isDisabled={ isDisabled } isExpanded={ flattenedIndex === expandedIndex } + isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } ref={ this.childItemRefs[flattenedIndex] } > From 409703548ac0f0259f11061f3f6534b9989f0350 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 16:06:20 -0500 Subject: [PATCH 166/286] rename collapseParent to collapse --- src/Menu/MenuBar.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 52d291fc..ff65c27d 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -235,7 +235,7 @@ class MenuBar extends React.Component { position={ position } flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapseParent={ this.collapseChild } + collapse={ this.collapseChild } focusPrevParentSibling={ this.focusPrevChild } focusNextMenubarItem={ this.focusNextChild } orientation={ orientation } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 3ee82159..e60f77fe 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -20,7 +20,7 @@ class ParentMenuItem extends React.Component { position: PropTypes.arrayOf(PropTypes.number).isRequired, flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, - collapseParent: PropTypes.func.isRequired, + collapse: PropTypes.func.isRequired, focusPrevParentSibling: PropTypes.func.isRequired, focusNextMenubarItem: PropTypes.func.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), @@ -67,7 +67,7 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, collapseParent, focusPrevParentSibling, focusNextMenubarItem, orientation } = this.props; + const { items, collapse, focusPrevParentSibling, focusNextMenubarItem, orientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -86,13 +86,13 @@ class ParentMenuItem extends React.Component { this.focusPrevChild(flattenedIndex); else { if(level === 1) { - collapseParent(false, () => { + collapse(false, () => { const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); focusPrevParentSibling(flatParentIndex, true); }); } else { - collapseParent(false, () => { + collapse(false, () => { this.focus(); }); } @@ -110,7 +110,7 @@ class ParentMenuItem extends React.Component { }); } else { - collapseParent(true, () => { + collapse(true, () => { const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); focusNextMenubarItem(flatMenubarIndex, true); }); @@ -122,13 +122,13 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') { if(level === 1) { - collapseParent(false, () => { + collapse(false, () => { const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); focusPrevParentSibling(flatParentIndex, true); }); } else { - collapseParent(false, () => { + collapse(false, () => { this.focus(); }); } @@ -146,7 +146,7 @@ class ParentMenuItem extends React.Component { }); } else { - collapseParent(true, () => { + collapse(true, () => { const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); focusNextMenubarItem(flatMenubarIndex, true); }); @@ -196,12 +196,12 @@ class ParentMenuItem extends React.Component { else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); - collapseParent(false, () => { + collapse(false, () => { this.focus(); }); } else if(key === 'Tab') - collapseParent(true); + collapse(true); else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -292,7 +292,7 @@ class ParentMenuItem extends React.Component { position={ _position } flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapseParent={ this.collapseChild } + collapse={ this.collapseChild } focusPrevParentSibling={ this.focusPrevChild } focusNextMenubarItem={ focusNextMenubarItem } orientation={ orientation } @@ -390,13 +390,13 @@ class ParentMenuItem extends React.Component { //---- Misc. --- collapseChild = (collapseAll, callback) => { - const { collapseParent } = this.props; + const { collapse } = this.props; this.setState({ expandedIndex: undefined, }, () => { if(collapseAll) - collapseParent(true, callback); + collapse(true, callback); else if(typeof callback === 'function') callback(); }); From b18f6876da7e0042773fa6b1293ab25db9d56a68 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 16:13:16 -0500 Subject: [PATCH 167/286] seems like parentmenuitem only wants to navigate to the previous root item, not its parent"s previous sibling --- src/Menu/MenuBar.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index ff65c27d..aba3dd56 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -236,7 +236,7 @@ class MenuBar extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } collapse={ this.collapseChild } - focusPrevParentSibling={ this.focusPrevChild } + focusPrevMenubarItem={ this.focusPrevChild } focusNextMenubarItem={ this.focusNextChild } orientation={ orientation } label={ label } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index e60f77fe..43919158 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -21,7 +21,7 @@ class ParentMenuItem extends React.Component { flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, collapse: PropTypes.func.isRequired, - focusPrevParentSibling: PropTypes.func.isRequired, + focusPrevMenubarItem: PropTypes.func.isRequired, focusNextMenubarItem: PropTypes.func.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, @@ -67,7 +67,7 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, collapse, focusPrevParentSibling, focusNextMenubarItem, orientation } = this.props; + const { items, collapse, focusPrevMenubarItem, focusNextMenubarItem, orientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -87,8 +87,7 @@ class ParentMenuItem extends React.Component { else { if(level === 1) { collapse(false, () => { - const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); - focusPrevParentSibling(flatParentIndex, true); + focusPrevMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); }); } else { @@ -123,8 +122,7 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') { if(level === 1) { collapse(false, () => { - const flatParentIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 2], 10); - focusPrevParentSibling(flatParentIndex, true); + focusPrevMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); }); } else { @@ -247,7 +245,7 @@ class ParentMenuItem extends React.Component { } renderItems = () => { - const { items, focusNextMenubarItem, position, flattenedPosition } = this.props; + const { items, focusPrevMenubarItem, focusNextMenubarItem, position, flattenedPosition } = this.props; const { tabbableIndex, expandedIndex } = this.state; const level = position.length; const itemNodes = []; @@ -293,7 +291,7 @@ class ParentMenuItem extends React.Component { flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } collapse={ this.collapseChild } - focusPrevParentSibling={ this.focusPrevChild } + focusPrevMenubarItem={ focusPrevMenubarItem } focusNextMenubarItem={ focusNextMenubarItem } orientation={ orientation } label={ label } From d2475fc9e64847ce29501f12c7c3623c6d407ce8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 16:17:20 -0500 Subject: [PATCH 168/286] this should simplify some conditional logic --- src/Menu/ParentMenuItem.jsx | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 43919158..52ab7cff 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -85,16 +85,12 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') this.focusPrevChild(flattenedIndex); else { - if(level === 1) { - collapse(false, () => { + collapse(false, () => { + if(level === 1) focusPrevMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); - }); - } - else { - collapse(false, () => { + else this.focus(); - }); - } + }); } } else if(key === 'ArrowDown' || key === 'Down') { @@ -110,8 +106,7 @@ class ParentMenuItem extends React.Component { } else { collapse(true, () => { - const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); - focusNextMenubarItem(flatMenubarIndex, true); + focusNextMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); }); } } @@ -120,16 +115,12 @@ class ParentMenuItem extends React.Component { event.preventDefault(); if(orientation === 'vertical') { - if(level === 1) { - collapse(false, () => { + collapse(false, () => { + if(level === 1) focusPrevMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); - }); - } - else { - collapse(false, () => { + else this.focus(); - }); - } + }); } else this.focusPrevChild(flattenedIndex); @@ -145,8 +136,7 @@ class ParentMenuItem extends React.Component { } else { collapse(true, () => { - const flatMenubarIndex = Number.parseInt(flattenedPosition[0], 10); - focusNextMenubarItem(flatMenubarIndex, true); + focusNextMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); }); } } From 3813d67797a15e1140bc6d2b536671d2f950138e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 16:18:48 -0500 Subject: [PATCH 169/286] no need to repeat logic --- src/Menu/ParentMenuItem.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 52ab7cff..ebd41ef0 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -73,6 +73,7 @@ class ParentMenuItem extends React.Component { const flattenedPosition = target.dataset.flattenedposition.split(','); const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); const level = position.length - 1; const item = items[index]; const { type } = item; @@ -87,7 +88,7 @@ class ParentMenuItem extends React.Component { else { collapse(false, () => { if(level === 1) - focusPrevMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); + focusPrevMenubarItem(flattenedRootIndex, true); else this.focus(); }); @@ -106,7 +107,7 @@ class ParentMenuItem extends React.Component { } else { collapse(true, () => { - focusNextMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); + focusNextMenubarItem(flattenedRootIndex, true); }); } } @@ -117,7 +118,7 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') { collapse(false, () => { if(level === 1) - focusPrevMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); + focusPrevMenubarItem(flattenedRootIndex, true); else this.focus(); }); @@ -136,7 +137,7 @@ class ParentMenuItem extends React.Component { } else { collapse(true, () => { - focusNextMenubarItem(Number.parseInt(flattenedPosition[0], 10), true); + focusNextMenubarItem(flattenedRootIndex, true); }); } } From 48c6e7bc1b16b2c729009733f10afc3435621474 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 21 Jan 2022 16:44:51 -0500 Subject: [PATCH 170/286] separators in menus aren"t focusable, so get rid of their refs and such --- src/Menu/MenuBar.jsx | 18 +++--------------- src/Menu/MenuItemSeparator.jsx | 7 ++----- src/Menu/ParentMenuItem.jsx | 27 +++++++-------------------- 3 files changed, 12 insertions(+), 40 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index aba3dd56..40d1b342 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -48,7 +48,9 @@ class MenuBar extends React.Component { items.forEach(item => { const { type, children } = item; - + + if(type === 'separator') + return; if(type === 'radiogroup') { children.forEach(() => { this.childItemRefs.push(React.createRef()); @@ -281,13 +283,10 @@ class MenuBar extends React.Component { key={ i } onKeyDown={ this.onChildKeyDown } orientation={ orientation } - ref={ this.childItemRefs[flattenedIndex] } > { node } ); - - flattenedIndex++; } else if(type === 'radiogroup') { const radioNodes = []; @@ -357,12 +356,6 @@ class MenuBar extends React.Component { let prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; let prevRef = this.childItemRefs[prevIndex]; - //TODO test edge cases, e.g. single-element separator and single-element non-separator? - while(isSeparatorRef(prevRef) && prevIndex !== flattenedIndex) { - prevIndex = prevIndex === 0 ? this.childItemRefs.length - 1 : prevIndex - 1; - prevRef = this.childItemRefs[prevIndex]; - } - this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; @@ -384,11 +377,6 @@ class MenuBar extends React.Component { let nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; let nextRef = this.childItemRefs[nextIndex]; - while(isSeparatorRef(nextRef) && nextIndex !== flattenedIndex) { - nextIndex = nextIndex === this.childItemRefs.length - 1 ? 0 : nextIndex + 1; - nextRef = this.childItemRefs[nextIndex]; - } - this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; diff --git a/src/Menu/MenuItemSeparator.jsx b/src/Menu/MenuItemSeparator.jsx index 21447216..35f6358f 100644 --- a/src/Menu/MenuItemSeparator.jsx +++ b/src/Menu/MenuItemSeparator.jsx @@ -16,22 +16,19 @@ import PropTypes from 'prop-types'; * * See: * https://www.w3.org/TR/wai-aria-practices-1.1/#menu - * - * TODO: is it necessary to attach a ref to these? */ -const MenuItemSeparator = React.forwardRef(function MenuItemSeparator(props, ref) { +function MenuItemSeparator(props) { const { children, orientation } = props; return (
                                    • { children }
                                    • ); -}); +} MenuItemSeparator.propTypes = { children: PropTypes.node, diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index ebd41ef0..52c9cb86 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -54,7 +54,9 @@ class ParentMenuItem extends React.Component { items.forEach(item => { const { type, children } = item; - + + if(type === 'separator') + return; if(type === 'radiogroup') { children.forEach(() => { this.childItemRefs.push(React.createRef()); @@ -325,13 +327,10 @@ class ParentMenuItem extends React.Component { key={ i } onKeyDown={ this.onChildKeyDown } orientation={ orientation } - ref={ this.childItemRefs[flattenedIndex] } > { node } ); - - flattenedIndex++; } else if(type === 'radiogroup') { const radioNodes = []; @@ -401,26 +400,14 @@ class ParentMenuItem extends React.Component { }; focusPrevChild = (flattenedIndex) => { - let prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; - let prevRef = this.childItemRefs[prevIndex]; - - while(isSeparatorRef(prevRef) && prevIndex !== flattenedIndex) { - prevIndex = prevIndex === 0 ? this.childItemRefs.length - 1 : prevIndex - 1; - prevRef = this.childItemRefs[prevIndex]; - }; - + const prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; + const prevRef = this.childItemRefs[prevIndex]; prevRef.current.focus(); }; focusNextChild = (flattenedIndex) => { - let nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; - let nextRef = this.childItemRefs[nextIndex]; - - while(isSeparatorRef(nextRef) && nextIndex !== flattenedIndex) { - nextIndex = nextIndex === this.childItemRefs.length - 1 ? 0 : nextIndex + 1; - nextRef = this.childItemRefs[nextIndex]; - } - + const nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; + const nextRef = this.childItemRefs[nextIndex]; nextRef.current.focus(); }; From e67fb795da66a9278ab7a5376b64abd47ca60eb4 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 18:08:11 -0500 Subject: [PATCH 171/286] change some linter rules, run the linter and resolve complaints --- .eslintrc.js | 9 ++++++++- .gitignore | 1 + src/Menu/MenuBar.jsx | 27 +++++++++++++++------------ src/Menu/MenuItem.jsx | 4 ++-- src/Menu/MenuItemCheckbox.jsx | 4 ++-- src/Menu/MenuItemRadioGroup.jsx | 3 --- src/Menu/MenuItemSeparator.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 21 ++++++++++++--------- 8 files changed, 41 insertions(+), 30 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 068fff78..4b3b79d0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -241,7 +241,13 @@ module.exports = { ], 'comma-dangle': [ 'error', - 'always-multiline', + { + arrays: 'always-multiline', + objects: 'always-multiline', + imports: 'always-multiline', + exports: 'always-multiline', + functions: 'only-multiline', + }, ], 'comma-spacing': [ 'error', @@ -268,6 +274,7 @@ module.exports = { ], 'function-paren-newline': [ 'error', + 'consistent', ], 'implicit-arrow-linebreak': [ 'error', diff --git a/.gitignore b/.gitignore index e5130312..c08efa94 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ # Vim *.swp +*.swo dist diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 40d1b342..c1f2a018 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -11,7 +11,6 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; -import { isSeparatorRef } from 'src/Menu/utils'; /* * Note: @@ -48,7 +47,7 @@ class MenuBar extends React.Component { items.forEach(item => { const { type, children } = item; - + if(type === 'separator') return; if(type === 'radiogroup') { @@ -72,7 +71,7 @@ class MenuBar extends React.Component { const item = items[index]; const { type } = item; - console.log(position, flattenedPosition, index, flattenedIndex, item); + //console.log(position, flattenedPosition, index, flattenedIndex, item); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); @@ -89,7 +88,7 @@ class MenuBar extends React.Component { } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - + if(orientation === 'horizontal') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { @@ -192,6 +191,8 @@ class MenuBar extends React.Component { } renderItems = () => { + /* eslint-disable react/no-array-index-key */ + const { items } = this.props; const { tabbableIndex, expandedIndex } = this.state; const itemNodes = []; @@ -298,7 +299,7 @@ class MenuBar extends React.Component { position[0] = i; flattenedPosition = flattenedPosition.slice(0); flattenedPosition[0] = flattenedIndex; - + //TODO isChecked? radioNodes.push( { - let prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; - let prevRef = this.childItemRefs[prevIndex]; - + const prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; + const prevRef = this.childItemRefs[prevIndex]; + this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; const _autoExpand = prevRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); - + //TODO would be nice if there was a better way to map ref indices to item indices //and vice-versa, checking instanceof ParentMenuItem almost feels sort of abusive //wrt using refs @@ -372,10 +375,10 @@ class MenuBar extends React.Component { prevRef.current.focus(); }); }; - + focusNextChild = (flattenedIndex, autoExpand = false) => { - let nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; - let nextRef = this.childItemRefs[nextIndex]; + const nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; + const nextRef = this.childItemRefs[nextIndex]; this.setState(state => { const { expandedIndex } = state; diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 71964b21..ef2c6410 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -21,8 +21,8 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { MenuItem.propTypes = { children: PropTypes.node.isRequired, - position: PropTypes.arrayOf(PropTypes.number.isRequired), - flattenedPosition: PropTypes.arrayOf(PropTypes.number.isRequired), + position: PropTypes.arrayOf(PropTypes.number).isRequired, + flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, diff --git a/src/Menu/MenuItemCheckbox.jsx b/src/Menu/MenuItemCheckbox.jsx index b1421a24..caeddf8c 100644 --- a/src/Menu/MenuItemCheckbox.jsx +++ b/src/Menu/MenuItemCheckbox.jsx @@ -22,8 +22,8 @@ const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox(props, ref) MenuItemCheckbox.propTypes = { children: PropTypes.node.isRequired, - position: PropTypes.arrayOf(PropTypes.number.isRequired), - flattenedPosition: PropTypes.arrayOf(PropTypes.number.isRequired), + position: PropTypes.arrayOf(PropTypes.number).isRequired, + flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, diff --git a/src/Menu/MenuItemRadioGroup.jsx b/src/Menu/MenuItemRadioGroup.jsx index 930a5384..c903f3ea 100644 --- a/src/Menu/MenuItemRadioGroup.jsx +++ b/src/Menu/MenuItemRadioGroup.jsx @@ -1,9 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -//Components and Styles -import MenuItemRadio from 'src/Menu/MenuItemRadio'; - /* * Note that if a menu or menubar contains more than one group of * menuitemradio elements or if the menu has a group of menuitemradio diff --git a/src/Menu/MenuItemSeparator.jsx b/src/Menu/MenuItemSeparator.jsx index 35f6358f..ababade4 100644 --- a/src/Menu/MenuItemSeparator.jsx +++ b/src/Menu/MenuItemSeparator.jsx @@ -21,7 +21,7 @@ function MenuItemSeparator(props) { const { children, orientation } = props; return ( -
                                    • diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 52c9cb86..e7999ca4 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -11,7 +11,6 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; -import { isSeparatorRef } from 'src/Menu/utils'; class ParentMenuItem extends React.Component { static propTypes = { @@ -54,7 +53,7 @@ class ParentMenuItem extends React.Component { items.forEach(item => { const { type, children } = item; - + if(type === 'separator') return; if(type === 'radiogroup') { @@ -80,8 +79,8 @@ class ParentMenuItem extends React.Component { const item = items[index]; const { type } = item; - console.log(position, flattenedPosition, index, flattenedIndex, level, item); - + //console.log(position, flattenedPosition, index, flattenedIndex, level, item); + if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); @@ -116,7 +115,7 @@ class ParentMenuItem extends React.Component { } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - + if(orientation === 'vertical') { collapse(false, () => { if(level === 1) @@ -130,7 +129,7 @@ class ParentMenuItem extends React.Component { } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - + if(orientation === 'vertical') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { @@ -238,8 +237,10 @@ class ParentMenuItem extends React.Component { } renderItems = () => { + /* eslint-disable react/no-array-index-key */ + const { items, focusPrevMenubarItem, focusNextMenubarItem, position, flattenedPosition } = this.props; - const { tabbableIndex, expandedIndex } = this.state; + const { expandedIndex } = this.state; const level = position.length; const itemNodes = []; let _position = []; @@ -248,7 +249,7 @@ class ParentMenuItem extends React.Component { items.forEach((item, i) => { const { type, node, children, orientation, label, labelId, isDisabled } = item; - + if(type === 'item') { _position = position.slice(0); _position[level] = i; @@ -337,7 +338,7 @@ class ParentMenuItem extends React.Component { children.forEach((radioItem, j) => { const { node, isDisabled } = radioItem; - + _position = position.slice(0); _position[level] = i; _flattenedPosition = flattenedPosition.slice(0); @@ -374,6 +375,8 @@ class ParentMenuItem extends React.Component { }); return itemNodes; + + /* eslint-enable react/no-array-index-key */ }; //---- Misc. --- From 5f98da1bddb6cc155422f0ccf1415df19917af33 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 18:34:59 -0500 Subject: [PATCH 172/286] convert into a class component --- src/Menu/Menu.jsx | 51 +++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index e16f036c..89ccbe23 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -7,32 +7,35 @@ import PropTypes from 'prop-types'; * - The menu should either have a labelId prop that points to the menuitem or * button that controls its display XOR a label prop. */ -function Menu(props) { - const { children, orientation, label, labelId } = props; +class Menu extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, + labelId: PropTypes.string, + }; - return ( -
                                        - { children } -
                                      - ); -} + static defaultProps = { + orientation: 'vertical', + label: undefined, + labelId: undefined, + }; -Menu.propTypes = { - children: PropTypes.node.isRequired, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - label: PropTypes.string, - labelId: PropTypes.string, -}; + //---- Rendering ---- + render() { + const { children, orientation, label, labelId } = this.props; -Menu.defaultProps = { - orientation: 'vertical', - label: undefined, - labelId: undefined, -}; + return ( +
                                        + { children } +
                                      + ); + } +} export default Menu; From ffb9551c154daba6b582494441cdb8d8d6a8e764 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 18:49:24 -0500 Subject: [PATCH 173/286] menubutton is the one that should be converted to a class component :x --- src/Menu/Menu.jsx | 51 +++++++++++++++++++---------------------- src/Menu/MenuButton.jsx | 45 +++++++++++++++++++----------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 89ccbe23..e16f036c 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -7,35 +7,32 @@ import PropTypes from 'prop-types'; * - The menu should either have a labelId prop that points to the menuitem or * button that controls its display XOR a label prop. */ -class Menu extends React.Component { - static propTypes = { - children: PropTypes.node.isRequired, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - label: PropTypes.string, - labelId: PropTypes.string, - }; +function Menu(props) { + const { children, orientation, label, labelId } = props; - static defaultProps = { - orientation: 'vertical', - label: undefined, - labelId: undefined, - }; + return ( +
                                        + { children } +
                                      + ); +} - //---- Rendering ---- - render() { - const { children, orientation, label, labelId } = this.props; +Menu.propTypes = { + children: PropTypes.node.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, + labelId: PropTypes.string, +}; - return ( -
                                        - { children } -
                                      - ); - } -} +Menu.defaultProps = { + orientation: 'vertical', + label: undefined, + labelId: undefined, +}; export default Menu; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index c9ae5470..fe542d90 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -1,29 +1,32 @@ import React from 'react'; import PropTypes from 'prop-types'; -function MenuButton(props) { - const { children, isExpanded } = props; +class MenuButton extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + isExpanded: PropTypes.bool, + }; - //TODO: point aria-controls to the menu (optional) - //TODO: keyboard interaction - return ( - - ); -} + static defaultProps = { + isExpanded: false, + }; -MenuButton.propTypes = { - children: PropTypes.node.isRequired, - isExpanded: PropTypes.bool, -}; + //---- Rendering ---- + render() { + const { children, isExpanded } = props; -MenuButton.defaultProps = { - isExpanded: false, -}; + //TODO: point aria-controls to the menu (optional) + //TODO: keyboard interaction + return ( + + ); + } +} export default MenuButton; From 4551b8a3b0ddb553d5920ecd76ba24c75de7d732 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 19:33:37 -0500 Subject: [PATCH 174/286] give an id prop --- src/Menu/Menu.jsx | 5 ++++- src/Menu/MenuButton.jsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index e16f036c..83322032 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; * button that controls its display XOR a label prop. */ function Menu(props) { - const { children, orientation, label, labelId } = props; + const { children, orientation, label, labelId, id } = props; return (
                                        { children }
                                      @@ -27,12 +28,14 @@ Menu.propTypes = { orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, + id: PropTypes.string, }; Menu.defaultProps = { orientation: 'vertical', label: undefined, labelId: undefined, + id: undefined, }; export default Menu; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index fe542d90..b82e4ac3 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -13,7 +13,7 @@ class MenuButton extends React.Component { //---- Rendering ---- render() { - const { children, isExpanded } = props; + const { children, isExpanded } = this.props; //TODO: point aria-controls to the menu (optional) //TODO: keyboard interaction From 179f8cb1fa44e7b2fe800bab015bf8c414846a33 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 19:36:04 -0500 Subject: [PATCH 175/286] give an items proptype --- src/App.jsx | 2 +- src/Menu/MenuButton.jsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/App.jsx b/src/App.jsx index 79941d8f..dcadd88e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1275,7 +1275,7 @@ function App() {

                                      Accordion

                                      Menu, Menubar, Menu Button

                                      - + Menu Button diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index b82e4ac3..ac2f3a54 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -1,9 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; +//Misc. +import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; + class MenuButton extends React.Component { static propTypes = { children: PropTypes.node.isRequired, + items: MENUITEMS_PROPTYPE.isRequired, isExpanded: PropTypes.bool, }; From 6b65f56365870f20e2815dc806ad05ef3f7a3740 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 19:36:46 -0500 Subject: [PATCH 176/286] give an orientation prop defaulting to vertical --- src/Menu/MenuButton.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index ac2f3a54..68f32ab2 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -8,10 +8,12 @@ class MenuButton extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENUITEMS_PROPTYPE.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal']), isExpanded: PropTypes.bool, }; static defaultProps = { + orientation: 'vertical', isExpanded: false, }; From fba219bfc74a5171744a38cc3d4fe13162f9322c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 19:40:46 -0500 Subject: [PATCH 177/286] have begin rendering a dummy --- src/Menu/MenuButton.jsx | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 68f32ab2..2d16217f 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -1,6 +1,9 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; +//Components and Styles +import Menu from 'src/Menu/Menu'; + //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -19,20 +22,33 @@ class MenuButton extends React.Component { //---- Rendering ---- render() { - const { children, isExpanded } = this.props; + const { children, orientation, isExpanded } = this.props; //TODO: point aria-controls to the menu (optional) //TODO: keyboard interaction return ( - + + + + { this.renderItems() } + + ); } + + renderItems = () => { + return ( +
                                      Hello world!
                                      + ); + }; } export default MenuButton; From 92d6380aefd43bd15695ab615dcf6ed88946d77c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 20:01:02 -0500 Subject: [PATCH 178/286] supply label/labelId props to Menu and aria-controls to MenuButton --- src/Menu/MenuButton.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 2d16217f..bc0dbc51 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -12,6 +12,9 @@ class MenuButton extends React.Component { children: PropTypes.node.isRequired, items: MENUITEMS_PROPTYPE.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal']), + menuLabel: PropTypes.string, + menuId: PropTypes.string, + id: PropTypes.string, isExpanded: PropTypes.bool, }; @@ -22,21 +25,24 @@ class MenuButton extends React.Component { //---- Rendering ---- render() { - const { children, orientation, isExpanded } = this.props; + const { children, orientation, menuLabel, menuId, id, isExpanded } = this.props; - //TODO: point aria-controls to the menu (optional) - //TODO: keyboard interaction return ( { this.renderItems() } From bfbf47e433b6fc516bd65d6751b830479c9fea43 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 20:47:09 -0500 Subject: [PATCH 179/286] give a classname prop, copy and paste item denring code into MenuButton, make isExpanded part of the state --- src/Menu/Menu.jsx | 5 +- src/Menu/MenuButton.jsx | 191 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 189 insertions(+), 7 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 83322032..0a94e362 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; * button that controls its display XOR a label prop. */ function Menu(props) { - const { children, orientation, label, labelId, id } = props; + const { children, orientation, label, labelId, id, className } = props; return (
                                        { children }
                                      @@ -29,6 +30,7 @@ Menu.propTypes = { label: PropTypes.string, labelId: PropTypes.string, id: PropTypes.string, + className: PropTypes.string, }; Menu.defaultProps = { @@ -36,6 +38,7 @@ Menu.defaultProps = { label: undefined, labelId: undefined, id: undefined, + className: undefined, }; export default Menu; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index bc0dbc51..5c5e166c 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -3,6 +3,12 @@ import PropTypes from 'prop-types'; //Components and Styles import Menu from 'src/Menu/Menu'; +import MenuItem from 'src/Menu/MenuItem'; +import ParentMenuItem from 'src/Menu/ParentMenuItem'; +import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; +import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; +import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; +import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -15,17 +21,53 @@ class MenuButton extends React.Component { menuLabel: PropTypes.string, menuId: PropTypes.string, id: PropTypes.string, - isExpanded: PropTypes.bool, }; static defaultProps = { orientation: 'vertical', - isExpanded: false, + menuLabel: undefined, + menuId: undefined, + id: undefined, + }; + + constructor(props) { + super(props); + + const { items } = props; + + this.state = { + isExpanded: false, + expandedIndex: undefined, + }; + + this.childItemRefs = []; + + items.forEach(item => { + const { type, children } = item; + + if(type === 'separator') + return; + if(type === 'radiogroup') { + children.forEach(() => { + this.childItemRefs.push(React.createRef()); + }); + } + else + this.childItemRefs.push(React.createRef()); + }); + } + + //---- Events ---- + onKeyDown = (event) => { + }; + + onChildKeyDown = (event) => { }; //---- Rendering ---- render() { - const { children, orientation, menuLabel, menuId, id, isExpanded } = this.props; + const { children, orientation, menuLabel, menuId, id } = this.props; + const { isExpanded } = this.state; return ( @@ -43,6 +85,7 @@ class MenuButton extends React.Component { label={ menuLabel } labelId={ id } id={ menuId } + className={ isExpanded ? undefined : 'hidden' } > { this.renderItems() }
                                      @@ -51,9 +94,145 @@ class MenuButton extends React.Component { } renderItems = () => { - return ( -
                                      Hello world!
                                      - ); + /* eslint-disable react/no-array-index-key */ + + const { items } = this.props; + const { expandedIndex } = this.state; + const itemNodes = []; + let position = []; + let flattenedPosition = []; + let flattenedIndex = 0; + + items.forEach((item, i) => { + const { type, node, children, orientation, label, labelId, isDisabled } = item; + + if(type === 'item') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'menu') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'checkbox') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + //TODO isChecked? + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'separator') { + itemNodes.push( + + { node } + + ); + } + else if(type === 'radiogroup') { + const radioNodes = []; + + children.forEach((radioItem, j) => { + const { node, isDisabled } = radioItem; + + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + //TODO isChecked? + radioNodes.push( + + { node } + + ); + + flattenedIndex++; + }); + + itemNodes.push( + + { radioNodes } + + ); + } + }); + + return itemNodes; + + /* eslint-enable react/no-array-index-key */ }; } From d4a20cb2878f30b0bc1bb360f530549c85f370e0 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 21:07:02 -0500 Subject: [PATCH 180/286] expand menubutton"s menu when pressing space or enter --- src/Menu/MenuButton.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 5c5e166c..385bb911 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -59,6 +59,24 @@ class MenuButton extends React.Component { //---- Events ---- onKeyDown = (event) => { + const { key } = event; + + //TODO" should we account for menu orientation when doing arrow keys? + if(key === 'Enter' || key === ' ' || key === 'Spacebar') { + event.preventDefault(); + + this.setState({ + isExpanded: true, + }, () => { + this.childItemRefs[0].current.focus(); + }); + } + else if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + } }; onChildKeyDown = (event) => { @@ -77,6 +95,7 @@ class MenuButton extends React.Component { aria-controls={ menuId } id={ id } aria-expanded={ isExpanded } + onKeyDown={ this.onKeyDown } > { children } From c11ca8fc5a5fff39499c54fc6be72dc381df7771 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 21:08:17 -0500 Subject: [PATCH 181/286] arrow up and arrow down for menubutton --- src/Menu/MenuButton.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 385bb911..fefb1bf3 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -73,9 +73,21 @@ class MenuButton extends React.Component { } else if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); + + this.setState({ + isExpanded: true, + }, () => { + this.childItemRefs[this.childItemRefs.length - 1].current.focus(); + }); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); + + this.setState({ + isExpanded: true, + }, () => { + this.childItemRefs[0].current.focus(); + }); } }; From d33c80fdeaca1a0ace4a525484cd56e1983f972f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 24 Jan 2022 21:14:32 -0500 Subject: [PATCH 182/286] copy and paste code from menubar into menubutton --- src/Menu/MenuButton.jsx | 174 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index fefb1bf3..3a13e6f0 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -92,6 +92,114 @@ class MenuButton extends React.Component { }; onChildKeyDown = (event) => { + const { items, orientation } = this.props; + const { key, target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type } = item; + + //console.log(position, flattenedPosition, index, flattenedIndex, item); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + + if(orientation === 'horizontal') { + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusLastChild(); + }); + } + } + else + this.focusPrevChild(flattenedIndex); + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + + if(orientation === 'horizontal') { + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + } + else + this.focusNextChild(flattenedIndex); + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + + if(orientation === 'horizontal') + this.focusPrevChild(flattenedIndex); + else { + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusLastChild(); + }); + } + } + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + + if(orientation === 'horizontal') + this.focusNextChild(flattenedIndex); + else { + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + } + } + else if(key === 'Enter') { + event.preventDefault(); + + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + else { + //TODO activate the item and close the (whole?) menu + } + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + else if(type === 'checkbox') { + //TODO change state without closing the menu + } + else if(type === 'radiogroup') { + //TODO change state without closing the menu + } + else if(type === 'item') { + //TODO activate the item and close the (whole?) menu + } + } + else if(key === 'Home') { + event.preventDefault(); + this.focusFirstChild(); + } + else if(key === 'End') { + event.preventDefault(); + this.focusLastChild(); + } + else if(key === 'Tab') + this.collapseChild(); + else { + //TODO: Any key that corresponds to a printable character (Optional): + //Move focus to the next menu item in the current menu whose label begins + //with that printable character. + } }; //---- Rendering ---- @@ -265,6 +373,72 @@ class MenuButton extends React.Component { /* eslint-enable react/no-array-index-key */ }; + + //---- Misc. ---- + collapseChild = (collapseAll, callback) => { + this.setState({ + expandedIndex: undefined, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + + expandChild = (flattenedIndex, callback) => { + this.setState({ + expandedIndex: flattenedIndex, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + + focusPrevChild = (flattenedIndex, autoExpand = false) => { + const prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; + const prevRef = this.childItemRefs[prevIndex]; + + this.setState(state => { + const { expandedIndex } = state; + const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; + const _autoExpand = prevRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + + //TODO would be nice if there was a better way to map ref indices to item indices + //and vice-versa, checking instanceof ParentMenuItem almost feels sort of abusive + //wrt using refs + return { + tabbableIndex: prevIndex, + expandedIndex: _autoExpand ? prevIndex : undefined, + }; + }, () => { + prevRef.current.focus(); + }); + }; + + focusNextChild = (flattenedIndex, autoExpand = false) => { + const nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; + const nextRef = this.childItemRefs[nextIndex]; + + this.setState(state => { + const { expandedIndex } = state; + const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; + const _autoExpand = nextRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + + return { + tabbableIndex: nextIndex, + expandedIndex: _autoExpand ? nextIndex : undefined, + }; + }, () => { + nextRef.current.focus(); + }); + }; + + focusFirstChild = () => { + this.focusNextChild(this.childItemRefs.length - 1); + }; + + focusLastChild = () => { + this.focusPrevChild(0); + }; } export default MenuButton; From 5e74bf37b840a12642118e1849bb0c2b2e69c425 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 14:36:43 -0500 Subject: [PATCH 183/286] more focus/nav api consolidation --- src/Menu/MenuBar.jsx | 48 +++++++++++++------------------------ src/Menu/MenuButton.jsx | 48 +++++++++++++------------------------ src/Menu/ParentMenuItem.jsx | 16 ++++++------- 3 files changed, 41 insertions(+), 71 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index c1f2a018..6056bc0f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -356,51 +356,37 @@ class MenuBar extends React.Component { }; focusPrevChild = (flattenedIndex, autoExpand = false) => { - const prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; - const prevRef = this.childItemRefs[prevIndex]; + this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1, autoExpand); + }; - this.setState(state => { - const { expandedIndex } = state; - const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; - const _autoExpand = prevRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + focusNextChild = (flattenedIndex, autoExpand = false) => { + this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1, autoExpand); + }; - //TODO would be nice if there was a better way to map ref indices to item indices - //and vice-versa, checking instanceof ParentMenuItem almost feels sort of abusive - //wrt using refs - return { - tabbableIndex: prevIndex, - expandedIndex: _autoExpand ? prevIndex : undefined, - }; - }, () => { - prevRef.current.focus(); - }); + focusFirstChild = () => { + this.focusChild(0); }; - focusNextChild = (flattenedIndex, autoExpand = false) => { - const nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; - const nextRef = this.childItemRefs[nextIndex]; + focusLastChild = () => { + this.focusLastChild(this.childItemRefs.length - 1); + }; + + focusChild = (flattenedIndex, autoExpand) => { + const targetRef = this.childItemRefs[flattenedIndex]; this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; - const _autoExpand = nextRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + const _autoExpand = targetRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); return { - tabbableIndex: nextIndex, - expandedIndex: _autoExpand ? nextIndex : undefined, + tabbableIndex: flattenedIndex, + expandedIndex: _autoExpand ? flattenedIndex : undefined, }; }, () => { - nextRef.current.focus(); + targetRef.current.focus(); }); }; - - focusFirstChild = () => { - this.focusNextChild(this.childItemRefs.length - 1); - }; - - focusLastChild = () => { - this.focusPrevChild(0); - }; } export default MenuBar; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 3a13e6f0..8bd7c272 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -61,7 +61,6 @@ class MenuButton extends React.Component { onKeyDown = (event) => { const { key } = event; - //TODO" should we account for menu orientation when doing arrow keys? if(key === 'Enter' || key === ' ' || key === 'Spacebar') { event.preventDefault(); @@ -394,51 +393,36 @@ class MenuButton extends React.Component { }; focusPrevChild = (flattenedIndex, autoExpand = false) => { - const prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; - const prevRef = this.childItemRefs[prevIndex]; + this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1, autoExpand); + }; - this.setState(state => { - const { expandedIndex } = state; - const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; - const _autoExpand = prevRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + focusNextChild = (flattenedIndex, autoExpand = false) => { + this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1, autoExpand); + }; - //TODO would be nice if there was a better way to map ref indices to item indices - //and vice-versa, checking instanceof ParentMenuItem almost feels sort of abusive - //wrt using refs - return { - tabbableIndex: prevIndex, - expandedIndex: _autoExpand ? prevIndex : undefined, - }; - }, () => { - prevRef.current.focus(); - }); + focusFirstChild = () => { + this.focusChild(0); }; - focusNextChild = (flattenedIndex, autoExpand = false) => { - const nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; - const nextRef = this.childItemRefs[nextIndex]; + focusLastChild = () => { + this.focusChild(this.childItemRefs.length - 1); + }; + + focusChild = (flattenedIndex, autoExpand = false) => { + const targetRef = this.childItemRefs[flattenedIndex]; this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; - const _autoExpand = nextRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + const _autoExpand = targetRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); return { - tabbableIndex: nextIndex, - expandedIndex: _autoExpand ? nextIndex : undefined, + expandedIndex: _autoExpand ? flattenedIndex : undefined, }; }, () => { - nextRef.current.focus(); + targetRef.current.focus(); }); }; - - focusFirstChild = () => { - this.focusNextChild(this.childItemRefs.length - 1); - }; - - focusLastChild = () => { - this.focusPrevChild(0); - }; } export default MenuButton; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index e7999ca4..8bcc4459 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -403,23 +403,23 @@ class ParentMenuItem extends React.Component { }; focusPrevChild = (flattenedIndex) => { - const prevIndex = flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1; - const prevRef = this.childItemRefs[prevIndex]; - prevRef.current.focus(); + this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1); }; focusNextChild = (flattenedIndex) => { - const nextIndex = flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1; - const nextRef = this.childItemRefs[nextIndex]; - nextRef.current.focus(); + this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1); }; focusFirstChild = () => { - this.focusNextChild(this.childItemRefs.length - 1); + this.focusChild(0); }; focusLastChild = () => { - this.focusPrevChild(0); + this.focusChild(this.childItemRefs.length - 1); + }; + + focusChild = (flattenedIndex) => { + this.childItemRefs[flattenedIndex].current.focus(); }; focus = () => { From ea7de54a96e6d8b5d5ac9504fde72bad3db332ca Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 18:21:15 -0500 Subject: [PATCH 184/286] use already-existing methods --- src/Menu/MenuButton.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 8bd7c272..5bef1992 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -67,7 +67,7 @@ class MenuButton extends React.Component { this.setState({ isExpanded: true, }, () => { - this.childItemRefs[0].current.focus(); + this.focusFirstChild(); }); } else if(key === 'ArrowUp' || key === 'Up') { @@ -76,7 +76,7 @@ class MenuButton extends React.Component { this.setState({ isExpanded: true, }, () => { - this.childItemRefs[this.childItemRefs.length - 1].current.focus(); + this.focusLastChild(); }); } else if(key === 'ArrowDown' || key === 'Down') { @@ -85,7 +85,7 @@ class MenuButton extends React.Component { this.setState({ isExpanded: true, }, () => { - this.childItemRefs[0].current.focus(); + this.focusFirstChild(); }); } }; From d180cb3f957779fe58ad233aa99ec04caabfc33f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 18:23:52 -0500 Subject: [PATCH 185/286] give expand/collapsing the button their own methods --- src/Menu/MenuButton.jsx | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 5bef1992..9148fda0 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -63,28 +63,22 @@ class MenuButton extends React.Component { if(key === 'Enter' || key === ' ' || key === 'Spacebar') { event.preventDefault(); - - this.setState({ - isExpanded: true, - }, () => { + + this.expandButton(() => { this.focusFirstChild(); }); } else if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - - this.setState({ - isExpanded: true, - }, () => { + + this.expandButton(() => { this.focusLastChild(); }); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - - this.setState({ - isExpanded: true, - }, () => { + + this.expandButton(() => { this.focusFirstChild(); }); } @@ -374,6 +368,24 @@ class MenuButton extends React.Component { }; //---- Misc. ---- + collapseButton = (callback) => { + this.setState({ + isExpanded: false, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + + expandButton = (callback) => { + this.setState({ + isExpanded: true, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + collapseChild = (collapseAll, callback) => { this.setState({ expandedIndex: undefined, From e1e6f75994ce57e7eee8e83d8dc16806645b44e1 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 18:25:08 -0500 Subject: [PATCH 186/286] let"s not worry about orientation for now --- src/Menu/MenuButton.jsx | 44 ++++++++++------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 9148fda0..f602998b 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -99,53 +99,29 @@ class MenuButton extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - if(orientation === 'horizontal') { - if(type === 'menu') { - this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusLastChild(); - }); - } - } - else - this.focusPrevChild(flattenedIndex); + this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - if(orientation === 'horizontal') { - if(type === 'menu') { - this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); - }); - } - } - else - this.focusNextChild(flattenedIndex); + this.focusNextChild(flattenedIndex); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - if(orientation === 'horizontal') - this.focusPrevChild(flattenedIndex); - else { - if(type === 'menu') { - this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusLastChild(); - }); - } + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusLastChild(); + }); } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - if(orientation === 'horizontal') - this.focusNextChild(flattenedIndex); - else { - if(type === 'menu') { - this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); - }); - } + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); } } else if(key === 'Enter') { From c6e6e329e3b16ba1cdcc447ba81bfbe9d7729cc4 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 18:33:00 -0500 Subject: [PATCH 187/286] I don"t think there"s actually anything to do here if the orientation is vertical --- src/Menu/MenuButton.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index f602998b..8770faa2 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -108,12 +108,6 @@ class MenuButton extends React.Component { } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - - if(type === 'menu') { - this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusLastChild(); - }); - } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); From 22fbca86292a33ea18165776584243806b3cbc1c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 18:48:27 -0500 Subject: [PATCH 188/286] collapse the button on escape and tab --- src/Menu/MenuButton.jsx | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 8770faa2..fe57a1ef 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -39,7 +39,8 @@ class MenuButton extends React.Component { isExpanded: false, expandedIndex: undefined, }; - + + this.buttonRef = React.createRef(); this.childItemRefs = []; items.forEach(item => { @@ -156,8 +157,15 @@ class MenuButton extends React.Component { event.preventDefault(); this.focusLastChild(); } - else if(key === 'Tab') - this.collapseChild(); + else if(key === 'Escape' || key === 'Esc') { + this.collapseButton(() => { + this.focus(); + }); + } + else if(key === 'Tab') { + //TODO: do we need to recursively collapse any children? + this.collapseButton(); + } else { //TODO: Any key that corresponds to a printable character (Optional): //Move focus to the next menu item in the current menu whose label begins @@ -179,6 +187,7 @@ class MenuButton extends React.Component { id={ id } aria-expanded={ isExpanded } onKeyDown={ this.onKeyDown } + ref={ this.buttonRef } > { children } @@ -360,7 +369,9 @@ class MenuButton extends React.Component { this.setState({ expandedIndex: undefined, }, () => { - if(typeof callback === 'function') + if(collapseAll) + this.collapseButton(callback); + else if(typeof callback === 'function') callback(); }); }; @@ -405,6 +416,10 @@ class MenuButton extends React.Component { targetRef.current.focus(); }); }; + + focus = () => { + this.buttonRef.current.focus(); + }; } export default MenuButton; From c434ca1522d1e6e92fe7c5e4e255e596b6cde3ae Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 19:36:19 -0500 Subject: [PATCH 189/286] feels like a somewhat cheap hack to get parentmenuitem to know that its part of a menubutton and not a menubar, but it works (for now) --- src/Menu/MenuButton.jsx | 2 -- src/Menu/ParentMenuItem.jsx | 12 ++++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index fe57a1ef..0c8900d2 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -252,8 +252,6 @@ class MenuButton extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } collapse={ this.collapseChild } - focusPrevMenubarItem={ this.focusPrevChild } - focusNextMenubarItem={ this.focusNextChild } orientation={ orientation } label={ label } labelId={ labelId } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8bcc4459..83644b6d 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -20,8 +20,8 @@ class ParentMenuItem extends React.Component { flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, collapse: PropTypes.func.isRequired, - focusPrevMenubarItem: PropTypes.func.isRequired, - focusNextMenubarItem: PropTypes.func.isRequired, + focusPrevMenubarItem: PropTypes.func, + focusNextMenubarItem: PropTypes.func, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, @@ -88,7 +88,7 @@ class ParentMenuItem extends React.Component { this.focusPrevChild(flattenedIndex); else { collapse(false, () => { - if(level === 1) + if(level === 1 && focusPrevMenubarItem) focusPrevMenubarItem(flattenedRootIndex, true); else this.focus(); @@ -106,7 +106,7 @@ class ParentMenuItem extends React.Component { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } - else { + else if(focusNextMenubarItem) { collapse(true, () => { focusNextMenubarItem(flattenedRootIndex, true); }); @@ -118,7 +118,7 @@ class ParentMenuItem extends React.Component { if(orientation === 'vertical') { collapse(false, () => { - if(level === 1) + if(level === 1 && focusPrevMenubarItem) focusPrevMenubarItem(flattenedRootIndex, true); else this.focus(); @@ -136,7 +136,7 @@ class ParentMenuItem extends React.Component { this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } - else { + else if(focusNextMenubarItem) { collapse(true, () => { focusNextMenubarItem(flattenedRootIndex, true); }); From 7afc91083c448680a6d0ab7d7fabd3c2e8de6e56 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 19:45:10 -0500 Subject: [PATCH 190/286] menubutton accounts for orientation --- src/App.jsx | 6 ++++++ src/Menu/MenuButton.jsx | 35 ++++++++++++++++++++++++++--------- src/styles.scss | 2 +- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index dcadd88e..e505bc23 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1278,6 +1278,12 @@ function App() { Menu Button + + Menu Button 2 + + + Menu Button 3 + diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 0c8900d2..dfd7f95c 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -99,24 +99,41 @@ class MenuButton extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - - this.focusPrevChild(flattenedIndex); + + if(orientation === 'vertical') + this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - - this.focusNextChild(flattenedIndex); + + if(orientation === 'vertical') + this.focusNextChild(flattenedIndex); + else { + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); + + if(orientation === 'horizontal') + this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - - if(type === 'menu') { - this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); - }); + + if(orientation === 'vertical') { + if(type === 'menu') { + this.expandChild(flattenedIndex, () => { + this.childItemRefs[flattenedIndex].current.focusFirstChild(); + }); + } + } + else { + this.focusNextChild(flattenedIndex); } } else if(key === 'Enter') { diff --git a/src/styles.scss b/src/styles.scss index 3ea60303..47f89ba0 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -3,7 +3,7 @@ } .hidden { - display: none; + display: none !important; } [role="menuitem"][aria-expanded="false"] + [role="menu"] { From 25973dd7f0c5a72566b6ae7fd76b321d3e04d650 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 20:06:53 -0500 Subject: [PATCH 191/286] fix linter complaints, menubutton doesn"t actually care about autoexpand --- src/Menu/MenuButton.jsx | 46 +++++++++++++------------------------ src/Menu/ParentMenuItem.jsx | 2 ++ 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index dfd7f95c..4ceb2d9d 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -17,7 +17,7 @@ class MenuButton extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENUITEMS_PROPTYPE.isRequired, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal']), + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), menuLabel: PropTypes.string, menuId: PropTypes.string, id: PropTypes.string, @@ -39,7 +39,7 @@ class MenuButton extends React.Component { isExpanded: false, expandedIndex: undefined, }; - + this.buttonRef = React.createRef(); this.childItemRefs = []; @@ -61,24 +61,24 @@ class MenuButton extends React.Component { //---- Events ---- onKeyDown = (event) => { const { key } = event; - + if(key === 'Enter' || key === ' ' || key === 'Spacebar') { event.preventDefault(); - + this.expandButton(() => { this.focusFirstChild(); }); } else if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - + this.expandButton(() => { this.focusLastChild(); }); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - + this.expandButton(() => { this.focusFirstChild(); }); @@ -99,13 +99,13 @@ class MenuButton extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - + if(orientation === 'vertical') this.focusPrevChild(flattenedIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - + if(orientation === 'vertical') this.focusNextChild(flattenedIndex); else { @@ -124,7 +124,7 @@ class MenuButton extends React.Component { } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - + if(orientation === 'vertical') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { @@ -132,9 +132,8 @@ class MenuButton extends React.Component { }); } } - else { + else this.focusNextChild(flattenedIndex); - } } else if(key === 'Enter') { event.preventDefault(); @@ -180,7 +179,6 @@ class MenuButton extends React.Component { }); } else if(key === 'Tab') { - //TODO: do we need to recursively collapse any children? this.collapseButton(); } else { @@ -400,12 +398,12 @@ class MenuButton extends React.Component { }); }; - focusPrevChild = (flattenedIndex, autoExpand = false) => { - this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1, autoExpand); + focusPrevChild = (flattenedIndex) => { + this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1); }; - focusNextChild = (flattenedIndex, autoExpand = false) => { - this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1, autoExpand); + focusNextChild = (flattenedIndex) => { + this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1); }; focusFirstChild = () => { @@ -416,20 +414,8 @@ class MenuButton extends React.Component { this.focusChild(this.childItemRefs.length - 1); }; - focusChild = (flattenedIndex, autoExpand = false) => { - const targetRef = this.childItemRefs[flattenedIndex]; - - this.setState(state => { - const { expandedIndex } = state; - const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; - const _autoExpand = targetRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); - - return { - expandedIndex: _autoExpand ? flattenedIndex : undefined, - }; - }, () => { - targetRef.current.focus(); - }); + focusChild = (flattenedIndex) => { + this.childItemRefs[flattenedIndex].current.focus(); }; focus = () => { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 83644b6d..ae92a361 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -31,6 +31,8 @@ class ParentMenuItem extends React.Component { }; static defaultProps = { + focusPrevMenubarItem: undefined, + focusNextMenubarItem: undefined, orientation: 'vertical', label: undefined, labelId: undefined, From b8376573c9afcc7227df682bc3d26e5d826119f8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 25 Jan 2022 21:40:49 -0500 Subject: [PATCH 192/286] comment to hopefully clarify some menubutton props --- src/Menu/MenuButton.jsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 4ceb2d9d..0c946660 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -13,6 +13,25 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; +/* + * Note about labels and IDs: + * + * 1) Passing in an id prop means giving the menu an aria-labelledby attribute + * that points to the
                                    ); } diff --git a/src/Menu/utils.js b/src/Menu/utils.js new file mode 100644 index 00000000..e2a2cb60 --- /dev/null +++ b/src/Menu/utils.js @@ -0,0 +1,168 @@ +import React from 'react'; + +//Components and Styles +import MenuItem from 'src/Menu/MenuItem'; +import ParentMenuItem from 'src/Menu/ParentMenuItem'; +import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; +import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; +import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; +import MenuItemRadio from 'src/Menu/MenuItemRadio'; + +/** + * Renders the menu items in a single level of a menu, + * whether it be from a menu button, menubar, or a parent + * menuitem. + * + * While this function doesn't accept arguments, it assumes + * this is being used in the context of a React component + * calling this with bind(). + * + * @return {object[]} + */ +export function renderItems() { + /* eslint-disable react/no-array-index-key */ + console.log(this); + const { items } = this.props; + const { tabbableIndex, expandedIndex } = this.state; + const itemNodes = []; + let position = []; + let flattenedPosition = []; + let flattenedIndex = 0; + + items.forEach((item, i) => { + const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; + + if(type === 'item') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'menu') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'checkbox') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'separator') { + itemNodes.push( + + { node } + + ); + } + else if(type === 'radiogroup') { + const radioNodes = []; + + children.forEach((radioItem, j) => { + const { node, isDisabled, isChecked, value } = radioItem; + + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + radioNodes.push( + + { node } + + ); + + flattenedIndex++; + }); + + itemNodes.push( + + { radioNodes } + + ); + } + }); + + return itemNodes; + + /* eslint-enable react/no-array-index-key */ +} From 4511d016ac6840cb702d63adff3e11ea2ae79340 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 28 Jan 2022 10:01:14 -0500 Subject: [PATCH 216/286] I think for now, I"ll hold off on this --- src/Menu/MenuBar.jsx | 5 +- src/Menu/utils.js | 168 ------------------------------------------- 2 files changed, 1 insertion(+), 172 deletions(-) delete mode 100644 src/Menu/utils.js diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 65506656..735dcf11 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -11,7 +11,6 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; -import { renderItems } from 'src/Menu/utils'; /* * Note: @@ -193,7 +192,6 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { const { orientation, label, labelId } = this.props; - const items = renderItems.bind(this)(); //console.log(this.props, this.state, this.childItemRefs); @@ -204,8 +202,7 @@ class MenuBar extends React.Component { aria-labelledby={ labelId } aria-label={ label } > - { /*this.renderItems()*/ } - { items } + this.renderItems()
                                  ); } diff --git a/src/Menu/utils.js b/src/Menu/utils.js deleted file mode 100644 index e2a2cb60..00000000 --- a/src/Menu/utils.js +++ /dev/null @@ -1,168 +0,0 @@ -import React from 'react'; - -//Components and Styles -import MenuItem from 'src/Menu/MenuItem'; -import ParentMenuItem from 'src/Menu/ParentMenuItem'; -import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; -import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; -import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; -import MenuItemRadio from 'src/Menu/MenuItemRadio'; - -/** - * Renders the menu items in a single level of a menu, - * whether it be from a menu button, menubar, or a parent - * menuitem. - * - * While this function doesn't accept arguments, it assumes - * this is being used in the context of a React component - * calling this with bind(). - * - * @return {object[]} - */ -export function renderItems() { - /* eslint-disable react/no-array-index-key */ - console.log(this); - const { items } = this.props; - const { tabbableIndex, expandedIndex } = this.state; - const itemNodes = []; - let position = []; - let flattenedPosition = []; - let flattenedIndex = 0; - - items.forEach((item, i) => { - const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; - - if(type === 'item') { - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'menu') { - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'checkbox') { - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'separator') { - itemNodes.push( - - { node } - - ); - } - else if(type === 'radiogroup') { - const radioNodes = []; - - children.forEach((radioItem, j) => { - const { node, isDisabled, isChecked, value } = radioItem; - - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - radioNodes.push( - - { node } - - ); - - flattenedIndex++; - }); - - itemNodes.push( - - { radioNodes } - - ); - } - }); - - return itemNodes; - - /* eslint-enable react/no-array-index-key */ -} From 3efd71d3cf33fe23aac6df3c50cbc64cc460aefb Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 28 Jan 2022 11:27:43 -0500 Subject: [PATCH 217/286] first attempt at offloading focus duties to a HOC --- src/Menu/MenuBar.jsx | 59 +++++++++++++++++---------- src/Menu/createMenuFocusManager.jsx | 62 +++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 src/Menu/createMenuFocusManager.jsx diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 735dcf11..357888c4 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -8,6 +8,7 @@ import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; +import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -25,6 +26,14 @@ class MenuBar extends React.Component { orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, + //From MenuFocusManager + setManagerRef: PropTypes.func.isRequired, + setItemRef: PropTypes.func.isRequired, + focusItem: PropTypes.func.isRequired, + focusPrevItem: PropTypes.func.isRequired, + focusNextItem: PropTypes.func.isRequired, + focusFirstItem: PropTypes.func.isRequired, + focusLastItem: PropTypes.func.isRequired, }; static defaultProps = { @@ -93,8 +102,10 @@ class MenuBar extends React.Component { }); } } - else - this.focusPrevChild(flattenedIndex); + else { + this.props.focusPrevItem(flattenedIndex); + //this.focusPrevChild(flattenedIndex); + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); @@ -106,14 +117,18 @@ class MenuBar extends React.Component { }); } } - else - this.focusNextChild(flattenedIndex); + else { + this.props.focusNextItem(flattenedIndex); + //this.focusNextChild(flattenedIndex); + } } - else if(key === 'ArrowLeft' || key === 'Left') { + else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - if(orientation === 'horizontal') - this.focusPrevChild(flattenedIndex); + if(orientation === 'horizontal') { + this.props.focusPrevItem(flattenedIndex); + //this.focusPrevChild(flattenedIndex); + } else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { @@ -125,8 +140,10 @@ class MenuBar extends React.Component { else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - if(orientation === 'horizontal') - this.focusNextChild(flattenedIndex); + if(orientation === 'horizontal') { + this.props.focusNextItem(flattenedIndex); + //this.focusNextChild(flattenedIndex); + } else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { @@ -179,11 +196,13 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); - this.focusFirstChild(); + //this.focusFirstChild(); + this.props.focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - this.focusLastChild(); + //this.focusLastChild(); + this.props.focusLastItem(); } else if(key === 'Tab') this.collapseChild(); @@ -202,7 +221,7 @@ class MenuBar extends React.Component { aria-labelledby={ labelId } aria-label={ label } > - this.renderItems() + { this.renderItems() }
                                ); } @@ -234,7 +253,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -256,16 +275,16 @@ class MenuBar extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } collapse={ this.collapseChild } - focusPrevRootItem={ this.focusPrevChild } - focusNextRootItem={ this.focusNextChild } - focusRootItem={ this.focusChild } + focusPrevRootItem={ this.props.focusPrevItem /*this.focusPrevChild*/ } + focusNextRootItem={ this.props.focusNextItem /*this.focusNextChild*/ } + focusRootItem={ this.props.focusItem /*this.focusChild*/ } orientation={ orientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -288,7 +307,7 @@ class MenuBar extends React.Component { isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -329,7 +348,7 @@ class MenuBar extends React.Component { isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } data-value={ value } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -408,4 +427,4 @@ class MenuBar extends React.Component { }; } -export default MenuBar; +export default createMenuFocusManager(MenuBar); diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx new file mode 100644 index 00000000..241eaf89 --- /dev/null +++ b/src/Menu/createMenuFocusManager.jsx @@ -0,0 +1,62 @@ +import React from 'react'; + +export default function createMenuFocusManager(Component) { + return class MenuFocusManager extends React.Component { + constructor(props) { + super(props); + + this.managerRef; + this.itemRefs = []; + } + + //---- Rendering ---- + render() { + console.log(this.managerRef, this.itemRefs); + + return ( + + ); + } + + //---- Misc. ---- + setManagerRef = (ref) => { + console.log(ref); + this.managerRef = ref; + }; + + setItemRef = (ref) => { + console.log(ref); + this.itemRefs.push(ref); + }; + + focusItem = (index) => { + //TODO: autoexpand capabilities? + this.itemRefs[index].focus(); + }; + + focusPrevItem = (index) => { + this.focusItem(index === 0 ? this.itemRefs.length - 1 : index - 1); + }; + + focusNextItem = (index) => { + this.focusItem(index === this.itemRefs.length - 1 ? 0 : index + 1); + }; + + focusFirstItem = () => { + this.focusItem(0); + }; + + focusLastItem = () => { + this.focusItem(this.itemRefs.length - 1); + }; + } +} From 23ba5d7c388ab8f6e68ce0b2dda7035e17aa5ead Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 28 Jan 2022 12:18:41 -0500 Subject: [PATCH 218/286] remove a bunch of functionality --- src/Menu/MenuBar.jsx | 44 +++++++++++++++++-------------------- src/Menu/ParentMenuItem.jsx | 27 ++++++++++------------- src/MenuButtonOne.jsx | 4 ++-- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 357888c4..b1794c4a 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -98,7 +98,7 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusLastChild(); + //this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -113,7 +113,7 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } } @@ -132,7 +132,7 @@ class MenuBar extends React.Component { else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusLastChild(); + //this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -147,7 +147,7 @@ class MenuBar extends React.Component { else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } } @@ -157,7 +157,7 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else if(type === 'checkbox') { @@ -178,7 +178,7 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else if(type === 'checkbox') { @@ -250,9 +250,9 @@ class MenuBar extends React.Component { key={ i } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ this.onChildKeyDown } + onKeyDown={ undefined /*this.onChildKeyDown*/ } isDisabled={ isDisabled } - isTabbable={ flattenedIndex === tabbableIndex } + isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -273,17 +273,17 @@ class MenuBar extends React.Component { items={ children } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ this.onChildKeyDown } - collapse={ this.collapseChild } - focusPrevRootItem={ this.props.focusPrevItem /*this.focusPrevChild*/ } - focusNextRootItem={ this.props.focusNextItem /*this.focusNextChild*/ } - focusRootItem={ this.props.focusItem /*this.focusChild*/ } + onKeyDown={ undefined /*this.onChildKeyDown*/ } + collapse={ undefined /*this.collapseChild*/ } + focusPrevRootItem={ undefined /*this.props.focusPrevItem*/ /*this.focusPrevChild*/ } + focusNextRootItem={ undefined /*this.props.focusNextItem*/ /*this.focusNextChild*/ } + focusRootItem={ undefined /*this.props.focusItem*/ /*this.focusChild*/ } orientation={ orientation } label={ label } labelId={ labelId } - isExpanded={ flattenedIndex === expandedIndex } + isExpanded={ undefined /*flattenedIndex === expandedIndex*/ } isDisabled={ isDisabled } - isTabbable={ flattenedIndex === tabbableIndex } + isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -303,9 +303,9 @@ class MenuBar extends React.Component { key={ i } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ this.onChildKeyDown } + onKeyDown={ undefined /*this.onChildKeyDown*/ } isDisabled={ isDisabled } - isTabbable={ flattenedIndex === tabbableIndex } + isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } isChecked={ isChecked } ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > @@ -317,11 +317,7 @@ class MenuBar extends React.Component { } else if(type === 'separator') { itemNodes.push( - + { node } ); @@ -343,9 +339,9 @@ class MenuBar extends React.Component { subIndex={ j } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ this.onChildKeyDown } + onKeyDown={ undefined /*this.onChildKeyDown*/ } isDisabled={ isDisabled } - isTabbable={ flattenedIndex === tabbableIndex } + isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } isChecked={ isChecked } data-value={ value } ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index d651e1f9..b450a559 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -210,8 +210,7 @@ class ParentMenuItem extends React.Component { } else if(type === 'item') { if(typeof onActivate === 'function') - ; - onActivate(event); + onActivate(event); collapse(true, () => { focusRootItem(flattenedRootIndex); @@ -299,7 +298,7 @@ class ParentMenuItem extends React.Component { key={ i } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ this.onChildKeyDown } + onKeyDown={ undefined /*this.onChildKeyDown*/ } isDisabled={ isDisabled } ref={ this.childItemRefs[flattenedIndex] } > @@ -321,15 +320,15 @@ class ParentMenuItem extends React.Component { items={ children } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ this.onChildKeyDown } - collapse={ this.collapseChild } - focusPrevRootItem={ focusPrevRootItem } - focusNextRootItem={ focusNextRootItem } - focusRootItem={ focusRootItem } + onKeyDown={ undefined /*this.onChildKeyDown*/ } + collapse={ undefined /*this.collapseChild*/ } + focusPrevRootItem={ undefined /*focusPrevRootItem*/ } + focusNextRootItem={ undefined /*focusNextRootItem*/ } + focusRootItem={ undefined /*focusRootItem*/ } orientation={ orientation } label={ label } labelId={ labelId } - isExpanded={ flattenedIndex === expandedIndex } + isExpanded={ undefined /*flattenedIndex === expandedIndex*/ } isDisabled={ isDisabled } ref={ this.childItemRefs[flattenedIndex] } > @@ -350,7 +349,7 @@ class ParentMenuItem extends React.Component { key={ i } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ this.onChildKeyDown } + onKeyDown={ undefined /*this.onChildKeyDown*/ } isDisabled={ isDisabled } isChecked={ isChecked } ref={ this.childItemRefs[flattenedIndex] } @@ -363,11 +362,7 @@ class ParentMenuItem extends React.Component { } else if(type === 'separator') { itemNodes.push( - + { node } ); @@ -389,7 +384,7 @@ class ParentMenuItem extends React.Component { subIndex={ j } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ this.onChildKeyDown } + onKeyDown={ undefined /*this.onChildKeyDown*/ } isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } diff --git a/src/MenuButtonOne.jsx b/src/MenuButtonOne.jsx index 87b29b8e..717c4496 100644 --- a/src/MenuButtonOne.jsx +++ b/src/MenuButtonOne.jsx @@ -3,7 +3,7 @@ import React from 'react'; //Components and Styles import MenuButton from 'src/Menu/MenuButton'; -class MenuBarOne extends React.Component { +class MenuButtonOne extends React.Component { constructor(props) { super(props); @@ -356,4 +356,4 @@ class MenuBarOne extends React.Component { }; } -export default MenuBarOne; +export default MenuButtonOne; From 507566ab0b5f0e98c92d25c56fef6510612d5bd4 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Fri, 28 Jan 2022 12:36:55 -0500 Subject: [PATCH 219/286] get rid of subIndex and per-radio option onActivate --- src/Menu/MenuBar.jsx | 13 +------------ src/Menu/MenuButton.jsx | 13 +------------ src/Menu/MenuItemRadio.jsx | 4 +--- src/Menu/ParentMenuItem.jsx | 13 +------------ src/MenuBarOne.jsx | 8 ++------ src/MenuButtonOne.jsx | 8 ++------ src/utils/propTypes.js | 3 +-- 7 files changed, 9 insertions(+), 53 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index b1794c4a..932506e5 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -77,18 +77,8 @@ class MenuBar extends React.Component { const flattenedPosition = target.dataset.flattenedposition.split(','); const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); - const subIndex = Number.parseInt(target.dataset.subindex, 10); const item = items[index]; - const { type, onActivate: itemOnActivate } = item; - let onActivate; - - if(type === 'radiogroup') { - const radioOption = item.children[subIndex]; - const { onActivate: radioOptionOnActivate } = radioOption; - onActivate = radioOptionOnActivate ? radioOptionOnActivate : itemOnActivate; - } - else - onActivate = itemOnActivate; + const { type, onActivate } = item; //console.log(position, flattenedPosition, index, flattenedIndex, item); @@ -336,7 +326,6 @@ class MenuBar extends React.Component { radioNodes.push( Date: Fri, 28 Jan 2022 12:54:38 -0500 Subject: [PATCH 220/286] bring back previous menubar functionality --- src/Menu/MenuBar.jsx | 70 ++++++++++++++++++------------------- src/Menu/ParentMenuItem.jsx | 18 +++++----- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 932506e5..192fbc79 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -88,13 +88,13 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusLastChild(); + this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } else { - this.props.focusPrevItem(flattenedIndex); - //this.focusPrevChild(flattenedIndex); + //this.props.focusPrevItem(flattenedIndex); + this.focusPrevChild(flattenedIndex); } } else if(key === 'ArrowDown' || key === 'Down') { @@ -103,26 +103,26 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } } else { - this.props.focusNextItem(flattenedIndex); - //this.focusNextChild(flattenedIndex); + //this.props.focusNextItem(flattenedIndex); + this.focusNextChild(flattenedIndex); } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); if(orientation === 'horizontal') { - this.props.focusPrevItem(flattenedIndex); - //this.focusPrevChild(flattenedIndex); + //this.props.focusPrevItem(flattenedIndex); + this.focusPrevChild(flattenedIndex); } else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusLastChild(); + this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -131,13 +131,13 @@ class MenuBar extends React.Component { event.preventDefault(); if(orientation === 'horizontal') { - this.props.focusNextItem(flattenedIndex); - //this.focusNextChild(flattenedIndex); + //this.props.focusNextItem(flattenedIndex); + this.focusNextChild(flattenedIndex); } else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } } @@ -147,7 +147,7 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else if(type === 'checkbox') { @@ -168,7 +168,7 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } else if(type === 'checkbox') { @@ -186,13 +186,13 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); - //this.focusFirstChild(); - this.props.focusFirstItem(); + this.focusFirstChild(); + //this.props.focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - //this.focusLastChild(); - this.props.focusLastItem(); + this.focusLastChild(); + //this.props.focusLastItem(); } else if(key === 'Tab') this.collapseChild(); @@ -240,10 +240,10 @@ class MenuBar extends React.Component { key={ i } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } + onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + isTabbable={ flattenedIndex === tabbableIndex } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } @@ -263,18 +263,18 @@ class MenuBar extends React.Component { items={ children } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } - collapse={ undefined /*this.collapseChild*/ } - focusPrevRootItem={ undefined /*this.props.focusPrevItem*/ /*this.focusPrevChild*/ } - focusNextRootItem={ undefined /*this.props.focusNextItem*/ /*this.focusNextChild*/ } - focusRootItem={ undefined /*this.props.focusItem*/ /*this.focusChild*/ } + onKeyDown={ this.onChildKeyDown } + collapse={ this.collapseChild } + focusPrevRootItem={ /*this.props.focusPrevItem*/ this.focusPrevChild } + focusNextRootItem={ /*this.props.focusNextItem*/ this.focusNextChild } + focusRootItem={ /*this.props.focusItem*/ this.focusChild } orientation={ orientation } label={ label } labelId={ labelId } - isExpanded={ undefined /*flattenedIndex === expandedIndex*/ } + isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } - isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + isTabbable={ flattenedIndex === tabbableIndex } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } @@ -293,11 +293,11 @@ class MenuBar extends React.Component { key={ i } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } + onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } + isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } @@ -328,12 +328,12 @@ class MenuBar extends React.Component { key={ j } position={ position } flattenedPosition={ flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } + onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - isTabbable={ undefined /*flattenedIndex === tabbableIndex*/ } + isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } data-value={ value } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 22c3a2de..acc00c16 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -288,7 +288,7 @@ class ParentMenuItem extends React.Component { key={ i } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } + onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } ref={ this.childItemRefs[flattenedIndex] } > @@ -310,15 +310,15 @@ class ParentMenuItem extends React.Component { items={ children } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } - collapse={ undefined /*this.collapseChild*/ } - focusPrevRootItem={ undefined /*focusPrevRootItem*/ } - focusNextRootItem={ undefined /*focusNextRootItem*/ } - focusRootItem={ undefined /*focusRootItem*/ } + onKeyDown={ this.onChildKeyDown } + collapse={ this.collapseChild } + focusPrevRootItem={ focusPrevRootItem } + focusNextRootItem={ focusNextRootItem } + focusRootItem={ focusRootItem } orientation={ orientation } label={ label } labelId={ labelId } - isExpanded={ undefined /*flattenedIndex === expandedIndex*/ } + isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } ref={ this.childItemRefs[flattenedIndex] } > @@ -339,7 +339,7 @@ class ParentMenuItem extends React.Component { key={ i } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } + onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } ref={ this.childItemRefs[flattenedIndex] } @@ -373,7 +373,7 @@ class ParentMenuItem extends React.Component { key={ j } position={ _position } flattenedPosition={ _flattenedPosition } - onKeyDown={ undefined /*this.onChildKeyDown*/ } + onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } From a0c1fcb922cf90436b27ef8030f906f6eba11ba6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 12:09:18 -0500 Subject: [PATCH 221/286] give menubar a dummy keydown --- src/App.jsx | 2 +- src/Menu/MenuBar.jsx | 92 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/App.jsx b/src/App.jsx index 65b9f1a5..f52ef68d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1289,7 +1289,7 @@ function App() { */ } - + {/**/} { /* diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 192fbc79..d41e2f51 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -198,6 +198,97 @@ class MenuBar extends React.Component { this.collapseChild(); }; + onKeyDown = (event) => { + const { items, orientation } = this.props; + const { key, target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type, onActivate } = item; + + //console.log(position, flattenedPosition, index, flattenedIndex, item); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + + if(orientation === 'horizontal') { + } + else { + } + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + + if(orientation === 'horizontal') { + } + else { + } + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + + if(orientation === 'horizontal') { + } + else { + } + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + + if(orientation === 'horizontal') { + } + else { + } + } + else if(key === 'Enter') { + event.preventDefault(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === 'Home') { + event.preventDefault(); + } + else if(key === 'End') { + event.preventDefault(); + } + else if(key === 'Tab') { + } + }; + + //---- Rendering ---- render() { const { orientation, label, labelId } = this.props; @@ -210,6 +301,7 @@ class MenuBar extends React.Component { aria-orientation={ orientation } aria-labelledby={ labelId } aria-label={ label } + onKeyDown={ this.onKeyDown } > { this.renderItems() }
                              From b82a95907cfe15c5e72030841d8891df61c13a6e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 12:15:01 -0500 Subject: [PATCH 222/286] give parentmenuitem a dummy keydown --- src/Menu/ParentMenuItem.jsx | 109 +++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index acc00c16..6ae598c5 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -69,8 +69,113 @@ class ParentMenuItem extends React.Component { this.childItemRefs.push(React.createRef()); }); } - + //---- Events ---- + onKeyDown = (event) => { + const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation } = this.props; + const { key, target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); + const level = position.length - 1; + const item = items[index]; + const { type, onActivate } = item; + + //console.log(position, flattenedPosition, index, flattenedIndex, level, item); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === 'Home') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'End') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Tab') { + event.stopPropagation(); + } + }; + onChildKeyDown = (event) => { const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation } = this.props; const { key, target } = event; @@ -244,7 +349,7 @@ class ParentMenuItem extends React.Component { aria-haspopup="menu" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ onKeyDown } + onKeyDown={ this.onKeyDown /*onKeyDown */ } aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } From cb8ed3233b1e79f1bc7d401fa5b277e06e84122f Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 12:16:45 -0500 Subject: [PATCH 223/286] remove keydown events from individual menu items --- src/Menu/MenuItem.jsx | 2 +- src/Menu/MenuItemCheckbox.jsx | 2 +- src/Menu/MenuItemRadio.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index ef2c6410..57a1262c 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -9,7 +9,7 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { role="menuitem" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ onKeyDown } + onKeyDown={ undefined /* onKeyDown */ } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } ref={ ref } diff --git a/src/Menu/MenuItemCheckbox.jsx b/src/Menu/MenuItemCheckbox.jsx index caeddf8c..42203ca8 100644 --- a/src/Menu/MenuItemCheckbox.jsx +++ b/src/Menu/MenuItemCheckbox.jsx @@ -9,7 +9,7 @@ const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox(props, ref) role="menuitemcheckbox" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ onKeyDown } + onKeyDown={ undefined /* onKeyDown */ } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } aria-checked={ isChecked } diff --git a/src/Menu/MenuItemRadio.jsx b/src/Menu/MenuItemRadio.jsx index d30c2a5a..e90406c9 100644 --- a/src/Menu/MenuItemRadio.jsx +++ b/src/Menu/MenuItemRadio.jsx @@ -11,7 +11,7 @@ const MenuItemRadio = React.forwardRef(function MenuItemRadio(props, ref) { role="menuitemradio" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ onKeyDown } + onKeyDown={ undefined /* onKeyDown */ } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } aria-checked={ isChecked } From 5b266688f1ec9280116d1f6c1fece6e51284b546 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 12:23:36 -0500 Subject: [PATCH 224/286] convert menu to a class component --- src/Menu/Menu.jsx | 63 +++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 0a94e362..0fa98f0b 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -7,38 +7,41 @@ import PropTypes from 'prop-types'; * - The menu should either have a labelId prop that points to the menuitem or * button that controls its display XOR a label prop. */ -function Menu(props) { - const { children, orientation, label, labelId, id, className } = props; +class Menu extends React.Component { + static propTypes = { + children: PropTypes.node.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, + labelId: PropTypes.string, + id: PropTypes.string, + className: PropTypes.string, + }; - return ( - - ); -} + static defaultProps = { + orientation: 'vertical', + label: undefined, + labelId: undefined, + id: undefined, + className: undefined, + }; -Menu.propTypes = { - children: PropTypes.node.isRequired, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - label: PropTypes.string, - labelId: PropTypes.string, - id: PropTypes.string, - className: PropTypes.string, -}; + //---- Rendering ---- + render() { + const { children, orientation, label, labelId, id, className } = this.props; -Menu.defaultProps = { - orientation: 'vertical', - label: undefined, - labelId: undefined, - id: undefined, - className: undefined, -}; + return ( + + ); + } +} export default Menu; From ad0dc4fded92b28a0adb5822c9d4c0441bb6b4b9 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 12:26:26 -0500 Subject: [PATCH 225/286] the dummy event should probably go on Menu --- src/Menu/Menu.jsx | 112 ++++++++++++++++++++++++++++++++++++ src/Menu/ParentMenuItem.jsx | 108 +--------------------------------- 2 files changed, 114 insertions(+), 106 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 0fa98f0b..ec705644 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -1,6 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; +//Misc. +import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; + /* * Note: * @@ -10,6 +13,7 @@ import PropTypes from 'prop-types'; class Menu extends React.Component { static propTypes = { children: PropTypes.node.isRequired, + items: MENUITEMS_PROPTYPE.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, @@ -25,6 +29,113 @@ class Menu extends React.Component { className: undefined, }; + //---- Events ---- + onKeyDown = (event) => { + const { items, orientation } = this.props; + const { key, target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); + const level = position.length - 1; + const item = items[index]; + const { type, onActivate } = item; + + //console.log(position, flattenedPosition, index, flattenedIndex, level, item); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === 'Home') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'End') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Tab') { + event.stopPropagation(); + } + }; + + //---- Rendering ---- render() { const { children, orientation, label, labelId, id, className } = this.props; @@ -37,6 +148,7 @@ class Menu extends React.Component { aria-labelledby={ labelId } id={ id } className={ className } + onKeyDown={ this.onKeyDown } > { children }
                            diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 6ae598c5..fc51119e 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -71,111 +71,6 @@ class ParentMenuItem extends React.Component { } //---- Events ---- - onKeyDown = (event) => { - const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation } = this.props; - const { key, target } = event; - const position = target.dataset.position.split(','); - const flattenedPosition = target.dataset.flattenedposition.split(','); - const index = Number.parseInt(position[position.length - 1], 10); - const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); - const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); - const level = position.length - 1; - const item = items[index]; - const { type, onActivate } = item; - - //console.log(position, flattenedPosition, index, flattenedIndex, level, item); - - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === 'Home') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'End') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Tab') { - event.stopPropagation(); - } - }; - onChildKeyDown = (event) => { const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation } = this.props; const { key, target } = event; @@ -349,7 +244,7 @@ class ParentMenuItem extends React.Component { aria-haspopup="menu" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ this.onKeyDown /*onKeyDown */ } + onKeyDown={ undefined /*onKeyDown */ } aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } @@ -361,6 +256,7 @@ class ParentMenuItem extends React.Component { orientation={ orientation } label={ label } labelId={ labelId } + items={ this.props.items } > { this.renderItems() } From 986ce0551d9767a1af94d887ccebc3f8e54c6dac Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 16:43:55 -0500 Subject: [PATCH 226/286] give ParentMenuItem a keydown event too --- src/Menu/Menu.jsx | 2 +- src/Menu/MenuBar.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 105 ++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index ec705644..e01256a4 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -42,7 +42,7 @@ class Menu extends React.Component { const item = items[index]; const { type, onActivate } = item; - //console.log(position, flattenedPosition, index, flattenedIndex, level, item); + console.log(position, flattenedPosition, index, flattenedIndex, level, item); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index d41e2f51..212dfafd 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -208,7 +208,7 @@ class MenuBar extends React.Component { const item = items[index]; const { type, onActivate } = item; - //console.log(position, flattenedPosition, index, flattenedIndex, item); + console.log(position, flattenedPosition, index, flattenedIndex, item); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index fc51119e..50abf9d1 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -226,6 +226,111 @@ class ParentMenuItem extends React.Component { collapse(true); }; + onKeyDown = (event) => { + const { items, orientation } = this.props; + const { key, target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); + const level = position.length - 1; + const item = items[index]; + const { type, onActivate } = item; + + console.log(position, flattenedPosition, index, flattenedIndex, level, item); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === 'Home') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'End') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Tab') { + event.stopPropagation(); + } + }; + //---- Rendering ---- render() { const { From 0d781506aa55abfd95b81a3371cc31783e927964 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 16:46:25 -0500 Subject: [PATCH 227/286] actually, scratch that for now --- src/Menu/ParentMenuItem.jsx | 105 ------------------------------------ 1 file changed, 105 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 50abf9d1..fc51119e 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -226,111 +226,6 @@ class ParentMenuItem extends React.Component { collapse(true); }; - onKeyDown = (event) => { - const { items, orientation } = this.props; - const { key, target } = event; - const position = target.dataset.position.split(','); - const flattenedPosition = target.dataset.flattenedposition.split(','); - const index = Number.parseInt(position[position.length - 1], 10); - const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); - const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); - const level = position.length - 1; - const item = items[index]; - const { type, onActivate } = item; - - console.log(position, flattenedPosition, index, flattenedIndex, level, item); - - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === 'Home') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'End') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Tab') { - event.stopPropagation(); - } - }; - //---- Rendering ---- render() { const { From 13f47e72587105f572ad0e15c48bd5dde2c786a3 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 16:56:06 -0500 Subject: [PATCH 228/286] try giving ParentMenuItem control over its own expand/collapse state --- src/Menu/ParentMenuItem.jsx | 124 +++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index fc51119e..c118179f 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -50,6 +50,7 @@ class ParentMenuItem extends React.Component { this.state = { expandedIndex: undefined, + isExpanded: false, }; this.itemRef = React.createRef(); @@ -226,13 +227,120 @@ class ParentMenuItem extends React.Component { collapse(true); }; + onKeyDown = (event) => { + const { items, orientation } = this.props; + const { key, target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); + const level = position.length - 1; + const item = items[index]; + const { type, onActivate } = item; + + console.log(position, flattenedPosition, index, flattenedIndex, level, item); + + if(key === 'ArrowUp' || key === 'Up') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowDown' || key === 'Down') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowLeft' || key === 'Left') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'ArrowRight' || key === 'Right') { + event.preventDefault(); + event.stopPropagation(); + + if(orientation === 'vertical') { + } + else { + } + } + else if(key === 'Enter') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === ' ' || key === 'Spacebar') { + event.preventDefault(); + event.stopPropagation(); + + if(type === 'menu') { + } + else if(type === 'checkbox') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'radiogroup') { + if(typeof onActivate === 'function') + onActivate(event); + } + else if(type === 'item') { + if(typeof onActivate === 'function') + onActivate(event); + } + } + else if(key === 'Home') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'End') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Escape' || key === 'Esc') { + event.preventDefault(); + event.stopPropagation(); + } + else if(key === 'Tab') { + event.stopPropagation(); + } + }; + + //---- Rendering ---- render() { const { children, position, flattenedPosition, onKeyDown, orientation, label, labelId, - isExpanded, isDisabled, isTabbable, + /*isExpanded,*/ isDisabled, isTabbable, } = this.props; + const { isExpanded } = this.state; //console.log(this.props, this.state, this.itemRef, this.childItemRefs); @@ -244,7 +352,7 @@ class ParentMenuItem extends React.Component { aria-haspopup="menu" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ undefined /*onKeyDown */ } + onKeyDown={ this.onKeyDown /* undefined */ /*onKeyDown */ } aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } @@ -405,6 +513,18 @@ class ParentMenuItem extends React.Component { }; //---- Misc. --- + expand = () => { + this.setState({ + isExpanded: true, + }); + }; + + collapse = () => { + this.setState({ + isExpanded: false, + }); + }; + collapseChild = (collapseAll, callback) => { const { collapse } = this.props; From 67a46b42bce115876b45aca844199e3e451f9409 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 16:59:42 -0500 Subject: [PATCH 229/286] wrap ParentMenuItem in a MenuFocusManager --- src/Menu/MenuBar.jsx | 2 ++ src/Menu/ParentMenuItem.jsx | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 212dfafd..396045eb 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -8,6 +8,8 @@ import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; + +//HOCs import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; //Misc. diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index c118179f..f6c44621 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -9,6 +9,9 @@ import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; +//HOCs +import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; + //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -29,6 +32,14 @@ class ParentMenuItem extends React.Component { isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, + //From MenuFocusManager + setManagerRef: PropTypes.func.isRequired, + setItemRef: PropTypes.func.isRequired, + focusItem: PropTypes.func.isRequired, + focusPrevItem: PropTypes.func.isRequired, + focusNextItem: PropTypes.func.isRequired, + focusFirstItem: PropTypes.func.isRequired, + focusLastItem: PropTypes.func.isRequired, }; static defaultProps = { @@ -572,4 +583,4 @@ class ParentMenuItem extends React.Component { }; } -export default ParentMenuItem; +export default createMenuFocusManager(ParentMenuItem); From afecc3791b28fdac9afdfd7a2c1bd8c69887fac8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 17:02:15 -0500 Subject: [PATCH 230/286] have ParentMenuItem start using methods from MenuFocusManager --- src/Menu/ParentMenuItem.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index f6c44621..6de4ff87 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -367,7 +367,7 @@ class ParentMenuItem extends React.Component { aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } - ref={ this.itemRef } + ref={ this.props.setManagerRef /* this.itemRef */ } > { children } @@ -410,7 +410,7 @@ class ParentMenuItem extends React.Component { flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } > { node } @@ -440,7 +440,7 @@ class ParentMenuItem extends React.Component { labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } > { node } @@ -462,7 +462,7 @@ class ParentMenuItem extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } > { node } @@ -497,7 +497,7 @@ class ParentMenuItem extends React.Component { isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } > { node } From d2a84dcf1b9e698758e96bdbb2d0856917cb7113 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 17:03:46 -0500 Subject: [PATCH 231/286] print orientation too --- src/Menu/Menu.jsx | 2 +- src/Menu/MenuBar.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index e01256a4..aaa06c89 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -42,7 +42,7 @@ class Menu extends React.Component { const item = items[index]; const { type, onActivate } = item; - console.log(position, flattenedPosition, index, flattenedIndex, level, item); + console.log(orientation, position, flattenedPosition, index, flattenedIndex, level, item); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 396045eb..e144358b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -210,7 +210,7 @@ class MenuBar extends React.Component { const item = items[index]; const { type, onActivate } = item; - console.log(position, flattenedPosition, index, flattenedIndex, item); + console.log(orientation, position, flattenedPosition, index, flattenedIndex, item); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 6de4ff87..55935295 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -250,7 +250,7 @@ class ParentMenuItem extends React.Component { const item = items[index]; const { type, onActivate } = item; - console.log(position, flattenedPosition, index, flattenedIndex, level, item); + console.log(orientation, position, flattenedPosition, index, flattenedIndex, level, item); if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); From 5aa0ff841077f240ee533aa865f0dce33f69d221 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 17:15:44 -0500 Subject: [PATCH 232/286] scope/naming issue --- src/Menu/ParentMenuItem.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 55935295..6e5b1ab6 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -15,7 +15,7 @@ import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; -class ParentMenuItem extends React.Component { +class _ParentMenuItem extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENUITEMS_PROPTYPE.isRequired, @@ -583,4 +583,6 @@ class ParentMenuItem extends React.Component { }; } -export default createMenuFocusManager(ParentMenuItem); +const ParentMenuItem = createMenuFocusManager(_ParentMenuItem); + +export default ParentMenuItem; From 1a0a97bbd42317dfcdb581e00ff9df09dd2c2ad0 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 17:22:09 -0500 Subject: [PATCH 233/286] MenuFocusManager forwards refs now --- src/Menu/createMenuFocusManager.jsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index 241eaf89..4668ea58 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -1,7 +1,12 @@ import React from 'react'; +import PropTypes from 'prop-types'; export default function createMenuFocusManager(Component) { - return class MenuFocusManager extends React.Component { + class _MenuFocusManager extends React.Component { + static propTypes = { + forwardedRef: PropTypes.object.isRequired, + }; + constructor(props) { super(props); @@ -11,6 +16,8 @@ export default function createMenuFocusManager(Component) { //---- Rendering ---- render() { + const { forwardedRef, ...rest } = this.props; + console.log(this.managerRef, this.itemRefs); return ( @@ -22,7 +29,8 @@ export default function createMenuFocusManager(Component) { focusNextItem={ this.focusNextItem } focusFirstItem={ this.focusFirstItem } focusLastItem={ this.focusLastItem } - { ...this.props } + ref={ forwardedRef } + { ...rest } /> ); } @@ -59,4 +67,8 @@ export default function createMenuFocusManager(Component) { this.focusItem(this.itemRefs.length - 1); }; } + + return React.forwardRef(function MenuFocusManager(props, ref) { + return <_MenuFocusManager {...props} forwardedRef={ ref } />; + }); } From 8ea054eb09c9466720ed1eb165e1b1d312a2c258 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 17:24:11 -0500 Subject: [PATCH 234/286] change forwardedRef proptype declaration --- src/Menu/createMenuFocusManager.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index 4668ea58..7e21c8a8 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -4,7 +4,10 @@ import PropTypes from 'prop-types'; export default function createMenuFocusManager(Component) { class _MenuFocusManager extends React.Component { static propTypes = { - forwardedRef: PropTypes.object.isRequired, + forwardedRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]), }; constructor(props) { From 5e03e55179c08e30378b8f2e2a1401beaf6e77b1 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 18:01:01 -0500 Subject: [PATCH 235/286] let ParentMenuItems know about their parent menu"s orientation --- src/Menu/MenuBar.jsx | 3 ++- src/Menu/ParentMenuItem.jsx | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index e144358b..95eff017 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -313,7 +313,7 @@ class MenuBar extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items } = this.props; + const { items, orientation: parentOrientation } = this.props; const { tabbableIndex, expandedIndex } = this.state; const itemNodes = []; let position = []; @@ -363,6 +363,7 @@ class MenuBar extends React.Component { focusNextRootItem={ /*this.props.focusNextItem*/ this.focusNextChild } focusRootItem={ /*this.props.focusItem*/ this.focusChild } orientation={ orientation } + parentOrientation={ parentOrientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 6e5b1ab6..7a0ac076 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -27,6 +27,7 @@ class _ParentMenuItem extends React.Component { focusNextRootItem: PropTypes.func, focusRootItem: PropTypes.func, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + parentOrientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), //TODO isRequired? not if it comes from a menubutton? label: PropTypes.string, labelId: PropTypes.string, isExpanded: PropTypes.bool, @@ -47,6 +48,7 @@ class _ParentMenuItem extends React.Component { focusNextRootItem: undefined, focusRootItem: undefined, orientation: 'vertical', + parentOrientation: 'vertical', label: undefined, labelId: undefined, isExpanded: false, @@ -343,7 +345,6 @@ class _ParentMenuItem extends React.Component { } }; - //---- Rendering ---- render() { const { @@ -386,7 +387,7 @@ class _ParentMenuItem extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition } = this.props; + const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, orientation: parentOrientation } = this.props; const { expandedIndex } = this.state; const level = position.length; const itemNodes = []; @@ -436,6 +437,7 @@ class _ParentMenuItem extends React.Component { focusNextRootItem={ focusNextRootItem } focusRootItem={ focusRootItem } orientation={ orientation } + parentOrientation={ parentOrientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } From 220eb41e7b0b2c1090ce7728fb208cbb3963bf1d Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 18:28:34 -0500 Subject: [PATCH 236/286] nvm --- src/Menu/MenuBar.jsx | 3 +-- src/Menu/ParentMenuItem.jsx | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 95eff017..e144358b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -313,7 +313,7 @@ class MenuBar extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, orientation: parentOrientation } = this.props; + const { items } = this.props; const { tabbableIndex, expandedIndex } = this.state; const itemNodes = []; let position = []; @@ -363,7 +363,6 @@ class MenuBar extends React.Component { focusNextRootItem={ /*this.props.focusNextItem*/ this.focusNextChild } focusRootItem={ /*this.props.focusItem*/ this.focusChild } orientation={ orientation } - parentOrientation={ parentOrientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 7a0ac076..02a8fee7 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -27,7 +27,6 @@ class _ParentMenuItem extends React.Component { focusNextRootItem: PropTypes.func, focusRootItem: PropTypes.func, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - parentOrientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), //TODO isRequired? not if it comes from a menubutton? label: PropTypes.string, labelId: PropTypes.string, isExpanded: PropTypes.bool, @@ -48,7 +47,6 @@ class _ParentMenuItem extends React.Component { focusNextRootItem: undefined, focusRootItem: undefined, orientation: 'vertical', - parentOrientation: 'vertical', label: undefined, labelId: undefined, isExpanded: false, @@ -387,7 +385,7 @@ class _ParentMenuItem extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, orientation: parentOrientation } = this.props; + const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition } = this.props; const { expandedIndex } = this.state; const level = position.length; const itemNodes = []; @@ -437,7 +435,6 @@ class _ParentMenuItem extends React.Component { focusNextRootItem={ focusNextRootItem } focusRootItem={ focusRootItem } orientation={ orientation } - parentOrientation={ parentOrientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } From 981b967fc74a2160b1d4e8b3aeb47d3943321add Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 18:46:18 -0500 Subject: [PATCH 237/286] bring back (most of) old functionality --- src/Menu/Menu.jsx | 2 ++ src/Menu/MenuBar.jsx | 7 ++++++- src/Menu/MenuItem.jsx | 2 +- src/Menu/MenuItemCheckbox.jsx | 2 +- src/Menu/MenuItemRadio.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 16 ++++++++-------- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index aaa06c89..cf5e11ca 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -31,6 +31,8 @@ class Menu extends React.Component { //---- Events ---- onKeyDown = (event) => { + return; + const { items, orientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index e144358b..c0ff2211 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -201,6 +201,8 @@ class MenuBar extends React.Component { }; onKeyDown = (event) => { + return; + const { items, orientation } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); @@ -494,7 +496,10 @@ class MenuBar extends React.Component { this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; - const _autoExpand = targetRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + + //FIXME forwardRef breaks instanceof ParentMenuItem + //const _autoExpand = targetRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); + const _autoExpand = wasExpanded || autoExpand; return { tabbableIndex: flattenedIndex, diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 57a1262c..ef2c6410 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -9,7 +9,7 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { role="menuitem" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ undefined /* onKeyDown */ } + onKeyDown={ onKeyDown } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } ref={ ref } diff --git a/src/Menu/MenuItemCheckbox.jsx b/src/Menu/MenuItemCheckbox.jsx index 42203ca8..caeddf8c 100644 --- a/src/Menu/MenuItemCheckbox.jsx +++ b/src/Menu/MenuItemCheckbox.jsx @@ -9,7 +9,7 @@ const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox(props, ref) role="menuitemcheckbox" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ undefined /* onKeyDown */ } + onKeyDown={ onKeyDown } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } aria-checked={ isChecked } diff --git a/src/Menu/MenuItemRadio.jsx b/src/Menu/MenuItemRadio.jsx index e90406c9..d30c2a5a 100644 --- a/src/Menu/MenuItemRadio.jsx +++ b/src/Menu/MenuItemRadio.jsx @@ -11,7 +11,7 @@ const MenuItemRadio = React.forwardRef(function MenuItemRadio(props, ref) { role="menuitemradio" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ undefined /* onKeyDown */ } + onKeyDown={ onKeyDown } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } aria-checked={ isChecked } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 02a8fee7..61bb3ae7 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -348,9 +348,9 @@ class _ParentMenuItem extends React.Component { const { children, position, flattenedPosition, onKeyDown, orientation, label, labelId, - /*isExpanded,*/ isDisabled, isTabbable, + isExpanded, isDisabled, isTabbable, } = this.props; - const { isExpanded } = this.state; + //const { isExpanded } = this.state; //console.log(this.props, this.state, this.itemRef, this.childItemRefs); @@ -362,11 +362,11 @@ class _ParentMenuItem extends React.Component { aria-haspopup="menu" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ this.onKeyDown /* undefined */ /*onKeyDown */ } + onKeyDown={ /*this.onKeyDown*/ /* undefined */ onKeyDown } aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } - ref={ this.props.setManagerRef /* this.itemRef */ } + ref={ /*this.props.setManagerRef*/ this.itemRef } > { children } @@ -409,7 +409,7 @@ class _ParentMenuItem extends React.Component { flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } @@ -439,7 +439,7 @@ class _ParentMenuItem extends React.Component { labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } - ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } @@ -461,7 +461,7 @@ class _ParentMenuItem extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } - ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } @@ -496,7 +496,7 @@ class _ParentMenuItem extends React.Component { isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } - ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */ } + ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } > { node } From 77d233a8c0d6a2bdcbe33e2a07b1c544050c7186 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 18:51:03 -0500 Subject: [PATCH 238/286] start reusing hoc focus methods in menubar again --- src/Menu/MenuBar.jsx | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index c0ff2211..8d1c6c79 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -95,8 +95,8 @@ class MenuBar extends React.Component { } } else { - //this.props.focusPrevItem(flattenedIndex); - this.focusPrevChild(flattenedIndex); + this.props.focusPrevItem(flattenedIndex); + //this.focusPrevChild(flattenedIndex); } } else if(key === 'ArrowDown' || key === 'Down') { @@ -110,16 +110,16 @@ class MenuBar extends React.Component { } } else { - //this.props.focusNextItem(flattenedIndex); - this.focusNextChild(flattenedIndex); + this.props.focusNextItem(flattenedIndex); + //this.focusNextChild(flattenedIndex); } } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); if(orientation === 'horizontal') { - //this.props.focusPrevItem(flattenedIndex); - this.focusPrevChild(flattenedIndex); + this.props.focusPrevItem(flattenedIndex); + //this.focusPrevChild(flattenedIndex); } else { if(type === 'menu') { @@ -133,8 +133,8 @@ class MenuBar extends React.Component { event.preventDefault(); if(orientation === 'horizontal') { - //this.props.focusNextItem(flattenedIndex); - this.focusNextChild(flattenedIndex); + this.props.focusNextItem(flattenedIndex); + //this.focusNextChild(flattenedIndex); } else { if(type === 'menu') { @@ -188,13 +188,13 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); - this.focusFirstChild(); - //this.props.focusFirstItem(); + //this.focusFirstChild(); + this.props.focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - this.focusLastChild(); - //this.props.focusLastItem(); + //this.focusLastChild(); + this.props.focusLastItem(); } else if(key === 'Tab') this.collapseChild(); @@ -339,7 +339,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -361,16 +361,16 @@ class MenuBar extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } collapse={ this.collapseChild } - focusPrevRootItem={ /*this.props.focusPrevItem*/ this.focusPrevChild } - focusNextRootItem={ /*this.props.focusNextItem*/ this.focusNextChild } - focusRootItem={ /*this.props.focusItem*/ this.focusChild } + focusPrevRootItem={ this.props.focusPrevItem /*this.focusPrevChild*/ } + focusNextRootItem={ this.props.focusNextItem /*this.focusNextChild*/ } + focusRootItem={ this.props.focusItem /*this.focusChild*/ } orientation={ orientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -393,7 +393,7 @@ class MenuBar extends React.Component { isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -429,7 +429,7 @@ class MenuBar extends React.Component { isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } data-value={ value } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } From 669c5010adc7390b6ebccb754f2b2eeeba516b8c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 18:59:01 -0500 Subject: [PATCH 239/286] give some more methods to MenuFocusManager --- src/Menu/createMenuFocusManager.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index 7e21c8a8..871f0d28 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -32,6 +32,8 @@ export default function createMenuFocusManager(Component) { focusNextItem={ this.focusNextItem } focusFirstItem={ this.focusFirstItem } focusLastItem={ this.focusLastItem } + focusItemFirstChild={ this.focusItemFirstChild } + focusItemLastChild={ this.focusItemLastChild } ref={ forwardedRef } { ...rest } /> @@ -49,6 +51,11 @@ export default function createMenuFocusManager(Component) { this.itemRefs.push(ref); }; + focus = () => { + console.log(this.managerRef); + this.managerRef.focus(); + }; + focusItem = (index) => { //TODO: autoexpand capabilities? this.itemRefs[index].focus(); @@ -69,6 +76,16 @@ export default function createMenuFocusManager(Component) { focusLastItem = () => { this.focusItem(this.itemRefs.length - 1); }; + + focusItemFirstChild = (index) => { + console.log(this.itemRefs[index]); + this.itemRefs[index].props.focusFirstItem(); + }; + + focusItemLastChild = (index) => { + console.log(this.itemRefs[index]); + this.itemRefs[index].props.focusLastItem(); + }; } return React.forwardRef(function MenuFocusManager(props, ref) { From d3537f09dc364e89d5861a7f417db8520600b0c1 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 19:00:43 -0500 Subject: [PATCH 240/286] parentmenuitem uses refs from menufocusmanager again --- src/Menu/ParentMenuItem.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 61bb3ae7..6fa621a2 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -366,7 +366,7 @@ class _ParentMenuItem extends React.Component { aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } - ref={ /*this.props.setManagerRef*/ this.itemRef } + ref={ this.props.setManagerRef /*this.itemRef*/ } > { children } @@ -409,7 +409,7 @@ class _ParentMenuItem extends React.Component { flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -439,7 +439,7 @@ class _ParentMenuItem extends React.Component { labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -461,7 +461,7 @@ class _ParentMenuItem extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -496,7 +496,7 @@ class _ParentMenuItem extends React.Component { isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } - ref={ /*this.props.setItemRef*/ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } From 1901b987eb8750537e12be0f365b502dd4366e03 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 19:24:31 -0500 Subject: [PATCH 241/286] this should bring back everything from menubar"s event handler --- src/Menu/MenuBar.jsx | 21 ++++++++++++++------- src/Menu/ParentMenuItem.jsx | 1 + src/Menu/createMenuFocusManager.jsx | 10 ++++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 8d1c6c79..89d9bc15 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -29,13 +29,14 @@ class MenuBar extends React.Component { label: PropTypes.string, labelId: PropTypes.string, //From MenuFocusManager - setManagerRef: PropTypes.func.isRequired, setItemRef: PropTypes.func.isRequired, focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, focusFirstItem: PropTypes.func.isRequired, focusLastItem: PropTypes.func.isRequired, + focusItemFirstChild: PropTypes.func.isRequired, + focusItemLastChild: PropTypes.func.isRequired, }; static defaultProps = { @@ -90,7 +91,8 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusLastChild(); + this.props.focusItemLastChild(flattenedIndex); + //this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -105,7 +107,8 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); } } @@ -124,7 +127,8 @@ class MenuBar extends React.Component { else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusLastChild(); + this.props.focusItemLastChild(flattenedIndex); + //this.childItemRefs[flattenedIndex].current.focusLastChild(); }); } } @@ -139,7 +143,8 @@ class MenuBar extends React.Component { else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } } @@ -149,7 +154,8 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -170,7 +176,8 @@ class MenuBar extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 6fa621a2..8036d810 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -35,6 +35,7 @@ class _ParentMenuItem extends React.Component { //From MenuFocusManager setManagerRef: PropTypes.func.isRequired, setItemRef: PropTypes.func.isRequired, + focus: PropTypes.func.isRequired, focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index 871f0d28..f43e7e4a 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -27,6 +27,7 @@ export default function createMenuFocusManager(Component) { { - console.log(this.managerRef); this.managerRef.focus(); }; focusItem = (index) => { //TODO: autoexpand capabilities? - this.itemRefs[index].focus(); + const itemRef = this.itemRefs[index]; + + //TODO: this feels somewhat fragile + if(itemRef instanceof HTMLElement) + itemRef.focus(); + else + itemRef.props.focus(); }; focusPrevItem = (index) => { From 21d8cf9dde2082ca9eabeeb247f8b24b31575fb7 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 19:53:03 -0500 Subject: [PATCH 242/286] parentmenuitem uses methods from menufocusmanager --- src/Menu/MenuBar.jsx | 1 - src/Menu/ParentMenuItem.jsx | 60 ++++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 89d9bc15..23e03a5e 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -299,7 +299,6 @@ class MenuBar extends React.Component { } }; - //---- Rendering ---- render() { const { orientation, label, labelId } = this.props; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8036d810..c4b4c958 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -101,26 +101,34 @@ class _ParentMenuItem extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - if(orientation === 'vertical') - this.focusPrevChild(flattenedIndex); + if(orientation === 'vertical') { + //this.focusPrevChild(flattenedIndex); + this.props.focusPrevItem(flattenedIndex); + } else { collapse(false, () => { - if(level === 1 && focusPrevRootItem) + if(level === 1 && focusPrevRootItem) { focusPrevRootItem(flattenedRootIndex, true); - else - this.focus(); + } + else { + //this.focus(); + this.props.focus(); + } }); } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - if(orientation === 'vertical') - this.focusNextChild(flattenedIndex); + if(orientation === 'vertical') { + //this.focusNextChild(flattenedIndex); + this.props.focusNextItem(flattenedIndex); + } else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(focusNextRootItem) { @@ -137,12 +145,16 @@ class _ParentMenuItem extends React.Component { collapse(false, () => { if(level === 1 && focusPrevRootItem) focusPrevRootItem(flattenedRootIndex, true); - else - this.focus(); + else { + //this.focus(); + this.props.focus(); + } }); } - else - this.focusPrevChild(flattenedIndex); + else { + //this.focusPrevChild(flattenedIndex); + this.props.focusPrevItem(flattenedIndex); + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); @@ -150,7 +162,8 @@ class _ParentMenuItem extends React.Component { if(orientation === 'vertical') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(focusNextRootItem) { @@ -159,15 +172,18 @@ class _ParentMenuItem extends React.Component { }); } } - else - this.focusNextChild(flattenedIndex); + else { + //this.focusNextChild(flattenedIndex); + this.props.focusNextItem(flattenedIndex); + } } else if(key === 'Enter') { event.preventDefault(); if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -200,7 +216,8 @@ class _ParentMenuItem extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -222,17 +239,20 @@ class _ParentMenuItem extends React.Component { } else if(key === 'Home') { event.preventDefault(); - this.focusFirstChild(); + //this.focusFirstChild(); + this.props.focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - this.focusLastChild(); + //this.focusLastChild(); + this.props.focusLastItem(); } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); collapse(false, () => { - this.focus(); + //this.focus(); + this.props.focus(); }); } else if(key === 'Tab') From ca01f218710c7986dfbef58e95785235309137c7 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 20:29:02 -0500 Subject: [PATCH 243/286] menubutton starts using refs from menufocusmanager --- src/App.jsx | 2 +- src/Menu/MenuButton.jsx | 24 ++++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index f52ef68d..65b9f1a5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1289,7 +1289,7 @@ function App() { */ } - {/**/} + { /* diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index e1f7c19c..a73da71e 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -10,6 +10,9 @@ import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; +//HOCs +import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; + //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -40,6 +43,15 @@ class MenuButton extends React.Component { menuLabel: PropTypes.string, menuId: PropTypes.string, id: PropTypes.string, + //From MenuFocusManager + setManagerRef: PropTypes.func.isRequired, + setItemRef: PropTypes.func.isRequired, + focus: PropTypes.func.isRequired, + focusItem: PropTypes.func.isRequired, + focusPrevItem: PropTypes.func.isRequired, + focusNextItem: PropTypes.func.isRequired, + focusFirstItem: PropTypes.func.isRequired, + focusLastItem: PropTypes.func.isRequired, }; static defaultProps = { @@ -243,7 +255,7 @@ class MenuButton extends React.Component { id={ id } aria-expanded={ isExpanded } onKeyDown={ this.onKeyDown } - ref={ this.buttonRef } + ref={ /*this.buttonRef*/ this.props.setManagerRef } > { children } @@ -286,7 +298,7 @@ class MenuButton extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -314,7 +326,7 @@ class MenuButton extends React.Component { labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -336,7 +348,7 @@ class MenuButton extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } > { node } @@ -375,7 +387,7 @@ class MenuButton extends React.Component { isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } - ref={ this.childItemRefs[flattenedIndex] } + ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */} > { node } @@ -465,4 +477,4 @@ class MenuButton extends React.Component { }; } -export default MenuButton; +export default createMenuFocusManager(MenuButton); From 452d8c310953ce6d02d90ba3868e4f9b7186f3fb Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Mon, 31 Jan 2022 21:08:54 -0500 Subject: [PATCH 244/286] menubutton uses methods from menufocusmanager --- src/Menu/MenuButton.jsx | 68 +++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index a73da71e..635164fc 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -97,21 +97,24 @@ class MenuButton extends React.Component { event.preventDefault(); this.expandButton(() => { - this.focusFirstChild(); + //this.focusFirstChild(); + this.props.focusFirstItem(); }); } else if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); this.expandButton(() => { - this.focusLastChild(); + //this.focusLastChild(); + //this.props.focusLastItem(); }); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); this.expandButton(() => { - this.focusFirstChild(); + //this.focusFirstChild(); + this.props.focusFirstItem(); }); } }; @@ -131,18 +134,23 @@ class MenuButton extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - if(orientation === 'vertical') - this.focusPrevChild(flattenedIndex); + if(orientation === 'vertical') { + //this.focusPrevChild(flattenedIndex); + this.props.focusPrevItem(flattenedIndex); + } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - if(orientation === 'vertical') - this.focusNextChild(flattenedIndex); + if(orientation === 'vertical') { + //this.focusNextChild(flattenedIndex); + this.props.focusNextItem(flattenedIndex); + } else { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } } @@ -150,8 +158,10 @@ class MenuButton extends React.Component { else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - if(orientation === 'horizontal') - this.focusPrevChild(flattenedIndex); + if(orientation === 'horizontal') { + //this.focusPrevChild(flattenedIndex); + this.props.focusPrevItem(flattenedIndex); + } } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); @@ -159,19 +169,23 @@ class MenuButton extends React.Component { if(orientation === 'vertical') { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } } - else - this.focusNextChild(flattenedIndex); + else { + //this.focusNextChild(flattenedIndex); + this.props.focusNextItem(flattenedIndex); + } } else if(key === 'Enter') { event.preventDefault(); if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -179,7 +193,8 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - this.focus(); + //this.focus(); + this.props.focus(); }); } else if(type === 'radiogroup') { @@ -187,7 +202,8 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - this.focus(); + //this.focus(); + this.props.focus(); }); } else if(type === 'item') { @@ -195,7 +211,8 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - this.focus(); + //this.focus(); + this.props.focus(); }); } } @@ -204,7 +221,8 @@ class MenuButton extends React.Component { if(type === 'menu') { this.expandChild(flattenedIndex, () => { - this.childItemRefs[flattenedIndex].current.focusFirstChild(); + //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + this.props.focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -220,21 +238,25 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - this.focus(); + //this.focus(); + this.props.focus(); }); } } else if(key === 'Home') { event.preventDefault(); - this.focusFirstChild(); + //this.focusFirstChild(); + this.props.focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - this.focusLastChild(); + //this.focusLastChild(); + this.props.focusLastItem(); } else if(key === 'Escape' || key === 'Esc') { this.collapseButton(() => { - this.focus(); + //this.focus(); + this.props.focus(); }); } else if(key === 'Tab') @@ -320,7 +342,7 @@ class MenuButton extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } collapse={ this.collapseChild } - focusRootItem={ this.focus } + focusRootItem={ this.props.focus /*this.focus*/ } orientation={ orientation } label={ label } labelId={ labelId } From eac62792ce7d726740f70f45dd4c309c1fff1cff Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 13:20:05 -0500 Subject: [PATCH 245/286] something feels better about treating menubuttons as a single root --- src/Menu/MenuButton.jsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 635164fc..5a840dc6 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -300,8 +300,8 @@ class MenuButton extends React.Component { const { items } = this.props; const { expandedIndex } = this.state; const itemNodes = []; - let position = []; - let flattenedPosition = []; + let position = [0]; + let flattenedPosition = [0]; let flattenedIndex = 0; items.forEach((item, i) => { @@ -309,9 +309,9 @@ class MenuButton extends React.Component { if(type === 'item') { position = position.slice(0); - position[0] = i; + position[1] = i; flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; + flattenedPosition[1] = flattenedIndex; itemNodes.push( Date: Tue, 1 Feb 2022 13:36:38 -0500 Subject: [PATCH 246/286] test out menubars and menubuttons with varying orientations --- src/App.jsx | 1255 +---------------------------------------- src/MenuBarOne.jsx | 48 ++ src/MenuBarTwo.jsx | 409 ++++++++++++++ src/MenuButtonOne.jsx | 48 ++ src/MenuButtonTwo.jsx | 407 +++++++++++++ 5 files changed, 916 insertions(+), 1251 deletions(-) create mode 100644 src/MenuBarTwo.jsx create mode 100644 src/MenuButtonTwo.jsx diff --git a/src/App.jsx b/src/App.jsx index 65b9f1a5..e8ca81bf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,10 +2,10 @@ import React from 'react'; //Components and Styles import Accordion from 'src/Accordion'; -import MenuButton from 'src/Menu/MenuButton'; -import MenuBar from 'src/Menu/MenuBar'; import MenuBarOne from 'src/MenuBarOne'; +import MenuBarTwo from 'src/MenuBarTwo'; import MenuButtonOne from 'src/MenuButtonOne'; +import MenuButtonTwo from 'src/MenuButtonTwo'; function onDummySubmit(event) { event.preventDefault(); @@ -37,1263 +37,16 @@ const DUMMY_ACCORDION_SECTIONS = [ }, ]; -const MENUITEMS = [ - { - type: 'menu', - node: 'Parent Menuitem 1', - children: [ - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - ], - }, - { - type: 'menu', - node: 'Parent Menuitem 2', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - children: [ - { - type: 'menu', - node: 'Nested Parent Menuitem', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Parent Menuitem 3', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'menu', - node: 'Parent Menuitem 4', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, -]; - -const MENUITEMS_2 = [ - { - type: 'menu', - node: 'Parent Menuitem 1', - orientation: 'horizontal', - children: [ - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - ], - }, - { - type: 'menu', - node: 'Parent Menuitem 2', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Parent Menuitem 3', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'menu', - node: 'Parent Menuitem 4', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, -]; - -const MENUITEMS_3 = [ - { - type: 'menu', - node: 'Parent Menuitem 1', - orientation: 'horizontal', - children: [ - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - ], - }, - { - type: 'menu', - node: 'Parent Menuitem 2', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'checkbox', - node: 'Checkbox 1', - }, - { - type: 'checkbox', - node: 'Checkbox 2', - }, - ], - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Parent Menuitem 3', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'menu', - node: 'Parent Menuitem 4', - orientation: 'horizontal', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, - { - type: 'menu', - node: 'Nested Parent Menuitem', - children: [ - { - type: 'item', - node: 'Hello world!', - }, - { - type: 'item', - node: 'Hello world!', - }, - ], - }, - ], - }, - { - type: 'separator', - }, - { - type: 'radiogroup', - children: [ - { - node: 'Radio Option 1', - }, - { - node: 'Radio Option 2', - }, - { - node: 'Radio Option 3', - }, - ], - }, - { - type: 'separator', - }, -]; - function App() { return (

                            Accordion

                            Menu, Menubar, Menu Button

                            - { /* - - Menu Button - - - Menu Button 2 - - - Menu Button 3 - - */ } + - { /* - - - */ } +
                            ); } diff --git a/src/MenuBarOne.jsx b/src/MenuBarOne.jsx index 74fc9dde..fd724866 100644 --- a/src/MenuBarOne.jsx +++ b/src/MenuBarOne.jsx @@ -241,6 +241,54 @@ class MenuBarOne extends React.Component { node: 'Hello world!', onActivate: this.onActivateSubmenuItem, }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + ], + }, { type: 'menu', node: 'Nested Parent Menuitem', diff --git a/src/MenuBarTwo.jsx b/src/MenuBarTwo.jsx new file mode 100644 index 00000000..e0557b9f --- /dev/null +++ b/src/MenuBarTwo.jsx @@ -0,0 +1,409 @@ +import React from 'react'; + +//Components and Styles +import MenuBar from 'src/Menu/MenuBar'; + +class MenuBarTwo extends React.Component { + constructor(props) { + super(props); + + this.state = { + radioGroupOne: undefined, + checkboxOneState: false, + checkboxTwoState: false, + radioGroupTwo: undefined, + radioGroupThree: undefined, + radioGroupFour: undefined, + checkboxThreeState: false, + checkboxParent: false, + checkboxChild1: false, + checkboxChild2: false, + checkboxChild3: false, + }; + } + + //---- Events ---- + onChangeRadioGroupOne = (event) => { + this.setState({ + radioGroupOne: event.target.dataset.value, + }); + }; + + onToggleCheckboxOne = () => { + this.setState(state => { + return { + checkboxOneState: !state.checkboxOneState, + }; + }); + }; + + onToggleCheckboxTwo = () => { + this.setState(state => { + return { + checkboxTwoState: !state.checkboxTwoState, + }; + }); + }; + + onChangeRadioGroupTwo = (event) => { + this.setState({ + radioGroupTwo: event.target.dataset.value, + }); + }; + + onChangeRadioGroupThree = (event) => { + this.setState({ + radioGroupThree: event.target.dataset.value, + }); + }; + + onChangeRadioGroupFour = (event) => { + this.setState({ + radioGroupFour: event.target.dataset.value, + }); + }; + + onToggleCheckboxThree = () => { + this.setState(state => { + return { + checkboxThreeState: !state.checkboxThreeState, + }; + }); + }; + + onActivateItem = () => { + alert('Hello from menubar item!'); + }; + + onActivateSubmenuItem = (event) => { + alert(`Hello from ${event.target.textContent}`); + }; + + onToggleCheckboxParent = () => { + this.setState(state => { + const { checkboxParent } = state; + let newValue; + + if(checkboxParent || checkboxParent === 'mixed') + newValue = false; + else if(!checkboxParent) + newValue = true; + + return { + checkboxParent: newValue, + checkboxChild1: newValue, + checkboxChild2: newValue, + checkboxChild3: newValue, + }; + }); + }; + + onToggleCheckboxChild1 = () => { + this.setState(state => { + const { checkboxChild1, checkboxChild2, checkboxChild3 } = state; + const newChild1 = !checkboxChild1; + const allTrue = newChild1 && checkboxChild2 && checkboxChild3; + const allFalse = !newChild1 && !checkboxChild2 && !checkboxChild3; + + return { + checkboxChild1: newChild1, + checkboxParent: allTrue ? true : (allFalse ? false : 'mixed'), + }; + }); + }; + + onToggleCheckboxChild2 = () => { + this.setState(state => { + const { checkboxChild1, checkboxChild2, checkboxChild3 } = state; + const newChild2 = !checkboxChild2; + const allTrue = checkboxChild1 && newChild2 && checkboxChild3; + const allFalse = !checkboxChild1 && !newChild2 && !checkboxChild3; + + return { + checkboxChild2: newChild2, + checkboxParent: allTrue ? true : (allFalse ? false : 'mixed'), + }; + }); + }; + + onToggleCheckboxChild3 = () => { + this.setState(state => { + const { checkboxChild1, checkboxChild2, checkboxChild3 } = state; + const newChild3 = !checkboxChild3; + const allTrue = checkboxChild1 && checkboxChild2 && newChild3; + const allFalse = !checkboxChild1 && !checkboxChild2 && !newChild3; + + return { + checkboxChild3: newChild3, + checkboxParent: allTrue ? true : (allFalse ? false : 'mixed'), + }; + }); + }; + + //---- Rendering ---- + render() { + return ( + + ); + } + + //---- Misc. ---- + getItems = () => { + const { + radioGroupOne, checkboxOneState, checkboxTwoState, radioGroupTwo, + radioGroupThree, radioGroupFour, checkboxThreeState, + checkboxParent, checkboxChild1, checkboxChild2, checkboxChild3, + } = this.state; + + return [ + { + type: 'menu', + node: 'Parent Menuitem 1', + orientation: 'horizontal', + children: [ + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupOne, + children: [ + { + node: 'Radio Option 1', + value: 'option1', + isChecked: radioGroupOne === 'option1', + }, + { + node: 'Radio Option 2', + value: 'option2', + isChecked: radioGroupOne === 'option2', + }, + { + node: 'Radio Option 3', + value: 'option3', + isChecked: radioGroupOne === 'option3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + isChecked: checkboxOneState, + onActivate: this.onToggleCheckboxOne, + }, + { + type: 'checkbox', + node: 'Checkbox 2', + isChecked: checkboxTwoState, + onActivate: this.onToggleCheckboxTwo, + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupTwo, + children: [ + { + node: 'Radio Option 1', + isChecked: radioGroupTwo === 'option1', + value: 'option1', + }, + { + node: 'Radio Option 2', + isChecked: radioGroupTwo === 'option2', + value: 'option2', + }, + { + node: 'Radio Option 3', + isChecked: radioGroupTwo === 'option3', + value: 'option3', + }, + ], + }, + ], + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateItem, + }, + { + type: 'menu', + node: 'Parent Menuitem 2', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + ], + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupThree, + children: [ + { + node: 'Radio Option 1', + isChecked: radioGroupThree === 'option1', + value: 'option1', + }, + { + node: 'Radio Option 2', + isChecked: radioGroupThree === 'option2', + value: 'option2', + }, + { + node: 'Radio Option 3', + isChecked: radioGroupThree === 'option3', + value: 'option3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupFour, + children: [ + { + node: 'Radio Option 1', + isChecked: radioGroupFour === 'option1', + value: 'option1', + }, + { + node: 'Radio Option 2', + isChecked: radioGroupFour === 'option2', + value: 'option2', + }, + { + node: 'Radio Option 3', + isChecked: radioGroupFour === 'option3', + value: 'option3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'checkbox', + node: 'Checkbox 3', + onActivate: this.onToggleCheckboxThree, + isChecked: checkboxThreeState, + }, + { + type: 'menu', + node: 'Parent Menuitem 3', + children: [ + { + type: 'checkbox', + node: 'Checkbox Parent', + isChecked: checkboxParent, + onActivate: this.onToggleCheckboxParent, + }, + { + type: 'checkbox', + node: 'Checkbox Child 1', + isChecked: checkboxChild1, + onActivate: this.onToggleCheckboxChild1, + }, + { + type: 'checkbox', + node: 'Checkbox Child 2', + isChecked: checkboxChild2, + onActivate: this.onToggleCheckboxChild2, + }, + { + type: 'checkbox', + node: 'Checkbox Child 3', + isChecked: checkboxChild3, + onActivate: this.onToggleCheckboxChild3, + }, + ], + }, + ]; + }; +} + +export default MenuBarTwo; diff --git a/src/MenuButtonOne.jsx b/src/MenuButtonOne.jsx index edd08a50..d82497ef 100644 --- a/src/MenuButtonOne.jsx +++ b/src/MenuButtonOne.jsx @@ -243,6 +243,54 @@ class MenuButtonOne extends React.Component { node: 'Hello world!', onActivate: this.onActivateSubmenuItem, }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + ], + }, { type: 'menu', node: 'Nested Parent Menuitem', diff --git a/src/MenuButtonTwo.jsx b/src/MenuButtonTwo.jsx new file mode 100644 index 00000000..d4e54d30 --- /dev/null +++ b/src/MenuButtonTwo.jsx @@ -0,0 +1,407 @@ +import React from 'react'; + +//Components and Styles +import MenuButton from 'src/Menu/MenuButton'; + +class MenuButtonOne extends React.Component { + constructor(props) { + super(props); + + this.state = { + radioGroupOne: undefined, + checkboxOneState: false, + checkboxTwoState: false, + radioGroupTwo: undefined, + radioGroupThree: undefined, + radioGroupFour: undefined, + checkboxThreeState: false, + checkboxParent: false, + checkboxChild1: false, + checkboxChild2: false, + checkboxChild3: false, + }; + } + + //---- Events ---- + onChangeRadioGroupOne = (event) => { + this.setState({ + radioGroupOne: event.target.dataset.value, + }); + }; + + onToggleCheckboxOne = () => { + this.setState(state => { + return { + checkboxOneState: !state.checkboxOneState, + }; + }); + }; + + onToggleCheckboxTwo = () => { + this.setState(state => { + return { + checkboxTwoState: !state.checkboxTwoState, + }; + }); + }; + + onChangeRadioGroupTwo = (event) => { + this.setState({ + radioGroupTwo: event.target.dataset.value, + }); + }; + + onChangeRadioGroupThree = (event) => { + this.setState({ + radioGroupThree: event.target.dataset.value, + }); + }; + + onChangeRadioGroupFour = (event) => { + this.setState({ + radioGroupFour: event.target.dataset.value, + }); + }; + + onToggleCheckboxThree = () => { + this.setState(state => { + return { + checkboxThreeState: !state.checkboxThreeState, + }; + }); + }; + + onActivateItem = () => { + alert('Hello from menubar item!'); + }; + + onActivateSubmenuItem = (event) => { + alert(`Hello from ${event.target.textContent}`); + }; + + onToggleCheckboxParent = () => { + this.setState(state => { + const { checkboxParent } = state; + let newValue; + + if(checkboxParent || checkboxParent === 'mixed') + newValue = false; + else if(!checkboxParent) + newValue = true; + + return { + checkboxParent: newValue, + checkboxChild1: newValue, + checkboxChild2: newValue, + checkboxChild3: newValue, + }; + }); + }; + + onToggleCheckboxChild1 = () => { + this.setState(state => { + const { checkboxChild1, checkboxChild2, checkboxChild3 } = state; + const newChild1 = !checkboxChild1; + const allTrue = newChild1 && checkboxChild2 && checkboxChild3; + const allFalse = !newChild1 && !checkboxChild2 && !checkboxChild3; + + return { + checkboxChild1: newChild1, + checkboxParent: allTrue ? true : (allFalse ? false : 'mixed'), + }; + }); + }; + + onToggleCheckboxChild2 = () => { + this.setState(state => { + const { checkboxChild1, checkboxChild2, checkboxChild3 } = state; + const newChild2 = !checkboxChild2; + const allTrue = checkboxChild1 && newChild2 && checkboxChild3; + const allFalse = !checkboxChild1 && !newChild2 && !checkboxChild3; + + return { + checkboxChild2: newChild2, + checkboxParent: allTrue ? true : (allFalse ? false : 'mixed'), + }; + }); + }; + + onToggleCheckboxChild3 = () => { + this.setState(state => { + const { checkboxChild1, checkboxChild2, checkboxChild3 } = state; + const newChild3 = !checkboxChild3; + const allTrue = checkboxChild1 && checkboxChild2 && newChild3; + const allFalse = !checkboxChild1 && !checkboxChild2 && !newChild3; + + return { + checkboxChild3: newChild3, + checkboxParent: allTrue ? true : (allFalse ? false : 'mixed'), + }; + }); + }; + + //---- Rendering ---- + render() { + return ( + + Test Button! + + ); + } + + //---- Misc. ---- + getItems = () => { + const { + radioGroupOne, checkboxOneState, checkboxTwoState, radioGroupTwo, + radioGroupThree, radioGroupFour, checkboxThreeState, + checkboxParent, checkboxChild1, checkboxChild2, checkboxChild3, + } = this.state; + + return [ + { + type: 'menu', + node: 'Parent Menuitem 1', + orientation: 'horizontal', + children: [ + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupOne, + children: [ + { + node: 'Radio Option 1', + value: 'option1', + isChecked: radioGroupOne === 'option1', + }, + { + node: 'Radio Option 2', + value: 'option2', + isChecked: radioGroupOne === 'option2', + }, + { + node: 'Radio Option 3', + value: 'option3', + isChecked: radioGroupOne === 'option3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'checkbox', + node: 'Checkbox 1', + isChecked: checkboxOneState, + onActivate: this.onToggleCheckboxOne, + }, + { + type: 'checkbox', + node: 'Checkbox 2', + isChecked: checkboxTwoState, + onActivate: this.onToggleCheckboxTwo, + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupTwo, + children: [ + { + node: 'Radio Option 1', + isChecked: radioGroupTwo === 'option1', + value: 'option1', + }, + { + node: 'Radio Option 2', + isChecked: radioGroupTwo === 'option2', + value: 'option2', + }, + { + node: 'Radio Option 3', + isChecked: radioGroupTwo === 'option3', + value: 'option3', + }, + ], + }, + ], + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateItem, + }, + { + type: 'menu', + node: 'Parent Menuitem 2', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + orientation: 'horizontal', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + ], + }, + { + type: 'menu', + node: 'Nested Parent Menuitem', + children: [ + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + { + type: 'item', + node: 'Hello world!', + onActivate: this.onActivateSubmenuItem, + }, + ], + }, + + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupThree, + children: [ + { + node: 'Radio Option 1', + isChecked: radioGroupThree === 'option1', + value: 'option1', + }, + { + node: 'Radio Option 2', + isChecked: radioGroupThree === 'option2', + value: 'option2', + }, + { + node: 'Radio Option 3', + isChecked: radioGroupThree === 'option3', + value: 'option3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'radiogroup', + onActivate: this.onChangeRadioGroupFour, + children: [ + { + node: 'Radio Option 1', + isChecked: radioGroupFour === 'option1', + value: 'option1', + }, + { + node: 'Radio Option 2', + isChecked: radioGroupFour === 'option2', + value: 'option2', + }, + { + node: 'Radio Option 3', + isChecked: radioGroupFour === 'option3', + value: 'option3', + }, + ], + }, + { + type: 'separator', + }, + { + type: 'checkbox', + node: 'Checkbox 3', + onActivate: this.onToggleCheckboxThree, + isChecked: checkboxThreeState, + }, + { + type: 'menu', + node: 'Parent Menuitem 3', + children: [ + { + type: 'checkbox', + node: 'Checkbox Parent', + isChecked: checkboxParent, + onActivate: this.onToggleCheckboxParent, + }, + { + type: 'checkbox', + node: 'Checkbox Child 1', + isChecked: checkboxChild1, + onActivate: this.onToggleCheckboxChild1, + }, + { + type: 'checkbox', + node: 'Checkbox Child 2', + isChecked: checkboxChild2, + onActivate: this.onToggleCheckboxChild2, + }, + { + type: 'checkbox', + node: 'Checkbox Child 3', + isChecked: checkboxChild3, + onActivate: this.onToggleCheckboxChild3, + }, + ], + }, + ]; + }; +} + +export default MenuButtonOne; From 8a5721fe63013b4256c37e6eb5f2a35b2f180434 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 13:59:12 -0500 Subject: [PATCH 247/286] make the MenuFocusManager be responsible for tabbableIndex --- src/Menu/MenuBar.jsx | 6 +++--- src/Menu/createMenuFocusManager.jsx | 27 +++++++++++++++++++++------ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 23e03a5e..bdc5eaa7 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -30,6 +30,7 @@ class MenuBar extends React.Component { labelId: PropTypes.string, //From MenuFocusManager setItemRef: PropTypes.func.isRequired, + tabbableIndex: PropTypes.number.isRequired, focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, @@ -51,7 +52,6 @@ class MenuBar extends React.Component { const { items } = props; this.state = { - tabbableIndex: 0, expandedIndex: undefined, }; @@ -321,8 +321,8 @@ class MenuBar extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items } = this.props; - const { tabbableIndex, expandedIndex } = this.state; + const { items, tabbableIndex } = this.props; + const { expandedIndex } = this.state; const itemNodes = []; let position = []; let flattenedPosition = []; diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index f43e7e4a..1c3547c9 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -12,6 +12,16 @@ export default function createMenuFocusManager(Component) { constructor(props) { super(props); + + //TODO: tabbableIndex is only really relevant for the root level + //for menubars. should we always pass down the prop and leave it + //to anyone consuming this HOC to decide whether or not it's + //worth using? or should we force it to be undefined if we know + //the "level" is >= 1? + this.state = { + tabbableIndex: 0, + expandedIndex: -1, + }; this.managerRef; this.itemRefs = []; @@ -20,6 +30,7 @@ export default function createMenuFocusManager(Component) { //---- Rendering ---- render() { const { forwardedRef, ...rest } = this.props; + const { tabbableIndex, expandedIndex } = this.state; console.log(this.managerRef, this.itemRefs); @@ -27,6 +38,7 @@ export default function createMenuFocusManager(Component) { { //TODO: autoexpand capabilities? const itemRef = this.itemRefs[index]; - - //TODO: this feels somewhat fragile - if(itemRef instanceof HTMLElement) - itemRef.focus(); - else - itemRef.props.focus(); + + this.setState({ + tabbableIndex: index, + }, () => { + if(itemRef instanceof HTMLElement) + itemRef.focus(); + else + itemRef.props.focus(); + }); }; focusPrevItem = (index) => { From 9f0a20f3afdfbbd271a81d2e7caf2ac58a24df42 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 16:05:00 -0500 Subject: [PATCH 248/286] (mostly) adopt expand/collapse methods from menufocusmanager --- src/Menu/MenuBar.jsx | 30 ++++++++++++++++--------- src/Menu/MenuButton.jsx | 18 +++++++++------ src/Menu/ParentMenuItem.jsx | 18 +++++++++------ src/Menu/createMenuFocusManager.jsx | 35 ++++++++++++++++++++++++----- 4 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index bdc5eaa7..cad1ccdf 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -31,6 +31,7 @@ class MenuBar extends React.Component { //From MenuFocusManager setItemRef: PropTypes.func.isRequired, tabbableIndex: PropTypes.number.isRequired, + expandedIndex: PropTypes.number.isRequired, focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, @@ -90,7 +91,8 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { this.props.focusItemLastChild(flattenedIndex); //this.childItemRefs[flattenedIndex].current.focusLastChild(); }); @@ -106,7 +108,8 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { this.props.focusItemFirstChild(flattenedIndex); //this.childItemRefs[flattenedIndex].current.focusFirstChild(); }); @@ -126,7 +129,8 @@ class MenuBar extends React.Component { } else { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { this.props.focusItemLastChild(flattenedIndex); //this.childItemRefs[flattenedIndex].current.focusLastChild(); }); @@ -142,7 +146,8 @@ class MenuBar extends React.Component { } else { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -153,7 +158,8 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -175,7 +181,8 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -203,8 +210,10 @@ class MenuBar extends React.Component { //this.focusLastChild(); this.props.focusLastItem(); } - else if(key === 'Tab') - this.collapseChild(); + else if(key === 'Tab') { + //this.collapseChild(); + this.props.collapseItem(); + } }; onKeyDown = (event) => { @@ -321,8 +330,7 @@ class MenuBar extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, tabbableIndex } = this.props; - const { expandedIndex } = this.state; + const { items, tabbableIndex, expandedIndex } = this.props; const itemNodes = []; let position = []; let flattenedPosition = []; @@ -366,7 +374,7 @@ class MenuBar extends React.Component { position={ position } flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapse={ this.collapseChild } + collapse={ this.props.collapseItem /*this.collapseChild*/ } focusPrevRootItem={ this.props.focusPrevItem /*this.focusPrevChild*/ } focusNextRootItem={ this.props.focusNextItem /*this.focusNextChild*/ } focusRootItem={ this.props.focusItem /*this.focusChild*/ } diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 5a840dc6..0b9a2430 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -148,7 +148,8 @@ class MenuButton extends React.Component { } else { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -168,7 +169,8 @@ class MenuButton extends React.Component { if(orientation === 'vertical') { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -183,7 +185,8 @@ class MenuButton extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -220,7 +223,8 @@ class MenuButton extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -297,8 +301,8 @@ class MenuButton extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items } = this.props; - const { expandedIndex } = this.state; + const { items, expandedIndex } = this.props; + //const { expandedIndex } = this.state; const itemNodes = []; let position = [0]; let flattenedPosition = [0]; @@ -341,7 +345,7 @@ class MenuButton extends React.Component { position={ position } flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapse={ this.collapseChild } + collapse={ this.props.collapseItem /*this.collapseChild*/ } focusRootItem={ this.props.focus /*this.focus*/ } orientation={ orientation } label={ label } diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index c4b4c958..639eb957 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -126,7 +126,8 @@ class _ParentMenuItem extends React.Component { } else { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -161,7 +162,8 @@ class _ParentMenuItem extends React.Component { if(orientation === 'vertical') { if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -181,7 +183,8 @@ class _ParentMenuItem extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -215,7 +218,8 @@ class _ParentMenuItem extends React.Component { event.preventDefault(); if(type === 'menu') { - this.expandChild(flattenedIndex, () => { + //this.expandChild(flattenedIndex, () => { + this.props.expandItem(flattenedIndex, () => { //this.childItemRefs[flattenedIndex].current.focusFirstChild(); this.props.focusItemFirstChild(flattenedIndex); }); @@ -406,8 +410,8 @@ class _ParentMenuItem extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition } = this.props; - const { expandedIndex } = this.state; + const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, expandedIndex } = this.props; + //const { expandedIndex } = this.state; const level = position.length; const itemNodes = []; let _position = []; @@ -451,7 +455,7 @@ class _ParentMenuItem extends React.Component { position={ _position } flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapse={ this.collapseChild } + collapse={ this.props.collapseItem /*this.collapseChild*/ } focusPrevRootItem={ focusPrevRootItem } focusNextRootItem={ focusNextRootItem } focusRootItem={ focusRootItem } diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index 1c3547c9..8bf73bd3 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -32,13 +32,16 @@ export default function createMenuFocusManager(Component) { const { forwardedRef, ...rest } = this.props; const { tabbableIndex, expandedIndex } = this.state; - console.log(this.managerRef, this.itemRefs); +// console.log(this.managerRef, this.itemRefs, this.props, this.state); return ( { - console.log(ref); this.managerRef = ref; }; setItemRef = (ref) => { - console.log(ref); this.itemRefs.push(ref); }; + collapseItem = (collapseAll, callback) => { + const { collapse } = this.props; + + console.log(collapseAll, callback); + + this.setState({ + expandedIndex: -1, + }, () => { + if(collapseAll && typeof collapse === 'function') + collapse(true, callback); //FIXME currently broken for MenuButton + else if(typeof callback === 'function') + callback(); + }); + }; + + expandItem = (index, callback) => { + console.log(index, callback); + + this.setState({ + expandedIndex: index, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + focus = () => { this.managerRef.focus(); }; @@ -99,12 +126,10 @@ export default function createMenuFocusManager(Component) { }; focusItemFirstChild = (index) => { - console.log(this.itemRefs[index]); this.itemRefs[index].props.focusFirstItem(); }; focusItemLastChild = (index) => { - console.log(this.itemRefs[index]); this.itemRefs[index].props.focusLastItem(); }; } From c6e3386bc5e4fbe2f38266358ac2daa375429329 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 16:08:25 -0500 Subject: [PATCH 249/286] menubutton no longer uses classes to hide its menu --- src/Menu/Menu.jsx | 5 +---- src/Menu/MenuButton.jsx | 1 - src/styles.scss | 6 ++++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index cf5e11ca..fdbb8263 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -18,7 +18,6 @@ class Menu extends React.Component { label: PropTypes.string, labelId: PropTypes.string, id: PropTypes.string, - className: PropTypes.string, }; static defaultProps = { @@ -26,7 +25,6 @@ class Menu extends React.Component { label: undefined, labelId: undefined, id: undefined, - className: undefined, }; //---- Events ---- @@ -140,7 +138,7 @@ class Menu extends React.Component { //---- Rendering ---- render() { - const { children, orientation, label, labelId, id, className } = this.props; + const { children, orientation, label, labelId, id } = this.props; return (
                              { children } diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 0b9a2430..63cb9c5d 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -290,7 +290,6 @@ class MenuButton extends React.Component { label={ menuLabel } labelId={ id } id={ menuId } - className={ isExpanded ? undefined : 'hidden' } > { this.renderItems() } diff --git a/src/styles.scss b/src/styles.scss index 99249ab1..30af89c3 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -6,8 +6,10 @@ display: none !important; } -[role="menuitem"][aria-expanded="false"] + [role="menu"] { - display: none; +[role="menuitem"], button[aria-haspopup="menu"] { + &[aria-expanded="false"] + [role="menu"] { + display: none; + } } [role="menubar"], [role="menu"] { From 22131e937dd8e2d38aa48c3aa6c596ab16e55962 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 16:48:59 -0500 Subject: [PATCH 250/286] bring back autoexpand capability --- src/Menu/createMenuFocusManager.jsx | 33 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index 8bf73bd3..f83a5161 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +import ParentMenuItem from 'src/Menu/ParentMenuItem'; + export default function createMenuFocusManager(Component) { class _MenuFocusManager extends React.Component { static propTypes = { @@ -95,26 +97,33 @@ export default function createMenuFocusManager(Component) { this.managerRef.focus(); }; - focusItem = (index) => { - //TODO: autoexpand capabilities? + focusItem = (index, autoExpand = false) => { const itemRef = this.itemRefs[index]; - - this.setState({ - tabbableIndex: index, + const isMenu = !(itemRef instanceof HTMLElement); //this feels rather fragile + + this.setState(state => { + const { expandedIndex } = state; + const wasExpanded = expandedIndex !== -1; + const _autoExpand = isMenu && (autoExpand || wasExpanded); + + return { + tabbableIndex: index, + expandedIndex: _autoExpand ? index : -1, + }; }, () => { - if(itemRef instanceof HTMLElement) - itemRef.focus(); - else + if(isMenu) itemRef.props.focus(); + else + itemRef.focus(); }); }; - focusPrevItem = (index) => { - this.focusItem(index === 0 ? this.itemRefs.length - 1 : index - 1); + focusPrevItem = (index, autoExpand) => { + this.focusItem(index === 0 ? this.itemRefs.length - 1 : index - 1, autoExpand); }; - focusNextItem = (index) => { - this.focusItem(index === this.itemRefs.length - 1 ? 0 : index + 1); + focusNextItem = (index, autoExpand) => { + this.focusItem(index === this.itemRefs.length - 1 ? 0 : index + 1, autoExpand); }; focusFirstItem = () => { From 72e415d832b285e791b26b01533f5cdb2edafcd6 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 17:36:58 -0500 Subject: [PATCH 251/286] get rid of older menu management code in MenuButton --- src/Menu/MenuButton.jsx | 167 ++++++++++------------------------------ 1 file changed, 41 insertions(+), 126 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 63cb9c5d..18feb73c 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -46,12 +46,15 @@ class MenuButton extends React.Component { //From MenuFocusManager setManagerRef: PropTypes.func.isRequired, setItemRef: PropTypes.func.isRequired, + expandedIndex: PropTypes.number.isRequired, + collapseItem: PropTypes.func.isRequired, + expandItem: PropTypes.func.isRequired, focus: PropTypes.func.isRequired, - focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, focusFirstItem: PropTypes.func.isRequired, focusLastItem: PropTypes.func.isRequired, + focusItemFirstChild: PropTypes.func.isRequired, }; static defaultProps = { @@ -68,59 +71,39 @@ class MenuButton extends React.Component { this.state = { isExpanded: false, - expandedIndex: undefined, }; - - this.buttonRef = React.createRef(); - this.childItemRefs = []; - - items.forEach(item => { - const { type, children } = item; - - if(type === 'separator') - return; - if(type === 'radiogroup') { - children.forEach(() => { - this.childItemRefs.push(React.createRef()); - }); - } - else - this.childItemRefs.push(React.createRef()); - }); } //---- Events ---- onKeyDown = (event) => { + const { focusFirstItem, focusLastItem } = this.props; const { key } = event; if(key === 'Enter' || key === ' ' || key === 'Spacebar') { event.preventDefault(); this.expandButton(() => { - //this.focusFirstChild(); - this.props.focusFirstItem(); + focusFirstItem(); }); } else if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); this.expandButton(() => { - //this.focusLastChild(); - //this.props.focusLastItem(); + focusLastItem(); }); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); this.expandButton(() => { - //this.focusFirstChild(); - this.props.focusFirstItem(); + focusFirstItem(); }); } }; onChildKeyDown = (event) => { - const { items, orientation } = this.props; + const { items, orientation, expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -134,24 +117,18 @@ class MenuButton extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - if(orientation === 'vertical') { - //this.focusPrevChild(flattenedIndex); - this.props.focusPrevItem(flattenedIndex); - } + if(orientation === 'vertical') + focusPrevItem(flattenedIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - if(orientation === 'vertical') { - //this.focusNextChild(flattenedIndex); - this.props.focusNextItem(flattenedIndex); - } + if(orientation === 'vertical') + focusNextItem(flattenedIndex); else { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } } @@ -159,36 +136,28 @@ class MenuButton extends React.Component { else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - if(orientation === 'horizontal') { - //this.focusPrevChild(flattenedIndex); - this.props.focusPrevItem(flattenedIndex); - } + if(orientation === 'horizontal') + focusPrevItem(flattenedIndex); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); if(orientation === 'vertical') { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } } - else { - //this.focusNextChild(flattenedIndex); - this.props.focusNextItem(flattenedIndex); - } + else + focusNextItem(flattenedIndex); } else if(key === 'Enter') { event.preventDefault(); if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -196,8 +165,7 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - //this.focus(); - this.props.focus(); + focus(); }); } else if(type === 'radiogroup') { @@ -205,8 +173,7 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - //this.focus(); - this.props.focus(); + focus(); }); } else if(type === 'item') { @@ -214,8 +181,7 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - //this.focus(); - this.props.focus(); + focus(); }); } } @@ -223,10 +189,8 @@ class MenuButton extends React.Component { event.preventDefault(); if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -242,25 +206,21 @@ class MenuButton extends React.Component { onActivate(event); this.collapseButton(() => { - //this.focus(); - this.props.focus(); + focus(); }); } } else if(key === 'Home') { event.preventDefault(); - //this.focusFirstChild(); - this.props.focusFirstItem(); + focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - //this.focusLastChild(); - this.props.focusLastItem(); + focusLastItem(); } else if(key === 'Escape' || key === 'Esc') { this.collapseButton(() => { - //this.focus(); - this.props.focus(); + focus(); }); } else if(key === 'Tab') @@ -269,7 +229,7 @@ class MenuButton extends React.Component { //---- Rendering ---- render() { - const { children, orientation, menuLabel, menuId, id } = this.props; + const { children, orientation, menuLabel, menuId, id, setManagerRef } = this.props; const { isExpanded } = this.state; return ( @@ -281,7 +241,7 @@ class MenuButton extends React.Component { id={ id } aria-expanded={ isExpanded } onKeyDown={ this.onKeyDown } - ref={ /*this.buttonRef*/ this.props.setManagerRef } + ref={ setManagerRef } > { children } @@ -300,8 +260,7 @@ class MenuButton extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, expandedIndex } = this.props; - //const { expandedIndex } = this.state; + const { items, expandedIndex, setItemRef, collapseItem, focus } = this.props; const itemNodes = []; let position = [0]; let flattenedPosition = [0]; @@ -323,7 +282,7 @@ class MenuButton extends React.Component { flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -344,14 +303,14 @@ class MenuButton extends React.Component { position={ position } flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapse={ this.props.collapseItem /*this.collapseChild*/ } - focusRootItem={ this.props.focus /*this.focus*/ } + collapse={ collapseItem } + focusRootItem={ focus } orientation={ orientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -373,7 +332,7 @@ class MenuButton extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -412,7 +371,7 @@ class MenuButton extends React.Component { isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } - ref={ this.props.setItemRef /* this.childItemRefs[flattenedIndex] */} + ref={ setItemRef } > { node } @@ -456,50 +415,6 @@ class MenuButton extends React.Component { callback(); }); }; - - collapseChild = (collapseAll, callback) => { - this.setState({ - expandedIndex: undefined, - }, () => { - if(collapseAll) - this.collapseButton(callback); - else if(typeof callback === 'function') - callback(); - }); - }; - - expandChild = (flattenedIndex, callback) => { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { - if(typeof callback === 'function') - callback(); - }); - }; - - focusPrevChild = (flattenedIndex) => { - this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1); - }; - - focusNextChild = (flattenedIndex) => { - this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1); - }; - - focusFirstChild = () => { - this.focusChild(0); - }; - - focusLastChild = () => { - this.focusChild(this.childItemRefs.length - 1); - }; - - focusChild = (flattenedIndex) => { - this.childItemRefs[flattenedIndex].current.focus(); - }; - - focus = () => { - this.buttonRef.current.focus(); - }; } export default createMenuFocusManager(MenuButton); From 00e25551b74950be7cad12981d937dd3f1c15bf1 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:17:26 -0500 Subject: [PATCH 252/286] get rid of old focus/expand/etc. code in menubar --- src/Menu/MenuBar.jsx | 180 +++++++++------------------------------- src/Menu/MenuButton.jsx | 4 +- 2 files changed, 40 insertions(+), 144 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index cad1ccdf..be0e8ec9 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -32,6 +32,8 @@ class MenuBar extends React.Component { setItemRef: PropTypes.func.isRequired, tabbableIndex: PropTypes.number.isRequired, expandedIndex: PropTypes.number.isRequired, + collapseItem: PropTypes.number.isRequired, + expandItem: PropTypes.func.isRequired, focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, @@ -47,35 +49,9 @@ class MenuBar extends React.Component { labelId: undefined, }; - constructor(props) { - super(props); - - const { items } = props; - - this.state = { - expandedIndex: undefined, - }; - - this.childItemRefs = []; - - items.forEach(item => { - const { type, children } = item; - - if(type === 'separator') - return; - if(type === 'radiogroup') { - children.forEach(() => { - this.childItemRefs.push(React.createRef()); - }); - } - else - this.childItemRefs.push(React.createRef()); - }); - } - //---- Events ---- onChildKeyDown = (event) => { - const { items, orientation } = this.props; + const { items, orientation, collapseItem, expandItem, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild, focusItemLastChild } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -91,48 +67,36 @@ class MenuBar extends React.Component { if(orientation === 'horizontal') { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - this.props.focusItemLastChild(flattenedIndex); - //this.childItemRefs[flattenedIndex].current.focusLastChild(); + expandItem(flattenedIndex, () => { + focusItemLastChild(flattenedIndex); }); } } - else { - this.props.focusPrevItem(flattenedIndex); - //this.focusPrevChild(flattenedIndex); - } + else + focusPrevItem(flattenedIndex); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); if(orientation === 'horizontal') { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - this.props.focusItemFirstChild(flattenedIndex); - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } } - else { - this.props.focusNextItem(flattenedIndex); - //this.focusNextChild(flattenedIndex); - } + else + focusNextItem(flattenedIndex); } else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); - if(orientation === 'horizontal') { - this.props.focusPrevItem(flattenedIndex); - //this.focusPrevChild(flattenedIndex); - } + if(orientation === 'horizontal') + focusPrevItem(flattenedIndex); else { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - this.props.focusItemLastChild(flattenedIndex); - //this.childItemRefs[flattenedIndex].current.focusLastChild(); + expandItem(flattenedIndex, () => { + focusItemLastChild(flattenedIndex); }); } } @@ -140,16 +104,12 @@ class MenuBar extends React.Component { else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); - if(orientation === 'horizontal') { - this.props.focusNextItem(flattenedIndex); - //this.focusNextChild(flattenedIndex); - } + if(orientation === 'horizontal') + focusNextItem(flattenedIndex); else { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } } @@ -158,10 +118,8 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -181,10 +139,8 @@ class MenuBar extends React.Component { event.preventDefault(); if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -202,20 +158,17 @@ class MenuBar extends React.Component { } else if(key === 'Home') { event.preventDefault(); - //this.focusFirstChild(); - this.props.focusFirstItem(); + focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - //this.focusLastChild(); - this.props.focusLastItem(); - } - else if(key === 'Tab') { - //this.collapseChild(); - this.props.collapseItem(); + focusLastItem(); } + else if(key === 'Tab') + collapseItem(); }; - + + /* onKeyDown = (event) => { return; @@ -307,6 +260,7 @@ class MenuBar extends React.Component { else if(key === 'Tab') { } }; + */ //---- Rendering ---- render() { @@ -320,7 +274,6 @@ class MenuBar extends React.Component { aria-orientation={ orientation } aria-labelledby={ labelId } aria-label={ label } - onKeyDown={ this.onKeyDown } > { this.renderItems() }
                            @@ -330,7 +283,7 @@ class MenuBar extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, tabbableIndex, expandedIndex } = this.props; + const { items, setItemRef, collapseItem, focusItem, focusPrevItem, focusNextItem, tabbableIndex, expandedIndex } = this.props; const itemNodes = []; let position = []; let flattenedPosition = []; @@ -353,7 +306,7 @@ class MenuBar extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node }
                            @@ -374,17 +327,17 @@ class MenuBar extends React.Component { position={ position } flattenedPosition={ flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapse={ this.props.collapseItem /*this.collapseChild*/ } - focusPrevRootItem={ this.props.focusPrevItem /*this.focusPrevChild*/ } - focusNextRootItem={ this.props.focusNextItem /*this.focusNextChild*/ } - focusRootItem={ this.props.focusItem /*this.focusChild*/ } + collapse={ collapseItem } + focusPrevRootItem={ focusPrevItem } + focusNextRootItem={ focusNextItem } + focusRootItem={ focusItem } orientation={ orientation } label={ label } labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -407,7 +360,7 @@ class MenuBar extends React.Component { isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -443,7 +396,7 @@ class MenuBar extends React.Component { isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } data-value={ value } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -468,61 +421,6 @@ class MenuBar extends React.Component { /* eslint-enable react/no-array-index-key */ }; - - //---- Misc. ---- - collapseChild = (collapseAll, callback) => { - this.setState({ - expandedIndex: undefined, - }, () => { - if(typeof callback === 'function') - callback(); - }); - }; - - expandChild = (flattenedIndex, callback) => { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { - if(typeof callback === 'function') - callback(); - }); - }; - - focusPrevChild = (flattenedIndex, autoExpand = false) => { - this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1, autoExpand); - }; - - focusNextChild = (flattenedIndex, autoExpand = false) => { - this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1, autoExpand); - }; - - focusFirstChild = () => { - this.focusChild(0); - }; - - focusLastChild = () => { - this.focusLastChild(this.childItemRefs.length - 1); - }; - - focusChild = (flattenedIndex, autoExpand) => { - const targetRef = this.childItemRefs[flattenedIndex]; - - this.setState(state => { - const { expandedIndex } = state; - const wasExpanded = expandedIndex !== undefined && expandedIndex !== null; - - //FIXME forwardRef breaks instanceof ParentMenuItem - //const _autoExpand = targetRef.current instanceof ParentMenuItem && (wasExpanded || autoExpand); - const _autoExpand = wasExpanded || autoExpand; - - return { - tabbableIndex: flattenedIndex, - expandedIndex: _autoExpand ? flattenedIndex : undefined, - }; - }, () => { - targetRef.current.focus(); - }); - }; } export default createMenuFocusManager(MenuBar); diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 18feb73c..f9bb56f6 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -67,8 +67,6 @@ class MenuButton extends React.Component { constructor(props) { super(props); - const { items } = props; - this.state = { isExpanded: false, }; @@ -260,7 +258,7 @@ class MenuButton extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, expandedIndex, setItemRef, collapseItem, focus } = this.props; + const { items, setItemRef, expandedIndex, collapseItem, focus } = this.props; const itemNodes = []; let position = [0]; let flattenedPosition = [0]; From f11e9ab8acc587756af1d3a1807afd4a3b7f40c8 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:18:35 -0500 Subject: [PATCH 253/286] ignore items in Menu (for now?) --- src/Menu/Menu.jsx | 4 ++-- src/Menu/ParentMenuItem.jsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index fdbb8263..92bf54da 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -28,6 +28,7 @@ class Menu extends React.Component { }; //---- Events ---- + /* onKeyDown = (event) => { return; @@ -134,7 +135,7 @@ class Menu extends React.Component { event.stopPropagation(); } }; - +*/ //---- Rendering ---- render() { @@ -147,7 +148,6 @@ class Menu extends React.Component { aria-label={ label } aria-labelledby={ labelId } id={ id } - onKeyDown={ this.onKeyDown } > { children }
                          diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 639eb957..2b0726d5 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -399,7 +399,6 @@ class _ParentMenuItem extends React.Component { orientation={ orientation } label={ label } labelId={ labelId } - items={ this.props.items } > { this.renderItems() } From 11aae9c286acf20041993956d55bc6549edac688 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:39:04 -0500 Subject: [PATCH 254/286] get rid of old focus/etc. cold in parentmenuitem --- src/Menu/ParentMenuItem.jsx | 188 ++++++++---------------------------- 1 file changed, 41 insertions(+), 147 deletions(-) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 2b0726d5..7c02ab77 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -35,12 +35,15 @@ class _ParentMenuItem extends React.Component { //From MenuFocusManager setManagerRef: PropTypes.func.isRequired, setItemRef: PropTypes.func.isRequired, - focus: PropTypes.func.isRequired, + expandedIndex: PropTypes.number.isRequired, + collapseItem: PropTypes.func.isRequired, + expandItem: PropTypes.func.isRequired, focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, focusFirstItem: PropTypes.func.isRequired, focusLastItem: PropTypes.func.isRequired, + focusItemFirstChild: PropTypes.func.isRequired, }; static defaultProps = { @@ -55,37 +58,9 @@ class _ParentMenuItem extends React.Component { isTabbable: false, }; - constructor(props) { - super(props); - - const { items } = props; - - this.state = { - expandedIndex: undefined, - isExpanded: false, - }; - - this.itemRef = React.createRef(); - this.childItemRefs = []; - - items.forEach(item => { - const { type, children } = item; - - if(type === 'separator') - return; - if(type === 'radiogroup') { - children.forEach(() => { - this.childItemRefs.push(React.createRef()); - }); - } - else - this.childItemRefs.push(React.createRef()); - }); - } - //---- Events ---- onChildKeyDown = (event) => { - const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation } = this.props; + const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation, expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -101,35 +76,26 @@ class _ParentMenuItem extends React.Component { if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - if(orientation === 'vertical') { - //this.focusPrevChild(flattenedIndex); - this.props.focusPrevItem(flattenedIndex); - } + if(orientation === 'vertical') + focusPrevItem(flattenedIndex); else { collapse(false, () => { - if(level === 1 && focusPrevRootItem) { + if(level === 1 && focusPrevRootItem) focusPrevRootItem(flattenedRootIndex, true); - } - else { - //this.focus(); - this.props.focus(); - } + else + focus(); }); } } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - if(orientation === 'vertical') { - //this.focusNextChild(flattenedIndex); - this.props.focusNextItem(flattenedIndex); - } + if(orientation === 'vertical') + focusNextItem(flattenedIndex); else { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(focusNextRootItem) { @@ -146,26 +112,20 @@ class _ParentMenuItem extends React.Component { collapse(false, () => { if(level === 1 && focusPrevRootItem) focusPrevRootItem(flattenedRootIndex, true); - else { - //this.focus(); - this.props.focus(); - } + else + focus(); }); } - else { - //this.focusPrevChild(flattenedIndex); - this.props.focusPrevItem(flattenedIndex); - } + else + focusPrevItem(flattenedIndex); } else if(key === 'ArrowRight' || key === 'Right') { event.preventDefault(); if(orientation === 'vertical') { if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(focusNextRootItem) { @@ -174,19 +134,15 @@ class _ParentMenuItem extends React.Component { }); } } - else { - //this.focusNextChild(flattenedIndex); - this.props.focusNextItem(flattenedIndex); - } + else + focusNextItem(flattenedIndex); } else if(key === 'Enter') { event.preventDefault(); if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -218,10 +174,8 @@ class _ParentMenuItem extends React.Component { event.preventDefault(); if(type === 'menu') { - //this.expandChild(flattenedIndex, () => { - this.props.expandItem(flattenedIndex, () => { - //this.childItemRefs[flattenedIndex].current.focusFirstChild(); - this.props.focusItemFirstChild(flattenedIndex); + expandItem(flattenedIndex, () => { + focusItemFirstChild(flattenedIndex); }); } else if(type === 'checkbox') { @@ -243,26 +197,24 @@ class _ParentMenuItem extends React.Component { } else if(key === 'Home') { event.preventDefault(); - //this.focusFirstChild(); - this.props.focusFirstItem(); + focusFirstItem(); } else if(key === 'End') { event.preventDefault(); - //this.focusLastChild(); - this.props.focusLastItem(); + focusLastItem(); } else if(key === 'Escape' || key === 'Esc') { event.preventDefault(); collapse(false, () => { - //this.focus(); - this.props.focus(); + focus(); }); } else if(key === 'Tab') collapse(true); }; - + + /* onKeyDown = (event) => { const { items, orientation } = this.props; const { key, target } = event; @@ -367,6 +319,7 @@ class _ParentMenuItem extends React.Component { event.stopPropagation(); } }; + */ //---- Rendering ---- render() { @@ -374,6 +327,7 @@ class _ParentMenuItem extends React.Component { children, position, flattenedPosition, onKeyDown, orientation, label, labelId, isExpanded, isDisabled, isTabbable, + setManagerRef, } = this.props; //const { isExpanded } = this.state; @@ -387,11 +341,11 @@ class _ParentMenuItem extends React.Component { aria-haspopup="menu" data-position={ position } data-flattenedposition={ flattenedPosition } - onKeyDown={ /*this.onKeyDown*/ /* undefined */ onKeyDown } + onKeyDown={ onKeyDown } aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } - ref={ this.props.setManagerRef /*this.itemRef*/ } + ref={ setManagerRef } > { children } @@ -409,8 +363,7 @@ class _ParentMenuItem extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, expandedIndex } = this.props; - //const { expandedIndex } = this.state; + const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, setItemRef, expandedIndex, collapseItem } = this.props; const level = position.length; const itemNodes = []; let _position = []; @@ -433,7 +386,7 @@ class _ParentMenuItem extends React.Component { flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -454,7 +407,7 @@ class _ParentMenuItem extends React.Component { position={ _position } flattenedPosition={ _flattenedPosition } onKeyDown={ this.onChildKeyDown } - collapse={ this.props.collapseItem /*this.collapseChild*/ } + collapse={ collapseItem } focusPrevRootItem={ focusPrevRootItem } focusNextRootItem={ focusNextRootItem } focusRootItem={ focusRootItem } @@ -463,7 +416,7 @@ class _ParentMenuItem extends React.Component { labelId={ labelId } isExpanded={ flattenedIndex === expandedIndex } isDisabled={ isDisabled } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -485,7 +438,7 @@ class _ParentMenuItem extends React.Component { onKeyDown={ this.onChildKeyDown } isDisabled={ isDisabled } isChecked={ isChecked } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -520,7 +473,7 @@ class _ParentMenuItem extends React.Component { isDisabled={ isDisabled } isChecked={ isChecked } data-value={ value } - ref={ this.props.setItemRef /*this.childItemRefs[flattenedIndex]*/ } + ref={ setItemRef } > { node } @@ -545,65 +498,6 @@ class _ParentMenuItem extends React.Component { /* eslint-enable react/no-array-index-key */ }; - - //---- Misc. --- - expand = () => { - this.setState({ - isExpanded: true, - }); - }; - - collapse = () => { - this.setState({ - isExpanded: false, - }); - }; - - collapseChild = (collapseAll, callback) => { - const { collapse } = this.props; - - this.setState({ - expandedIndex: undefined, - }, () => { - if(collapseAll) - collapse(true, callback); - else if(typeof callback === 'function') - callback(); - }); - }; - - expandChild = (flattenedIndex, callback) => { - this.setState({ - expandedIndex: flattenedIndex, - }, () => { - if(typeof callback === 'function') - callback(); - }); - }; - - focusPrevChild = (flattenedIndex) => { - this.focusChild(flattenedIndex === 0 ? this.childItemRefs.length - 1 : flattenedIndex - 1); - }; - - focusNextChild = (flattenedIndex) => { - this.focusChild(flattenedIndex === this.childItemRefs.length - 1 ? 0 : flattenedIndex + 1); - }; - - focusFirstChild = () => { - this.focusChild(0); - }; - - focusLastChild = () => { - this.focusChild(this.childItemRefs.length - 1); - }; - - focusChild = (flattenedIndex) => { - this.childItemRefs[flattenedIndex].current.focus(); - }; - - focus = () => { - this.itemRef.current.focus(); - }; } const ParentMenuItem = createMenuFocusManager(_ParentMenuItem); From d0facc936c0f36c65e01b51c9d6e07b4a44c0eb7 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:45:16 -0500 Subject: [PATCH 255/286] resolve linter complaints on MenuFocusManager --- src/Menu/Menu.jsx | 1 - src/Menu/MenuBar.jsx | 8 +++---- src/Menu/MenuButton.jsx | 4 ++-- src/Menu/ParentMenuItem.jsx | 2 +- src/Menu/createMenuFocusManager.jsx | 36 ++++++++++++++--------------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 92bf54da..93a6355d 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -13,7 +13,6 @@ import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; class Menu extends React.Component { static propTypes = { children: PropTypes.node.isRequired, - items: MENUITEMS_PROPTYPE.isRequired, orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index be0e8ec9..8745e8cf 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -32,7 +32,7 @@ class MenuBar extends React.Component { setItemRef: PropTypes.func.isRequired, tabbableIndex: PropTypes.number.isRequired, expandedIndex: PropTypes.number.isRequired, - collapseItem: PropTypes.number.isRequired, + collapseItem: PropTypes.func.isRequired, expandItem: PropTypes.func.isRequired, focusItem: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, @@ -88,7 +88,7 @@ class MenuBar extends React.Component { else focusNextItem(flattenedIndex); } - else if(key === 'ArrowLeft' || key === 'Left') { + else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); if(orientation === 'horizontal') @@ -167,7 +167,7 @@ class MenuBar extends React.Component { else if(key === 'Tab') collapseItem(); }; - + /* onKeyDown = (event) => { return; @@ -199,7 +199,7 @@ class MenuBar extends React.Component { else { } } - else if(key === 'ArrowLeft' || key === 'Left') { + else if(key === 'ArrowLeft' || key === 'Left') { event.preventDefault(); if(orientation === 'horizontal') { diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index f9bb56f6..6eed69b6 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -260,8 +260,8 @@ class MenuButton extends React.Component { const { items, setItemRef, expandedIndex, collapseItem, focus } = this.props; const itemNodes = []; - let position = [0]; - let flattenedPosition = [0]; + let position = [ 0 ]; + let flattenedPosition = [ 0 ]; let flattenedIndex = 0; items.forEach((item, i) => { diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 7c02ab77..79b7c3bd 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -213,7 +213,7 @@ class _ParentMenuItem extends React.Component { else if(key === 'Tab') collapse(true); }; - + /* onKeyDown = (event) => { const { items, orientation } = this.props; diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx index f83a5161..4ec57993 100644 --- a/src/Menu/createMenuFocusManager.jsx +++ b/src/Menu/createMenuFocusManager.jsx @@ -1,31 +1,35 @@ +/* eslint-disable react/jsx-props-no-spreading */ + import React from 'react'; import PropTypes from 'prop-types'; -import ParentMenuItem from 'src/Menu/ParentMenuItem'; - export default function createMenuFocusManager(Component) { - class _MenuFocusManager extends React.Component { + class MenuFocusManager extends React.Component { static propTypes = { forwardedRef: PropTypes.oneOfType([ PropTypes.func, PropTypes.object, ]), + collapse: PropTypes.func, + }; + + static defaultProps = { + forwardedRef: null, + collapse: () => {}, }; constructor(props) { super(props); - - //TODO: tabbableIndex is only really relevant for the root level - //for menubars. should we always pass down the prop and leave it - //to anyone consuming this HOC to decide whether or not it's - //worth using? or should we force it to be undefined if we know - //the "level" is >= 1? + + //Note: tabbableIndex is only really relevant to the + //root menuitems in a menubar. Submenus should ignore + //it. this.state = { tabbableIndex: 0, expandedIndex: -1, }; - this.managerRef; + this.managerRef = undefined; this.itemRefs = []; } @@ -34,8 +38,6 @@ export default function createMenuFocusManager(Component) { const { forwardedRef, ...rest } = this.props; const { tabbableIndex, expandedIndex } = this.state; -// console.log(this.managerRef, this.itemRefs, this.props, this.state); - return ( { const { collapse } = this.props; - console.log(collapseAll, callback); - this.setState({ expandedIndex: -1, }, () => { @@ -83,8 +83,6 @@ export default function createMenuFocusManager(Component) { }; expandItem = (index, callback) => { - console.log(index, callback); - this.setState({ expandedIndex: index, }, () => { @@ -105,7 +103,7 @@ export default function createMenuFocusManager(Component) { const { expandedIndex } = state; const wasExpanded = expandedIndex !== -1; const _autoExpand = isMenu && (autoExpand || wasExpanded); - + return { tabbableIndex: index, expandedIndex: _autoExpand ? index : -1, @@ -143,7 +141,7 @@ export default function createMenuFocusManager(Component) { }; } - return React.forwardRef(function MenuFocusManager(props, ref) { - return <_MenuFocusManager {...props} forwardedRef={ ref } />; + return React.forwardRef((props, ref) => { + return ; }); } From f9f07807426a0ca9f8306bbac6eee704eda2a1dc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:47:35 -0500 Subject: [PATCH 256/286] resolve more linter complaints --- src/Menu/Menu.jsx | 3 --- src/Menu/ParentMenuItem.jsx | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 93a6355d..39fd1e3d 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -1,9 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -//Misc. -import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; - /* * Note: * diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 79b7c3bd..a77f69f4 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -38,7 +38,7 @@ class _ParentMenuItem extends React.Component { expandedIndex: PropTypes.number.isRequired, collapseItem: PropTypes.func.isRequired, expandItem: PropTypes.func.isRequired, - focusItem: PropTypes.func.isRequired, + focus: PropTypes.func.isRequired, focusPrevItem: PropTypes.func.isRequired, focusNextItem: PropTypes.func.isRequired, focusFirstItem: PropTypes.func.isRequired, From 7cfab0d7af0049a2069f15c4260432d6a2c58478 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:48:48 -0500 Subject: [PATCH 257/286] make Menu a function component again --- src/Menu/Menu.jsx | 171 ++++++++-------------------------------------- 1 file changed, 29 insertions(+), 142 deletions(-) diff --git a/src/Menu/Menu.jsx b/src/Menu/Menu.jsx index 39fd1e3d..83322032 100644 --- a/src/Menu/Menu.jsx +++ b/src/Menu/Menu.jsx @@ -7,148 +7,35 @@ import PropTypes from 'prop-types'; * - The menu should either have a labelId prop that points to the menuitem or * button that controls its display XOR a label prop. */ -class Menu extends React.Component { - static propTypes = { - children: PropTypes.node.isRequired, - orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), - label: PropTypes.string, - labelId: PropTypes.string, - id: PropTypes.string, - }; - - static defaultProps = { - orientation: 'vertical', - label: undefined, - labelId: undefined, - id: undefined, - }; - - //---- Events ---- - /* - onKeyDown = (event) => { - return; - - const { items, orientation } = this.props; - const { key, target } = event; - const position = target.dataset.position.split(','); - const flattenedPosition = target.dataset.flattenedposition.split(','); - const index = Number.parseInt(position[position.length - 1], 10); - const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); - const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); - const level = position.length - 1; - const item = items[index]; - const { type, onActivate } = item; - - console.log(orientation, position, flattenedPosition, index, flattenedIndex, level, item); - - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === 'Home') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'End') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Tab') { - event.stopPropagation(); - } - }; -*/ - - //---- Rendering ---- - render() { - const { children, orientation, label, labelId, id } = this.props; - - return ( - - ); - } +function Menu(props) { + const { children, orientation, label, labelId, id } = props; + + return ( + + ); } +Menu.propTypes = { + children: PropTypes.node.isRequired, + orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), + label: PropTypes.string, + labelId: PropTypes.string, + id: PropTypes.string, +}; + +Menu.defaultProps = { + orientation: 'vertical', + label: undefined, + labelId: undefined, + id: undefined, +}; + export default Menu; From 9295c3df541a8ac4c317451e7bbc73a39a42c175 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:51:22 -0500 Subject: [PATCH 258/286] get rid of a bunch of commented code --- src/Menu/MenuBar.jsx | 94 ------------------------------- src/Menu/ParentMenuItem.jsx | 108 ------------------------------------ 2 files changed, 202 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 8745e8cf..460478c1 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -168,100 +168,6 @@ class MenuBar extends React.Component { collapseItem(); }; - /* - onKeyDown = (event) => { - return; - - const { items, orientation } = this.props; - const { key, target } = event; - const position = target.dataset.position.split(','); - const flattenedPosition = target.dataset.flattenedposition.split(','); - const index = Number.parseInt(position[position.length - 1], 10); - const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); - const item = items[index]; - const { type, onActivate } = item; - - console.log(orientation, position, flattenedPosition, index, flattenedIndex, item); - - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - - if(orientation === 'horizontal') { - } - else { - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - - if(orientation === 'horizontal') { - } - else { - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - - if(orientation === 'horizontal') { - } - else { - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - - if(orientation === 'horizontal') { - } - else { - } - } - else if(key === 'Enter') { - event.preventDefault(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === 'Home') { - event.preventDefault(); - } - else if(key === 'End') { - event.preventDefault(); - } - else if(key === 'Tab') { - } - }; - */ - //---- Rendering ---- render() { const { orientation, label, labelId } = this.props; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index a77f69f4..8fc09414 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -214,113 +214,6 @@ class _ParentMenuItem extends React.Component { collapse(true); }; - /* - onKeyDown = (event) => { - const { items, orientation } = this.props; - const { key, target } = event; - const position = target.dataset.position.split(','); - const flattenedPosition = target.dataset.flattenedposition.split(','); - const index = Number.parseInt(position[position.length - 1], 10); - const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); - const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); - const level = position.length - 1; - const item = items[index]; - const { type, onActivate } = item; - - console.log(orientation, position, flattenedPosition, index, flattenedIndex, level, item); - - if(key === 'ArrowUp' || key === 'Up') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowDown' || key === 'Down') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowLeft' || key === 'Left') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'ArrowRight' || key === 'Right') { - event.preventDefault(); - event.stopPropagation(); - - if(orientation === 'vertical') { - } - else { - } - } - else if(key === 'Enter') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === ' ' || key === 'Spacebar') { - event.preventDefault(); - event.stopPropagation(); - - if(type === 'menu') { - } - else if(type === 'checkbox') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'radiogroup') { - if(typeof onActivate === 'function') - onActivate(event); - } - else if(type === 'item') { - if(typeof onActivate === 'function') - onActivate(event); - } - } - else if(key === 'Home') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'End') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Escape' || key === 'Esc') { - event.preventDefault(); - event.stopPropagation(); - } - else if(key === 'Tab') { - event.stopPropagation(); - } - }; - */ - //---- Rendering ---- render() { const { @@ -329,7 +222,6 @@ class _ParentMenuItem extends React.Component { isExpanded, isDisabled, isTabbable, setManagerRef, } = this.props; - //const { isExpanded } = this.state; //console.log(this.props, this.state, this.itemRef, this.childItemRefs); From 8c53ccf4a815cdca0ecce2d48a5411e41d100f56 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:55:22 -0500 Subject: [PATCH 259/286] spread destructuring across multiple lines --- src/Menu/MenuBar.jsx | 11 +++++++++-- src/Menu/MenuButton.jsx | 6 +++++- src/Menu/ParentMenuItem.jsx | 10 ++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 460478c1..accb0bef 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -51,7 +51,11 @@ class MenuBar extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, orientation, collapseItem, expandItem, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild, focusItemLastChild } = this.props; + const { + items, orientation, + collapseItem, expandItem, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, + focusItemFirstChild, focusItemLastChild, + } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -189,7 +193,10 @@ class MenuBar extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, setItemRef, collapseItem, focusItem, focusPrevItem, focusNextItem, tabbableIndex, expandedIndex } = this.props; + const { + items, setItemRef, tabbableIndex, expandedIndex, + collapseItem, focusItem, focusPrevItem, focusNextItem, + } = this.props; const itemNodes = []; let position = []; let flattenedPosition = []; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 6eed69b6..e1878055 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -101,7 +101,11 @@ class MenuButton extends React.Component { }; onChildKeyDown = (event) => { - const { items, orientation, expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild } = this.props; + const { + items, orientation, + expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, + focusItemFirstChild, + } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8fc09414..8c6cb9f7 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -60,7 +60,10 @@ class _ParentMenuItem extends React.Component { //---- Events ---- onChildKeyDown = (event) => { - const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation, expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild } = this.props; + const { + items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation, + expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild, + } = this.props; const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); @@ -255,7 +258,10 @@ class _ParentMenuItem extends React.Component { renderItems = () => { /* eslint-disable react/no-array-index-key */ - const { items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, setItemRef, expandedIndex, collapseItem } = this.props; + const { + items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, + setItemRef, expandedIndex, collapseItem, + } = this.props; const level = position.length; const itemNodes = []; let _position = []; From a7493b23dfff9726b735035560ffeae11c434e57 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 19:57:43 -0500 Subject: [PATCH 260/286] rename MenuFocusManager to MenuManager --- src/Menu/MenuBar.jsx | 6 +- src/Menu/MenuButton.jsx | 6 +- src/Menu/ParentMenuItem.jsx | 6 +- src/Menu/createMenuFocusManager.jsx | 147 ---------------------------- 4 files changed, 9 insertions(+), 156 deletions(-) delete mode 100644 src/Menu/createMenuFocusManager.jsx diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index accb0bef..3bac7496 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -10,7 +10,7 @@ import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; //HOCs -import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; +import createMenuManager from 'src/Menu/createMenuManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -28,7 +28,7 @@ class MenuBar extends React.Component { orientation: PropTypes.oneOf([ 'vertical', 'horizontal' ]), label: PropTypes.string, labelId: PropTypes.string, - //From MenuFocusManager + //From MenuManager setItemRef: PropTypes.func.isRequired, tabbableIndex: PropTypes.number.isRequired, expandedIndex: PropTypes.number.isRequired, @@ -336,4 +336,4 @@ class MenuBar extends React.Component { }; } -export default createMenuFocusManager(MenuBar); +export default createMenuManager(MenuBar); diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index e1878055..5db741ea 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -11,7 +11,7 @@ import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; //HOCs -import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; +import createMenuManager from 'src/Menu/createMenuManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -43,7 +43,7 @@ class MenuButton extends React.Component { menuLabel: PropTypes.string, menuId: PropTypes.string, id: PropTypes.string, - //From MenuFocusManager + //From MenuManager setManagerRef: PropTypes.func.isRequired, setItemRef: PropTypes.func.isRequired, expandedIndex: PropTypes.number.isRequired, @@ -419,4 +419,4 @@ class MenuButton extends React.Component { }; } -export default createMenuFocusManager(MenuButton); +export default createMenuManager(MenuButton); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8c6cb9f7..8445e70f 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -10,7 +10,7 @@ import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; //HOCs -import createMenuFocusManager from 'src/Menu/createMenuFocusManager'; +import createMenuManager from 'src/Menu/createMenuManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; @@ -32,7 +32,7 @@ class _ParentMenuItem extends React.Component { isExpanded: PropTypes.bool, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, - //From MenuFocusManager + //From MenuManager setManagerRef: PropTypes.func.isRequired, setItemRef: PropTypes.func.isRequired, expandedIndex: PropTypes.number.isRequired, @@ -398,6 +398,6 @@ class _ParentMenuItem extends React.Component { }; } -const ParentMenuItem = createMenuFocusManager(_ParentMenuItem); +const ParentMenuItem = createMenuManager(_ParentMenuItem); export default ParentMenuItem; diff --git a/src/Menu/createMenuFocusManager.jsx b/src/Menu/createMenuFocusManager.jsx deleted file mode 100644 index 4ec57993..00000000 --- a/src/Menu/createMenuFocusManager.jsx +++ /dev/null @@ -1,147 +0,0 @@ -/* eslint-disable react/jsx-props-no-spreading */ - -import React from 'react'; -import PropTypes from 'prop-types'; - -export default function createMenuFocusManager(Component) { - class MenuFocusManager extends React.Component { - static propTypes = { - forwardedRef: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.object, - ]), - collapse: PropTypes.func, - }; - - static defaultProps = { - forwardedRef: null, - collapse: () => {}, - }; - - constructor(props) { - super(props); - - //Note: tabbableIndex is only really relevant to the - //root menuitems in a menubar. Submenus should ignore - //it. - this.state = { - tabbableIndex: 0, - expandedIndex: -1, - }; - - this.managerRef = undefined; - this.itemRefs = []; - } - - //---- Rendering ---- - render() { - const { forwardedRef, ...rest } = this.props; - const { tabbableIndex, expandedIndex } = this.state; - - return ( - - ); - } - - //---- Misc. ---- - setManagerRef = (ref) => { - this.managerRef = ref; - }; - - setItemRef = (ref) => { - this.itemRefs.push(ref); - }; - - collapseItem = (collapseAll, callback) => { - const { collapse } = this.props; - - this.setState({ - expandedIndex: -1, - }, () => { - if(collapseAll && typeof collapse === 'function') - collapse(true, callback); //FIXME currently broken for MenuButton - else if(typeof callback === 'function') - callback(); - }); - }; - - expandItem = (index, callback) => { - this.setState({ - expandedIndex: index, - }, () => { - if(typeof callback === 'function') - callback(); - }); - }; - - focus = () => { - this.managerRef.focus(); - }; - - focusItem = (index, autoExpand = false) => { - const itemRef = this.itemRefs[index]; - const isMenu = !(itemRef instanceof HTMLElement); //this feels rather fragile - - this.setState(state => { - const { expandedIndex } = state; - const wasExpanded = expandedIndex !== -1; - const _autoExpand = isMenu && (autoExpand || wasExpanded); - - return { - tabbableIndex: index, - expandedIndex: _autoExpand ? index : -1, - }; - }, () => { - if(isMenu) - itemRef.props.focus(); - else - itemRef.focus(); - }); - }; - - focusPrevItem = (index, autoExpand) => { - this.focusItem(index === 0 ? this.itemRefs.length - 1 : index - 1, autoExpand); - }; - - focusNextItem = (index, autoExpand) => { - this.focusItem(index === this.itemRefs.length - 1 ? 0 : index + 1, autoExpand); - }; - - focusFirstItem = () => { - this.focusItem(0); - }; - - focusLastItem = () => { - this.focusItem(this.itemRefs.length - 1); - }; - - focusItemFirstChild = (index) => { - this.itemRefs[index].props.focusFirstItem(); - }; - - focusItemLastChild = (index) => { - this.itemRefs[index].props.focusLastItem(); - }; - } - - return React.forwardRef((props, ref) => { - return ; - }); -} From 8d8a15fea0e6206cf3220f4985f480051b5d21fa Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 20:12:04 -0500 Subject: [PATCH 261/286] fix issue with collapseAll and callbacks --- src/Menu/ParentMenuItem.jsx | 2 + src/Menu/createMenuManager.jsx | 153 +++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/Menu/createMenuManager.jsx diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8445e70f..7f463722 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -132,7 +132,9 @@ class _ParentMenuItem extends React.Component { }); } else if(focusNextRootItem) { + console.log('here 1'); collapse(true, () => { + console.log('here 2'); focusNextRootItem(flattenedRootIndex, true); }); } diff --git a/src/Menu/createMenuManager.jsx b/src/Menu/createMenuManager.jsx new file mode 100644 index 00000000..d6239ffe --- /dev/null +++ b/src/Menu/createMenuManager.jsx @@ -0,0 +1,153 @@ +/* eslint-disable react/jsx-props-no-spreading */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +export default function createMenuManager(Component) { + class MenuManager extends React.Component { + static propTypes = { + forwardedRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]), + collapse: PropTypes.func, + }; + + static defaultProps = { + forwardedRef: null, //seems to be React's default + collapse: undefined, + }; + + constructor(props) { + super(props); + + //Note: tabbableIndex is only really relevant to the + //root menuitems in a menubar. Submenus should ignore + //it. + this.state = { + tabbableIndex: 0, + expandedIndex: -1, + }; + + this.managerRef = undefined; + this.itemRefs = []; + } + + //---- Rendering ---- + render() { + const { forwardedRef, ...rest } = this.props; + const { tabbableIndex, expandedIndex } = this.state; + + return ( + + ); + } + + //---- Misc. ---- + setManagerRef = (ref) => { + this.managerRef = ref; + }; + + setItemRef = (ref) => { + this.itemRefs.push(ref); + }; + + collapseItem = (collapseAll, callback) => { + const { collapse } = this.props; + + this.setState({ + expandedIndex: -1, + }, () => { + if(collapseAll) { + if(typeof collapse === 'function') + collapse(true, callback); //FIXME currently broken for MenuButton + else if(typeof callback === 'function') + callback(); + } + else if(typeof callback === 'function') + callback(); + }); + }; + + expandItem = (index, callback) => { + this.setState({ + expandedIndex: index, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + + focus = () => { + this.managerRef.focus(); + }; + + focusItem = (index, autoExpand = false) => { + const itemRef = this.itemRefs[index]; + const isMenu = !(itemRef instanceof HTMLElement); //this feels rather fragile + + console.log(index, itemRef); + + this.setState(state => { + const { expandedIndex } = state; + const wasExpanded = expandedIndex !== -1; + const _autoExpand = isMenu && (autoExpand || wasExpanded); + + return { + tabbableIndex: index, + expandedIndex: _autoExpand ? index : -1, + }; + }, () => { + if(isMenu) + itemRef.props.focus(); + else + itemRef.focus(); + }); + }; + + focusPrevItem = (index, autoExpand) => { + this.focusItem(index === 0 ? this.itemRefs.length - 1 : index - 1, autoExpand); + }; + + focusNextItem = (index, autoExpand) => { + this.focusItem(index === this.itemRefs.length - 1 ? 0 : index + 1, autoExpand); + }; + + focusFirstItem = () => { + this.focusItem(0); + }; + + focusLastItem = () => { + this.focusItem(this.itemRefs.length - 1); + }; + + focusItemFirstChild = (index) => { + this.itemRefs[index].props.focusFirstItem(); + }; + + focusItemLastChild = (index) => { + this.itemRefs[index].props.focusLastItem(); + }; + } + + return React.forwardRef((props, ref) => { + return ; + }); +} From cd287f66e389cb0ea7cd0698ce729b5ee76b27aa Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 1 Feb 2022 20:13:48 -0500 Subject: [PATCH 262/286] lint --- src/Menu/createMenuManager.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Menu/createMenuManager.jsx b/src/Menu/createMenuManager.jsx index d6239ffe..155fe1fa 100644 --- a/src/Menu/createMenuManager.jsx +++ b/src/Menu/createMenuManager.jsx @@ -103,8 +103,6 @@ export default function createMenuManager(Component) { const itemRef = this.itemRefs[index]; const isMenu = !(itemRef instanceof HTMLElement); //this feels rather fragile - console.log(index, itemRef); - this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== -1; From 077e69bbbf27a9e4742ec9c0e74a61518685a20c Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 2 Feb 2022 13:30:41 -0500 Subject: [PATCH 263/286] add styling and controls for isDisabled --- src/MenuBarOne.jsx | 36 ++++++++++++++++++++++++++++++++++++ src/MenuBarTwo.jsx | 35 +++++++++++++++++++++++++++++++++++ src/MenuButtonOne.jsx | 36 ++++++++++++++++++++++++++++++++++++ src/MenuButtonTwo.jsx | 41 ++++++++++++++++++++++++++++++++++++++--- src/styles.scss | 5 +++++ 5 files changed, 150 insertions(+), 3 deletions(-) diff --git a/src/MenuBarOne.jsx b/src/MenuBarOne.jsx index fd724866..34073717 100644 --- a/src/MenuBarOne.jsx +++ b/src/MenuBarOne.jsx @@ -19,6 +19,7 @@ class MenuBarOne extends React.Component { checkboxChild1: false, checkboxChild2: false, checkboxChild3: false, + disableAll: false, }; } @@ -140,6 +141,14 @@ class MenuBarOne extends React.Component { }); }; + onToggleDisableAll = () => { + this.setState(state => { + return { + disableAll: !state.disableAll, + }; + }); + }; + //---- Rendering ---- render() { return ( @@ -153,6 +162,7 @@ class MenuBarOne extends React.Component { radioGroupOne, checkboxOneState, checkboxTwoState, radioGroupTwo, radioGroupThree, radioGroupFour, checkboxThreeState, checkboxParent, checkboxChild1, checkboxChild2, checkboxChild3, + disableAll, } = this.state; return [ @@ -168,16 +178,19 @@ class MenuBarOne extends React.Component { node: 'Radio Option 1', value: 'option1', isChecked: radioGroupOne === 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', value: 'option2', isChecked: radioGroupOne === 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', value: 'option3', isChecked: radioGroupOne === 'option3', + isDisabled: disableAll, }, ], }, @@ -189,12 +202,14 @@ class MenuBarOne extends React.Component { node: 'Checkbox 1', isChecked: checkboxOneState, onActivate: this.onToggleCheckboxOne, + isDisabled: disableAll, }, { type: 'checkbox', node: 'Checkbox 2', isChecked: checkboxTwoState, onActivate: this.onToggleCheckboxTwo, + isDisabled: disableAll, }, { type: 'separator', @@ -207,16 +222,19 @@ class MenuBarOne extends React.Component { node: 'Radio Option 1', isChecked: radioGroupTwo === 'option1', value: 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', isChecked: radioGroupTwo === 'option2', value: 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', isChecked: radioGroupTwo === 'option3', value: 'option3', + isDisabled: disableAll, }, ], }, @@ -226,6 +244,7 @@ class MenuBarOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateItem, + isDisabled: disableAll, }, { type: 'menu', @@ -235,11 +254,13 @@ class MenuBarOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -249,11 +270,13 @@ class MenuBarOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -263,11 +286,13 @@ class MenuBarOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -279,11 +304,13 @@ class MenuBarOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -297,14 +324,17 @@ class MenuBarOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, + ], }, { @@ -364,6 +394,12 @@ class MenuBarOne extends React.Component { onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, }, + { + type: 'checkbox', + node: 'Disable All', + onActivate: this.onToggleDisableAll, + isChecked: disableAll, + }, { type: 'menu', node: 'Parent Menuitem 3', diff --git a/src/MenuBarTwo.jsx b/src/MenuBarTwo.jsx index e0557b9f..162405c4 100644 --- a/src/MenuBarTwo.jsx +++ b/src/MenuBarTwo.jsx @@ -19,6 +19,7 @@ class MenuBarTwo extends React.Component { checkboxChild1: false, checkboxChild2: false, checkboxChild3: false, + disableAll: false, }; } @@ -140,6 +141,14 @@ class MenuBarTwo extends React.Component { }); }; + onToggleDisableAll = () => { + this.setState(state => { + return { + disableAll: !state.disableAll, + }; + }); + }; + //---- Rendering ---- render() { return ( @@ -157,6 +166,7 @@ class MenuBarTwo extends React.Component { radioGroupOne, checkboxOneState, checkboxTwoState, radioGroupTwo, radioGroupThree, radioGroupFour, checkboxThreeState, checkboxParent, checkboxChild1, checkboxChild2, checkboxChild3, + disableAll, } = this.state; return [ @@ -173,16 +183,19 @@ class MenuBarTwo extends React.Component { node: 'Radio Option 1', value: 'option1', isChecked: radioGroupOne === 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', value: 'option2', isChecked: radioGroupOne === 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', value: 'option3', isChecked: radioGroupOne === 'option3', + isDisabled: disableAll, }, ], }, @@ -194,12 +207,14 @@ class MenuBarTwo extends React.Component { node: 'Checkbox 1', isChecked: checkboxOneState, onActivate: this.onToggleCheckboxOne, + isDisabled: disableAll, }, { type: 'checkbox', node: 'Checkbox 2', isChecked: checkboxTwoState, onActivate: this.onToggleCheckboxTwo, + isDisabled: disableAll, }, { type: 'separator', @@ -212,16 +227,19 @@ class MenuBarTwo extends React.Component { node: 'Radio Option 1', isChecked: radioGroupTwo === 'option1', value: 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', isChecked: radioGroupTwo === 'option2', value: 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', isChecked: radioGroupTwo === 'option3', value: 'option3', + isDisabled: disableAll, }, ], }, @@ -231,6 +249,7 @@ class MenuBarTwo extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateItem, + isDisabled: disableAll, }, { type: 'menu', @@ -240,11 +259,13 @@ class MenuBarTwo extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -255,11 +276,13 @@ class MenuBarTwo extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -269,11 +292,13 @@ class MenuBarTwo extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -286,11 +311,13 @@ class MenuBarTwo extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -304,11 +331,13 @@ class MenuBarTwo extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -372,6 +401,12 @@ class MenuBarTwo extends React.Component { onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, }, + { + type: 'checkbox', + node: 'Disable All', + onActivate: this.onToggleDisableAll, + isChecked: disableAll, + }, { type: 'menu', node: 'Parent Menuitem 3', diff --git a/src/MenuButtonOne.jsx b/src/MenuButtonOne.jsx index d82497ef..21709cca 100644 --- a/src/MenuButtonOne.jsx +++ b/src/MenuButtonOne.jsx @@ -19,6 +19,7 @@ class MenuButtonOne extends React.Component { checkboxChild1: false, checkboxChild2: false, checkboxChild3: false, + disableAll: false, }; } @@ -140,6 +141,14 @@ class MenuButtonOne extends React.Component { }); }; + onToggleDisableAll = () => { + this.setState(state => { + return { + disableAll: !state.disableAll, + }; + }); + }; + //---- Rendering ---- render() { return ( @@ -155,6 +164,7 @@ class MenuButtonOne extends React.Component { radioGroupOne, checkboxOneState, checkboxTwoState, radioGroupTwo, radioGroupThree, radioGroupFour, checkboxThreeState, checkboxParent, checkboxChild1, checkboxChild2, checkboxChild3, + disableAll, } = this.state; return [ @@ -170,16 +180,19 @@ class MenuButtonOne extends React.Component { node: 'Radio Option 1', value: 'option1', isChecked: radioGroupOne === 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', value: 'option2', isChecked: radioGroupOne === 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', value: 'option3', isChecked: radioGroupOne === 'option3', + isDisabled: disableAll, }, ], }, @@ -191,12 +204,14 @@ class MenuButtonOne extends React.Component { node: 'Checkbox 1', isChecked: checkboxOneState, onActivate: this.onToggleCheckboxOne, + isDisabled: disableAll, }, { type: 'checkbox', node: 'Checkbox 2', isChecked: checkboxTwoState, onActivate: this.onToggleCheckboxTwo, + isDisabled: disableAll, }, { type: 'separator', @@ -209,16 +224,19 @@ class MenuButtonOne extends React.Component { node: 'Radio Option 1', isChecked: radioGroupTwo === 'option1', value: 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', isChecked: radioGroupTwo === 'option2', value: 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', isChecked: radioGroupTwo === 'option3', value: 'option3', + isDisabled: disableAll, }, ], }, @@ -228,6 +246,7 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateItem, + isDisabled: disableAll, }, { type: 'menu', @@ -237,11 +256,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -251,11 +272,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -265,11 +288,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -281,11 +306,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -299,14 +326,17 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, + ], }, { @@ -366,6 +396,12 @@ class MenuButtonOne extends React.Component { onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, }, + { + type: 'checkbox', + node: 'Disable All', + onActivate: this.onToggleDisableAll, + isChecked: disableAll, + }, { type: 'menu', node: 'Parent Menuitem 3', diff --git a/src/MenuButtonTwo.jsx b/src/MenuButtonTwo.jsx index d4e54d30..58b64369 100644 --- a/src/MenuButtonTwo.jsx +++ b/src/MenuButtonTwo.jsx @@ -3,7 +3,7 @@ import React from 'react'; //Components and Styles import MenuButton from 'src/Menu/MenuButton'; -class MenuButtonOne extends React.Component { +class MenuButtonTwo extends React.Component { constructor(props) { super(props); @@ -19,6 +19,7 @@ class MenuButtonOne extends React.Component { checkboxChild1: false, checkboxChild2: false, checkboxChild3: false, + disableAll: false, }; } @@ -140,10 +141,18 @@ class MenuButtonOne extends React.Component { }); }; + onToggleDisableAll = () => { + this.setState(state => { + return { + disableAll: !state.disableAll, + }; + }); + }; + //---- Rendering ---- render() { return ( - + Test Button! ); @@ -155,6 +164,7 @@ class MenuButtonOne extends React.Component { radioGroupOne, checkboxOneState, checkboxTwoState, radioGroupTwo, radioGroupThree, radioGroupFour, checkboxThreeState, checkboxParent, checkboxChild1, checkboxChild2, checkboxChild3, + disableAll, } = this.state; return [ @@ -171,16 +181,19 @@ class MenuButtonOne extends React.Component { node: 'Radio Option 1', value: 'option1', isChecked: radioGroupOne === 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', value: 'option2', isChecked: radioGroupOne === 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', value: 'option3', isChecked: radioGroupOne === 'option3', + isDisabled: disableAll, }, ], }, @@ -192,12 +205,14 @@ class MenuButtonOne extends React.Component { node: 'Checkbox 1', isChecked: checkboxOneState, onActivate: this.onToggleCheckboxOne, + isDisabled: disableAll, }, { type: 'checkbox', node: 'Checkbox 2', isChecked: checkboxTwoState, onActivate: this.onToggleCheckboxTwo, + isDisabled: disableAll, }, { type: 'separator', @@ -210,16 +225,19 @@ class MenuButtonOne extends React.Component { node: 'Radio Option 1', isChecked: radioGroupTwo === 'option1', value: 'option1', + isDisabled: disableAll, }, { node: 'Radio Option 2', isChecked: radioGroupTwo === 'option2', value: 'option2', + isDisabled: disableAll, }, { node: 'Radio Option 3', isChecked: radioGroupTwo === 'option3', value: 'option3', + isDisabled: disableAll, }, ], }, @@ -229,6 +247,7 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateItem, + isDisabled: disableAll, }, { type: 'menu', @@ -238,11 +257,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -253,11 +274,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'menu', @@ -267,11 +290,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -284,11 +309,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -302,11 +329,13 @@ class MenuButtonOne extends React.Component { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, { type: 'item', node: 'Hello world!', onActivate: this.onActivateSubmenuItem, + isDisabled: disableAll, }, ], }, @@ -370,6 +399,12 @@ class MenuButtonOne extends React.Component { onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, }, + { + type: 'checkbox', + node: 'Disable All', + onActivate: this.onToggleDisableAll, + isChecked: disableAll, + }, { type: 'menu', node: 'Parent Menuitem 3', @@ -404,4 +439,4 @@ class MenuButtonOne extends React.Component { }; } -export default MenuButtonOne; +export default MenuButtonTwo; diff --git a/src/styles.scss b/src/styles.scss index 30af89c3..a7410f82 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -6,6 +6,11 @@ display: none !important; } +[aria-disabled="true"] { + opacity: 50%; + cursor: not-allowed; +} + [role="menuitem"], button[aria-haspopup="menu"] { &[aria-expanded="false"] + [role="menu"] { display: none; From cba7f22cff14c861d1bd0108147e87ecd6a6431b Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 2 Feb 2022 13:41:59 -0500 Subject: [PATCH 264/286] actually prevent activation if an item is disabled --- src/Menu/MenuBar.jsx | 7 +++++++ src/Menu/MenuButton.jsx | 7 +++++++ src/Menu/ParentMenuItem.jsx | 7 +++++++ src/MenuBarOne.jsx | 2 ++ src/MenuBarTwo.jsx | 2 ++ src/MenuButtonOne.jsx | 2 ++ src/MenuButtonTwo.jsx | 2 ++ 7 files changed, 29 insertions(+) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 3bac7496..5b867cd9 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -59,6 +59,7 @@ class MenuBar extends React.Component { const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); + const isDisabled = target.ariaDisabled === 'true'; //can't use isDisabled on the item for radigroups const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); const item = items[index]; @@ -121,6 +122,9 @@ class MenuBar extends React.Component { else if(key === 'Enter') { event.preventDefault(); + if(isDisabled) + return; + if(type === 'menu') { expandItem(flattenedIndex, () => { focusItemFirstChild(flattenedIndex); @@ -142,6 +146,9 @@ class MenuBar extends React.Component { else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + if(isDisabled) + return; + if(type === 'menu') { expandItem(flattenedIndex, () => { focusItemFirstChild(flattenedIndex); diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 5db741ea..89e6d3c6 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -109,6 +109,7 @@ class MenuButton extends React.Component { const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); + const isDisabled = target.ariaDisabled === 'true'; //can't use isDisabled on the item for radiogroups const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); const item = items[index]; @@ -157,6 +158,9 @@ class MenuButton extends React.Component { else if(key === 'Enter') { event.preventDefault(); + if(isDisabled) + return; + if(type === 'menu') { expandItem(flattenedIndex, () => { focusItemFirstChild(flattenedIndex); @@ -190,6 +194,9 @@ class MenuButton extends React.Component { else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + if(isDisabled) + return; + if(type === 'menu') { expandItem(flattenedIndex, () => { focusItemFirstChild(flattenedIndex); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 7f463722..bea86a0c 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -67,6 +67,7 @@ class _ParentMenuItem extends React.Component { const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); + const isDisabled = target.ariaDisabled === 'true'; //can't use isDisabled on the item for radiogroups const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); @@ -145,6 +146,9 @@ class _ParentMenuItem extends React.Component { else if(key === 'Enter') { event.preventDefault(); + if(isDisabled) + return; + if(type === 'menu') { expandItem(flattenedIndex, () => { focusItemFirstChild(flattenedIndex); @@ -178,6 +182,9 @@ class _ParentMenuItem extends React.Component { else if(key === ' ' || key === 'Spacebar') { event.preventDefault(); + if(isDisabled) + return; + if(type === 'menu') { expandItem(flattenedIndex, () => { focusItemFirstChild(flattenedIndex); diff --git a/src/MenuBarOne.jsx b/src/MenuBarOne.jsx index 34073717..20f7610d 100644 --- a/src/MenuBarOne.jsx +++ b/src/MenuBarOne.jsx @@ -382,6 +382,7 @@ class MenuBarOne extends React.Component { node: 'Radio Option 3', isChecked: radioGroupFour === 'option3', value: 'option3', + isDisabled: true, }, ], }, @@ -393,6 +394,7 @@ class MenuBarOne extends React.Component { node: 'Checkbox 3', onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, + isDisabled: true, }, { type: 'checkbox', diff --git a/src/MenuBarTwo.jsx b/src/MenuBarTwo.jsx index 162405c4..1a59898e 100644 --- a/src/MenuBarTwo.jsx +++ b/src/MenuBarTwo.jsx @@ -389,6 +389,7 @@ class MenuBarTwo extends React.Component { node: 'Radio Option 3', isChecked: radioGroupFour === 'option3', value: 'option3', + isDisabled: true, }, ], }, @@ -400,6 +401,7 @@ class MenuBarTwo extends React.Component { node: 'Checkbox 3', onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, + isDisabled: true, }, { type: 'checkbox', diff --git a/src/MenuButtonOne.jsx b/src/MenuButtonOne.jsx index 21709cca..9ed21533 100644 --- a/src/MenuButtonOne.jsx +++ b/src/MenuButtonOne.jsx @@ -384,6 +384,7 @@ class MenuButtonOne extends React.Component { node: 'Radio Option 3', isChecked: radioGroupFour === 'option3', value: 'option3', + isDisabled: true, }, ], }, @@ -395,6 +396,7 @@ class MenuButtonOne extends React.Component { node: 'Checkbox 3', onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, + isDisabled: true, }, { type: 'checkbox', diff --git a/src/MenuButtonTwo.jsx b/src/MenuButtonTwo.jsx index 58b64369..bb5a6c3a 100644 --- a/src/MenuButtonTwo.jsx +++ b/src/MenuButtonTwo.jsx @@ -387,6 +387,7 @@ class MenuButtonTwo extends React.Component { node: 'Radio Option 3', isChecked: radioGroupFour === 'option3', value: 'option3', + isDisabled: true, }, ], }, @@ -398,6 +399,7 @@ class MenuButtonTwo extends React.Component { node: 'Checkbox 3', onActivate: this.onToggleCheckboxThree, isChecked: checkboxThreeState, + isDisabled: true, }, { type: 'checkbox', From f539744ce999572f305a0df1731dfb44b1074f06 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 2 Feb 2022 13:54:42 -0500 Subject: [PATCH 265/286] some browsers dont support element.ariaDisabled --- src/Menu/MenuBar.jsx | 2 +- src/Menu/MenuButton.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 5b867cd9..4733f135 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -59,7 +59,7 @@ class MenuBar extends React.Component { const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); - const isDisabled = target.ariaDisabled === 'true'; //can't use isDisabled on the item for radigroups + const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radigroups const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); const item = items[index]; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 89e6d3c6..127f72f9 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -109,7 +109,7 @@ class MenuButton extends React.Component { const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); - const isDisabled = target.ariaDisabled === 'true'; //can't use isDisabled on the item for radiogroups + const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radiogroups const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); const item = items[index]; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index bea86a0c..8c40aa11 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -67,7 +67,7 @@ class _ParentMenuItem extends React.Component { const { key, target } = event; const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); - const isDisabled = target.ariaDisabled === 'true'; //can't use isDisabled on the item for radiogroups + const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radiogroups const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); From ab04993de1a3c1b3032d6d17f60b6711ea877612 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 2 Feb 2022 16:54:39 -0500 Subject: [PATCH 266/286] create a HOC to handle MenuButton-specific state --- src/Menu/createMenuButtonManager.jsx | 48 ++++++++++++++++++++++++++++ src/Menu/createMenuManager.jsx | 6 ++-- 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 src/Menu/createMenuButtonManager.jsx diff --git a/src/Menu/createMenuButtonManager.jsx b/src/Menu/createMenuButtonManager.jsx new file mode 100644 index 00000000..df487ac2 --- /dev/null +++ b/src/Menu/createMenuButtonManager.jsx @@ -0,0 +1,48 @@ +/* eslint-disable react/jsx-props-no-spreading */ + +import React from 'react'; + +export default function createMenuButtonManager(Component) { + return class MenuButtonManager extends React.Component { + constructor(props) { + super(props); + + this.state = { + isExpanded: false, + }; + } + + //---- Rendering ---- + render() { + const { isExpanded } = this.state; + + return ( + + ); + } + + //---- Misc. ---- + collapse = (collapseAllParents, callback) => { + this.setState({ + isExpanded: false, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + + expand = (callback) => { + this.setState({ + isExpanded: true, + }, () => { + if(typeof callback === 'function') + callback(); + }); + }; + } +} diff --git a/src/Menu/createMenuManager.jsx b/src/Menu/createMenuManager.jsx index 155fe1fa..01611d76 100644 --- a/src/Menu/createMenuManager.jsx +++ b/src/Menu/createMenuManager.jsx @@ -68,14 +68,14 @@ export default function createMenuManager(Component) { setItemRef = (ref) => { this.itemRefs.push(ref); }; - - collapseItem = (collapseAll, callback) => { + + collapseItem = (collapseAllParents, callback) => { const { collapse } = this.props; this.setState({ expandedIndex: -1, }, () => { - if(collapseAll) { + if(collapseAllParents) { if(typeof collapse === 'function') collapse(true, callback); //FIXME currently broken for MenuButton else if(typeof callback === 'function') From 6231850820853c686cf74381a8be8071bc5bf7bd Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 2 Feb 2022 17:00:41 -0500 Subject: [PATCH 267/286] have MenuButton use the MenuButtonManager HOC --- src/Menu/MenuButton.jsx | 59 +++++++++++----------------------- src/Menu/createMenuManager.jsx | 4 ++- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 127f72f9..04a7e332 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -11,6 +11,7 @@ import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; import MenuItemRadio from 'src/Menu/MenuItemRadio'; //HOCs +import createMenuButtonManager from 'src/Menu/createMenuButtonManager'; import createMenuManager from 'src/Menu/createMenuManager'; //Misc. @@ -43,6 +44,10 @@ class MenuButton extends React.Component { menuLabel: PropTypes.string, menuId: PropTypes.string, id: PropTypes.string, + //From MenuButtonManager + isExpanded: PropTypes.bool.isRequired, + collapse: PropTypes.func.isRequired, + expand: PropTypes.func.isRequired, //From MenuManager setManagerRef: PropTypes.func.isRequired, setItemRef: PropTypes.func.isRequired, @@ -64,37 +69,29 @@ class MenuButton extends React.Component { id: undefined, }; - constructor(props) { - super(props); - - this.state = { - isExpanded: false, - }; - } - //---- Events ---- onKeyDown = (event) => { - const { focusFirstItem, focusLastItem } = this.props; + const { expand, focusFirstItem, focusLastItem } = this.props; const { key } = event; if(key === 'Enter' || key === ' ' || key === 'Spacebar') { event.preventDefault(); - this.expandButton(() => { + expand(() => { focusFirstItem(); }); } else if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); - this.expandButton(() => { + expand(() => { focusLastItem(); }); } else if(key === 'ArrowDown' || key === 'Down') { event.preventDefault(); - this.expandButton(() => { + expand(() => { focusFirstItem(); }); } @@ -102,7 +99,7 @@ class MenuButton extends React.Component { onChildKeyDown = (event) => { const { - items, orientation, + items, orientation, collapse, expand, expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild, } = this.props; @@ -170,7 +167,7 @@ class MenuButton extends React.Component { if(typeof onActivate === 'function') onActivate(event); - this.collapseButton(() => { + collapse(false, () => { focus(); }); } @@ -178,7 +175,7 @@ class MenuButton extends React.Component { if(typeof onActivate === 'function') onActivate(event); - this.collapseButton(() => { + collapse(false, () => { focus(); }); } @@ -186,7 +183,7 @@ class MenuButton extends React.Component { if(typeof onActivate === 'function') onActivate(event); - this.collapseButton(() => { + collapse(false, () => { focus(); }); } @@ -214,7 +211,7 @@ class MenuButton extends React.Component { if(typeof onActivate === 'function') onActivate(event); - this.collapseButton(() => { + collapse(false, () => { focus(); }); } @@ -228,18 +225,17 @@ class MenuButton extends React.Component { focusLastItem(); } else if(key === 'Escape' || key === 'Esc') { - this.collapseButton(() => { + collapse(false, () => { focus(); }); } else if(key === 'Tab') - this.collapseButton(); + collapse(); }; //---- Rendering ---- render() { - const { children, orientation, menuLabel, menuId, id, setManagerRef } = this.props; - const { isExpanded } = this.state; + const { children, orientation, menuLabel, menuId, id, isExpanded, setManagerRef } = this.props; return ( @@ -405,25 +401,6 @@ class MenuButton extends React.Component { /* eslint-enable react/no-array-index-key */ }; - - //---- Misc. ---- - collapseButton = (callback) => { - this.setState({ - isExpanded: false, - }, () => { - if(typeof callback === 'function') - callback(); - }); - }; - - expandButton = (callback) => { - this.setState({ - isExpanded: true, - }, () => { - if(typeof callback === 'function') - callback(); - }); - }; } -export default createMenuManager(MenuButton); +export default createMenuButtonManager(createMenuManager(MenuButton)); diff --git a/src/Menu/createMenuManager.jsx b/src/Menu/createMenuManager.jsx index 01611d76..2afe6842 100644 --- a/src/Menu/createMenuManager.jsx +++ b/src/Menu/createMenuManager.jsx @@ -75,9 +75,11 @@ export default function createMenuManager(Component) { this.setState({ expandedIndex: -1, }, () => { + //This feels rather fragile. Do we need to care about children + //or bidirectionality? if(collapseAllParents) { if(typeof collapse === 'function') - collapse(true, callback); //FIXME currently broken for MenuButton + collapse(true, callback); else if(typeof callback === 'function') callback(); } From a2d9434318bc4990abf66e6decf941a13cc743df Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Wed, 2 Feb 2022 17:01:26 -0500 Subject: [PATCH 268/286] fix some linter complaints --- src/Menu/MenuButton.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 2 -- src/Menu/createMenuButtonManager.jsx | 2 +- src/Menu/createMenuManager.jsx | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 04a7e332..7ed3a2a1 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -99,7 +99,7 @@ class MenuButton extends React.Component { onChildKeyDown = (event) => { const { - items, orientation, collapse, expand, + items, orientation, collapse, expandItem, focus, focusPrevItem, focusNextItem, focusFirstItem, focusLastItem, focusItemFirstChild, } = this.props; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8c40aa11..1351a572 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -133,9 +133,7 @@ class _ParentMenuItem extends React.Component { }); } else if(focusNextRootItem) { - console.log('here 1'); collapse(true, () => { - console.log('here 2'); focusNextRootItem(flattenedRootIndex, true); }); } diff --git a/src/Menu/createMenuButtonManager.jsx b/src/Menu/createMenuButtonManager.jsx index df487ac2..11777ce0 100644 --- a/src/Menu/createMenuButtonManager.jsx +++ b/src/Menu/createMenuButtonManager.jsx @@ -44,5 +44,5 @@ export default function createMenuButtonManager(Component) { callback(); }); }; - } + }; } diff --git a/src/Menu/createMenuManager.jsx b/src/Menu/createMenuManager.jsx index 2afe6842..49fa6fe4 100644 --- a/src/Menu/createMenuManager.jsx +++ b/src/Menu/createMenuManager.jsx @@ -68,7 +68,7 @@ export default function createMenuManager(Component) { setItemRef = (ref) => { this.itemRefs.push(ref); }; - + collapseItem = (collapseAllParents, callback) => { const { collapse } = this.props; From c050741a8fbe68c0f08054ce546ea33b1bc966a9 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 3 Feb 2022 13:08:35 -0500 Subject: [PATCH 269/286] try offloading rendering items to an external function in menubar --- src/Menu/MenuBar.jsx | 12 ++++ src/Menu/utils.js | 153 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 src/Menu/utils.js diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 4733f135..62ac5ec6 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -14,6 +14,7 @@ import createMenuManager from 'src/Menu/createMenuManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { renderItems } from 'src/Menu/utils'; /* * Note: @@ -182,6 +183,17 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { const { orientation, label, labelId } = this.props; + const itemNodes = renderItems({ + items: this.props.items, + setItemRef: this.props.setItemRef, + tabbableIndex: this.props.tabbableIndex, + expandedIndex: this.props.expandedIndex, + collapse: this.props.collapseItem, + focusRootItem: this.props.focusItem, + focusPrevRootItem: this.props.focusPrevItem, + focusNextRootItem: this.props.focusNextItem, + onChildKeyDown: this.onChildKeyDown, + }); //console.log(this.props, this.state, this.childItemRefs); diff --git a/src/Menu/utils.js b/src/Menu/utils.js new file mode 100644 index 00000000..7b711e55 --- /dev/null +++ b/src/Menu/utils.js @@ -0,0 +1,153 @@ +import React from 'react'; + +//Components and Styles +import MenuItem from 'src/Menu/MenuItem'; +import ParentMenuItem from 'src/Menu/ParentMenuItem'; +import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; +import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; +import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; +import MenuItemRadio from 'src/Menu/MenuItemRadio'; + +/** + * Renders the items in a particular menu/sub-menu. + * + * @param {object} optiopns + * @returns {React.Component[]} + */ +export function renderItems(options) { + const { items, setItemRef, tabbableIndex, expandedIndex, collapse, focusRootItem, focusPrevRootItem, focusNextRootItem, onChildKeyDown } = options; + const itemNodes = []; + let position = []; + let flattenedPosition = []; + let flattenedIndex = 0; + + items.forEach((item, i) => { + const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; + + if(type === 'item') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'menu') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'checkbox') { + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'separator') { + itemNodes.push( + + { node } + + ); + } + else if(type === 'radiogroup') { + const radioNodes = []; + + children.forEach((radioItem, j) => { + const { node, isDisabled, isChecked, value } = radioItem; + + position = position.slice(0); + position[0] = i; + flattenedPosition = flattenedPosition.slice(0); + flattenedPosition[0] = flattenedIndex; + + radioNodes.push( + + { node } + + ); + + flattenedIndex++; + }); + + itemNodes.push( + + { radioNodes } + + ); + } + }); + + return itemNodes; +} From 98c27c11d6515ef88b69880e66695e24ba258472 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 3 Feb 2022 14:31:02 -0500 Subject: [PATCH 270/286] have menubutton adopt the external renderItems function --- src/Menu/MenuBar.jsx | 4 ++- src/Menu/MenuButton.jsx | 13 +++++++++- src/Menu/utils.js | 55 +++++++++++++++++++++-------------------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 62ac5ec6..0bdf7534 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -192,6 +192,8 @@ class MenuBar extends React.Component { focusRootItem: this.props.focusItem, focusPrevRootItem: this.props.focusPrevItem, focusNextRootItem: this.props.focusNextItem, + position: [], + flattenedPosition: [], onChildKeyDown: this.onChildKeyDown, }); @@ -204,7 +206,7 @@ class MenuBar extends React.Component { aria-labelledby={ labelId } aria-label={ label } > - { this.renderItems() } + { itemNodes /*this.renderItems()*/ }
                        ); } diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 7ed3a2a1..a5f0f591 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -16,6 +16,7 @@ import createMenuManager from 'src/Menu/createMenuManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { renderItems } from 'src/Menu/utils'; /* * Note about labels and IDs: @@ -236,6 +237,16 @@ class MenuButton extends React.Component { //---- Rendering ---- render() { const { children, orientation, menuLabel, menuId, id, isExpanded, setManagerRef } = this.props; + const itemNodes = renderItems({ + items: this.props.items, + setItemRef: this.props.setItemRef, + expandedIndex: this.props.expandedIndex, + collapse: this.props.collapseItem, + focusRootItem: this.props.focus, + position: [0], + flattenedPosition: [0], + onChildKeyDown: this.onChildKeyDown, + }); return ( @@ -256,7 +267,7 @@ class MenuButton extends React.Component { labelId={ id } id={ menuId } > - { this.renderItems() } + { itemNodes /*this.renderItems()*/ } ); diff --git a/src/Menu/utils.js b/src/Menu/utils.js index 7b711e55..5dabb7cf 100644 --- a/src/Menu/utils.js +++ b/src/Menu/utils.js @@ -15,26 +15,27 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; * @returns {React.Component[]} */ export function renderItems(options) { - const { items, setItemRef, tabbableIndex, expandedIndex, collapse, focusRootItem, focusPrevRootItem, focusNextRootItem, onChildKeyDown } = options; + const { items, setItemRef, tabbableIndex, expandedIndex, collapse, focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, onChildKeyDown } = options; const itemNodes = []; - let position = []; - let flattenedPosition = []; + const level = position.length; + let _position = []; + let _flattenedPosition = []; let flattenedIndex = 0; items.forEach((item, i) => { const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; if(type === 'item') { - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; itemNodes.push( { const { node, isDisabled, isChecked, value } = radioItem; - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; radioNodes.push( Date: Thu, 3 Feb 2022 14:39:49 -0500 Subject: [PATCH 271/286] have parentmenuitem adopt the external renderItems function --- src/Menu/MenuBar.jsx | 8 ++++---- src/Menu/MenuButton.jsx | 4 ++-- src/Menu/ParentMenuItem.jsx | 15 ++++++++++++++- src/Menu/utils.js | 8 ++++++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 0bdf7534..a23ba99f 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -185,13 +185,13 @@ class MenuBar extends React.Component { const { orientation, label, labelId } = this.props; const itemNodes = renderItems({ items: this.props.items, - setItemRef: this.props.setItemRef, - tabbableIndex: this.props.tabbableIndex, - expandedIndex: this.props.expandedIndex, - collapse: this.props.collapseItem, focusRootItem: this.props.focusItem, focusPrevRootItem: this.props.focusPrevItem, focusNextRootItem: this.props.focusNextItem, + setItemRef: this.props.setItemRef, + tabbableIndex: this.props.tabbableIndex, + expandedIndex: this.props.expandedIndex, + collapseItem: this.props.collapseItem, position: [], flattenedPosition: [], onChildKeyDown: this.onChildKeyDown, diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index a5f0f591..353ab338 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -239,10 +239,10 @@ class MenuButton extends React.Component { const { children, orientation, menuLabel, menuId, id, isExpanded, setManagerRef } = this.props; const itemNodes = renderItems({ items: this.props.items, + focusRootItem: this.props.focus, setItemRef: this.props.setItemRef, expandedIndex: this.props.expandedIndex, - collapse: this.props.collapseItem, - focusRootItem: this.props.focus, + collapseItem: this.props.collapseItem, position: [0], flattenedPosition: [0], onChildKeyDown: this.onChildKeyDown, diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 1351a572..2a8b88b4 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -14,6 +14,7 @@ import createMenuManager from 'src/Menu/createMenuManager'; //Misc. import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; +import { renderItems } from 'src/Menu/utils'; class _ParentMenuItem extends React.Component { static propTypes = { @@ -232,6 +233,18 @@ class _ParentMenuItem extends React.Component { isExpanded, isDisabled, isTabbable, setManagerRef, } = this.props; + const itemNodes = renderItems({ + items: this.props.items, + focusPrevRootItem: this.props.focusPrevRootItem, + focusNextRootItem: this.props.focusNextRootItem, + focusRootItem: this.props.focusRootItem, + setItemRef: this.props.setItemRef, + expandedIndex: this.props.expandedIndex, + collapseItem: this.props.collapseItem, + position: this.props.position, + flattenedPosition: this.props.flattenedPosition, + onChildKeyDown: this.onChildKeyDown, + }); //console.log(this.props, this.state, this.itemRef, this.childItemRefs); @@ -256,7 +269,7 @@ class _ParentMenuItem extends React.Component { label={ label } labelId={ labelId } > - { this.renderItems() } + { itemNodes /*this.renderItems()*/ } ); diff --git a/src/Menu/utils.js b/src/Menu/utils.js index 5dabb7cf..3266b062 100644 --- a/src/Menu/utils.js +++ b/src/Menu/utils.js @@ -15,7 +15,9 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; * @returns {React.Component[]} */ export function renderItems(options) { - const { items, setItemRef, tabbableIndex, expandedIndex, collapse, focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, onChildKeyDown } = options; + /* eslint-disable react/no-array-index-key */ + + const { items, setItemRef, tabbableIndex, expandedIndex, collapseItem, focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, onChildKeyDown } = options; const itemNodes = []; const level = position.length; let _position = []; @@ -60,7 +62,7 @@ export function renderItems(options) { position={ _position } flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } - collapse={ collapse } + collapse={ collapseItem } focusPrevRootItem={ focusPrevRootItem } focusNextRootItem={ focusNextRootItem } focusRootItem={ focusRootItem } @@ -151,4 +153,6 @@ export function renderItems(options) { }); return itemNodes; + + /* eslint-enable react/no-array-index-key */ } From 1882e8c5fd64bde58ea5e94e6cd6c07bde6b0ae3 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 3 Feb 2022 14:47:45 -0500 Subject: [PATCH 272/286] remove old renderItems methods --- src/Menu/MenuBar.jsx | 149 +---------------------------------- src/Menu/MenuButton.jsx | 142 +-------------------------------- src/Menu/ParentMenuItem.jsx | 152 +----------------------------------- 3 files changed, 5 insertions(+), 438 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index a23ba99f..238df41d 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -197,8 +197,6 @@ class MenuBar extends React.Component { onChildKeyDown: this.onChildKeyDown, }); - //console.log(this.props, this.state, this.childItemRefs); - return (
                          - { itemNodes /*this.renderItems()*/ } + { itemNodes }
                        ); } - - renderItems = () => { - /* eslint-disable react/no-array-index-key */ - - const { - items, setItemRef, tabbableIndex, expandedIndex, - collapseItem, focusItem, focusPrevItem, focusNextItem, - } = this.props; - const itemNodes = []; - let position = []; - let flattenedPosition = []; - let flattenedIndex = 0; - - items.forEach((item, i) => { - const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; - - if(type === 'item') { - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'menu') { - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'checkbox') { - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'separator') { - itemNodes.push( - - { node } - - ); - } - else if(type === 'radiogroup') { - const radioNodes = []; - - children.forEach((radioItem, j) => { - const { node, isDisabled, isChecked, value } = radioItem; - - position = position.slice(0); - position[0] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[0] = flattenedIndex; - - radioNodes.push( - - { node } - - ); - - flattenedIndex++; - }); - - itemNodes.push( - - { radioNodes } - - ); - } - }); - - return itemNodes; - - /* eslint-enable react/no-array-index-key */ - }; } export default createMenuManager(MenuBar); diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 353ab338..61e0a315 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -267,151 +267,11 @@ class MenuButton extends React.Component { labelId={ id } id={ menuId } > - { itemNodes /*this.renderItems()*/ } + { itemNodes } ); } - - renderItems = () => { - /* eslint-disable react/no-array-index-key */ - - const { items, setItemRef, expandedIndex, collapseItem, focus } = this.props; - const itemNodes = []; - let position = [ 0 ]; - let flattenedPosition = [ 0 ]; - let flattenedIndex = 0; - - items.forEach((item, i) => { - const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; - - if(type === 'item') { - position = position.slice(0); - position[1] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[1] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'menu') { - position = position.slice(0); - position[1] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[1] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'checkbox') { - position = position.slice(0); - position[1] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[1] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'separator') { - itemNodes.push( - - { node } - - ); - } - else if(type === 'radiogroup') { - const radioNodes = []; - - children.forEach((radioItem, j) => { - const { node, isDisabled, isChecked, value } = radioItem; - - position = position.slice(0); - position[1] = i; - flattenedPosition = flattenedPosition.slice(0); - flattenedPosition[1] = flattenedIndex; - - radioNodes.push( - - { node } - - ); - - flattenedIndex++; - }); - - itemNodes.push( - - { radioNodes } - - ); - } - }); - - return itemNodes; - - /* eslint-enable react/no-array-index-key */ - }; } export default createMenuButtonManager(createMenuManager(MenuButton)); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 2a8b88b4..300294ec 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -16,7 +16,7 @@ import createMenuManager from 'src/Menu/createMenuManager'; import { MENUITEMS_PROPTYPE } from 'src/utils/propTypes'; import { renderItems } from 'src/Menu/utils'; -class _ParentMenuItem extends React.Component { +class ParentMenuItem extends React.Component { static propTypes = { children: PropTypes.node.isRequired, items: MENUITEMS_PROPTYPE.isRequired, @@ -246,8 +246,6 @@ class _ParentMenuItem extends React.Component { onChildKeyDown: this.onChildKeyDown, }); - //console.log(this.props, this.state, this.itemRef, this.childItemRefs); - return (
                      • - { itemNodes /*this.renderItems()*/ } + { itemNodes }
                      • ); } - - renderItems = () => { - /* eslint-disable react/no-array-index-key */ - - const { - items, focusPrevRootItem, focusNextRootItem, focusRootItem, position, flattenedPosition, - setItemRef, expandedIndex, collapseItem, - } = this.props; - const level = position.length; - const itemNodes = []; - let _position = []; - let _flattenedPosition = []; - let flattenedIndex = 0; - - items.forEach((item, i) => { - const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; - - if(type === 'item') { - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'menu') { - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'checkbox') { - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'separator') { - itemNodes.push( - - { node } - - ); - } - else if(type === 'radiogroup') { - const radioNodes = []; - - children.forEach((radioItem, j) => { - const { node, isDisabled, isChecked, value } = radioItem; - - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - radioNodes.push( - - { node } - - ); - - flattenedIndex++; - }); - - itemNodes.push( - - { radioNodes } - - ); - } - }); - - return itemNodes; - - /* eslint-enable react/no-array-index-key */ - }; } -const ParentMenuItem = createMenuManager(_ParentMenuItem); - -export default ParentMenuItem; +export default createMenuManager(ParentMenuItem); From da29fc905201afa373fbc8984525ff752205c54e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 3 Feb 2022 15:18:23 -0500 Subject: [PATCH 273/286] use destructuring and finish jsdoc types for renderItems --- src/Menu/MenuBar.jsx | 23 ++++++++++++----------- src/Menu/MenuButton.jsx | 15 +++++++++------ src/Menu/ParentMenuItem.jsx | 26 +++++++++++++------------- src/Menu/utils.js | 18 ++++++++++++++++-- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 238df41d..8f8ddc16 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -66,8 +66,6 @@ class MenuBar extends React.Component { const item = items[index]; const { type, onActivate } = item; - //console.log(position, flattenedPosition, index, flattenedIndex, item); - if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); @@ -182,16 +180,19 @@ class MenuBar extends React.Component { //---- Rendering ---- render() { - const { orientation, label, labelId } = this.props; + const { + items, orientation, label, labelId, + setItemRef, tabbableIndex, expandedIndex, collapseItem, focusItem, focusPrevItem, focusNextItem, + } = this.props; const itemNodes = renderItems({ - items: this.props.items, - focusRootItem: this.props.focusItem, - focusPrevRootItem: this.props.focusPrevItem, - focusNextRootItem: this.props.focusNextItem, - setItemRef: this.props.setItemRef, - tabbableIndex: this.props.tabbableIndex, - expandedIndex: this.props.expandedIndex, - collapseItem: this.props.collapseItem, + items, + setItemRef, + tabbableIndex, + expandedIndex, + collapseItem, + focusRootItem: focusItem, + focusPrevRootItem: focusPrevItem, + focusNextRootItem: focusNextItem, position: [], flattenedPosition: [], onChildKeyDown: this.onChildKeyDown, diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 61e0a315..3c40286c 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -236,13 +236,16 @@ class MenuButton extends React.Component { //---- Rendering ---- render() { - const { children, orientation, menuLabel, menuId, id, isExpanded, setManagerRef } = this.props; + const { + children, items, orientation, menuLabel, menuId, id, isExpanded, + setManagerRef, setItemRef, expandedIndex, collapseItem, focus + } = this.props; const itemNodes = renderItems({ - items: this.props.items, - focusRootItem: this.props.focus, - setItemRef: this.props.setItemRef, - expandedIndex: this.props.expandedIndex, - collapseItem: this.props.collapseItem, + items, + setItemRef, + expandedIndex, + collapseItem, + focusRootItem: focus, position: [0], flattenedPosition: [0], onChildKeyDown: this.onChildKeyDown, diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 300294ec..4c887a25 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -228,21 +228,21 @@ class ParentMenuItem extends React.Component { //---- Rendering ---- render() { const { - children, position, flattenedPosition, onKeyDown, - orientation, label, labelId, - isExpanded, isDisabled, isTabbable, - setManagerRef, + children, items, position, flattenedPosition, onKeyDown, + focusPrevRootItem, focusNextRootItem, focusRootItem, + orientation, label, labelId, isExpanded, isDisabled, isTabbable, + setManagerRef, setItemRef, expandedIndex, collapseItem } = this.props; const itemNodes = renderItems({ - items: this.props.items, - focusPrevRootItem: this.props.focusPrevRootItem, - focusNextRootItem: this.props.focusNextRootItem, - focusRootItem: this.props.focusRootItem, - setItemRef: this.props.setItemRef, - expandedIndex: this.props.expandedIndex, - collapseItem: this.props.collapseItem, - position: this.props.position, - flattenedPosition: this.props.flattenedPosition, + items, + setItemRef, + expandedIndex, + collapseItem, + focusRootItem, + focusPrevRootItem, + focusNextRootItem, + position, + flattenedPosition, onChildKeyDown: this.onChildKeyDown, }); diff --git a/src/Menu/utils.js b/src/Menu/utils.js index 3266b062..4464ab51 100644 --- a/src/Menu/utils.js +++ b/src/Menu/utils.js @@ -11,13 +11,27 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; /** * Renders the items in a particular menu/sub-menu. * - * @param {object} optiopns + * @param {object} options + * @param {object[]} options.items + * @param {function} options.setItemRef + * @param {number} [options.tabbableIndex] + * @param {number} options.expandableIndex + * @param {function} options.collapseItem + * @param {function} [options.focusRootItem] + * @param {function} [options.focusPrevRootItem] + * @param {function} [options.focusNextRootItem] + * @param {number[]} options.position + * @param {number[]} options.flattenedPosition + * @param {function} options.onChildKeyDown * @returns {React.Component[]} */ export function renderItems(options) { /* eslint-disable react/no-array-index-key */ - const { items, setItemRef, tabbableIndex, expandedIndex, collapseItem, focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, onChildKeyDown } = options; + const { + items, setItemRef, tabbableIndex, expandedIndex, collapseItem, + focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, onChildKeyDown + } = options; const itemNodes = []; const level = position.length; let _position = []; From b9319566e855595ae1bcd8c06761912b1bc0e033 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 3 Feb 2022 15:19:38 -0500 Subject: [PATCH 274/286] resolve some linter complaints --- src/Menu/MenuBar.jsx | 8 -- src/Menu/MenuButton.jsx | 12 +-- src/Menu/ParentMenuItem.jsx | 7 +- src/Menu/utils.js | 172 ------------------------------------ 4 files changed, 4 insertions(+), 195 deletions(-) delete mode 100644 src/Menu/utils.js diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 8f8ddc16..28fbc6ab 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -1,14 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -//Components and Styles -import MenuItem from 'src/Menu/MenuItem'; -import ParentMenuItem from 'src/Menu/ParentMenuItem'; -import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; -import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; -import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; -import MenuItemRadio from 'src/Menu/MenuItemRadio'; - //HOCs import createMenuManager from 'src/Menu/createMenuManager'; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 3c40286c..9f5f4e61 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -3,12 +3,6 @@ import PropTypes from 'prop-types'; //Components and Styles import Menu from 'src/Menu/Menu'; -import MenuItem from 'src/Menu/MenuItem'; -import ParentMenuItem from 'src/Menu/ParentMenuItem'; -import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; -import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; -import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; -import MenuItemRadio from 'src/Menu/MenuItemRadio'; //HOCs import createMenuButtonManager from 'src/Menu/createMenuButtonManager'; @@ -238,7 +232,7 @@ class MenuButton extends React.Component { render() { const { children, items, orientation, menuLabel, menuId, id, isExpanded, - setManagerRef, setItemRef, expandedIndex, collapseItem, focus + setManagerRef, setItemRef, expandedIndex, collapseItem, focus, } = this.props; const itemNodes = renderItems({ items, @@ -246,8 +240,8 @@ class MenuButton extends React.Component { expandedIndex, collapseItem, focusRootItem: focus, - position: [0], - flattenedPosition: [0], + position: [ 0 ], + flattenedPosition: [ 0 ], onChildKeyDown: this.onChildKeyDown, }); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 4c887a25..eb3dfd4f 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -3,11 +3,6 @@ import PropTypes from 'prop-types'; //Components and Styles import Menu from 'src/Menu/Menu'; -import MenuItem from 'src/Menu/MenuItem'; -import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; -import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; -import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; -import MenuItemRadio from 'src/Menu/MenuItemRadio'; //HOCs import createMenuManager from 'src/Menu/createMenuManager'; @@ -231,7 +226,7 @@ class ParentMenuItem extends React.Component { children, items, position, flattenedPosition, onKeyDown, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation, label, labelId, isExpanded, isDisabled, isTabbable, - setManagerRef, setItemRef, expandedIndex, collapseItem + setManagerRef, setItemRef, expandedIndex, collapseItem, } = this.props; const itemNodes = renderItems({ items, diff --git a/src/Menu/utils.js b/src/Menu/utils.js deleted file mode 100644 index 4464ab51..00000000 --- a/src/Menu/utils.js +++ /dev/null @@ -1,172 +0,0 @@ -import React from 'react'; - -//Components and Styles -import MenuItem from 'src/Menu/MenuItem'; -import ParentMenuItem from 'src/Menu/ParentMenuItem'; -import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; -import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; -import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; -import MenuItemRadio from 'src/Menu/MenuItemRadio'; - -/** - * Renders the items in a particular menu/sub-menu. - * - * @param {object} options - * @param {object[]} options.items - * @param {function} options.setItemRef - * @param {number} [options.tabbableIndex] - * @param {number} options.expandableIndex - * @param {function} options.collapseItem - * @param {function} [options.focusRootItem] - * @param {function} [options.focusPrevRootItem] - * @param {function} [options.focusNextRootItem] - * @param {number[]} options.position - * @param {number[]} options.flattenedPosition - * @param {function} options.onChildKeyDown - * @returns {React.Component[]} - */ -export function renderItems(options) { - /* eslint-disable react/no-array-index-key */ - - const { - items, setItemRef, tabbableIndex, expandedIndex, collapseItem, - focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, onChildKeyDown - } = options; - const itemNodes = []; - const level = position.length; - let _position = []; - let _flattenedPosition = []; - let flattenedIndex = 0; - - items.forEach((item, i) => { - const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; - - if(type === 'item') { - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'menu') { - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'checkbox') { - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - itemNodes.push( - - { node } - - ); - - flattenedIndex++; - } - else if(type === 'separator') { - itemNodes.push( - - { node } - - ); - } - else if(type === 'radiogroup') { - const radioNodes = []; - - children.forEach((radioItem, j) => { - const { node, isDisabled, isChecked, value } = radioItem; - - _position = position.slice(0); - _position[level] = i; - _flattenedPosition = flattenedPosition.slice(0); - _flattenedPosition[level] = flattenedIndex; - - radioNodes.push( - - { node } - - ); - - flattenedIndex++; - }); - - itemNodes.push( - - { radioNodes } - - ); - } - }); - - return itemNodes; - - /* eslint-enable react/no-array-index-key */ -} From 1af263cea756f79e412304c6b532fb938de292ea Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Thu, 3 Feb 2022 15:27:46 -0500 Subject: [PATCH 275/286] remove some repeated code --- src/Menu/MenuButton.jsx | 2 - src/Menu/ParentMenuItem.jsx | 2 - src/Menu/utils.jsx | 172 ++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/Menu/utils.jsx diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 9f5f4e61..ced9ed55 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -107,8 +107,6 @@ class MenuButton extends React.Component { const item = items[index]; const { type, onActivate } = item; - //console.log(position, flattenedPosition, index, flattenedIndex, item); - if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index eb3dfd4f..c100dc29 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -71,8 +71,6 @@ class ParentMenuItem extends React.Component { const item = items[index]; const { type, onActivate } = item; - //console.log(position, flattenedPosition, index, flattenedIndex, level, item); - if(key === 'ArrowUp' || key === 'Up') { event.preventDefault(); diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx new file mode 100644 index 00000000..c2df148c --- /dev/null +++ b/src/Menu/utils.jsx @@ -0,0 +1,172 @@ +import React from 'react'; + +//Components and Styles +import MenuItem from 'src/Menu/MenuItem'; +import ParentMenuItem from 'src/Menu/ParentMenuItem'; +import MenuItemCheckbox from 'src/Menu/MenuItemCheckbox'; +import MenuItemSeparator from 'src/Menu/MenuItemSeparator'; +import MenuItemRadioGroup from 'src/Menu/MenuItemRadioGroup'; +import MenuItemRadio from 'src/Menu/MenuItemRadio'; + +/** + * Renders the items in a particular menu/sub-menu. + * + * @param {object} options + * @param {object[]} options.items + * @param {function} options.setItemRef + * @param {number} [options.tabbableIndex] + * @param {number} options.expandableIndex + * @param {function} options.collapseItem + * @param {function} [options.focusRootItem] + * @param {function} [options.focusPrevRootItem] + * @param {function} [options.focusNextRootItem] + * @param {number[]} options.position + * @param {number[]} options.flattenedPosition + * @param {function} options.onChildKeyDown + * @returns {React.Component[]} + */ +export function renderItems(options) { + /* eslint-disable react/no-array-index-key */ + + const { + items, setItemRef, tabbableIndex, expandedIndex, collapseItem, + focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, onChildKeyDown, + } = options; + const itemNodes = []; + const level = position.length; + let _position = []; + let _flattenedPosition = []; + let flattenedIndex = 0; + + items.forEach((item, i) => { + const { type, node, children, orientation, label, labelId, isDisabled, isChecked } = item; + + if(type === 'item') { + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'menu') { + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'checkbox') { + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; + + itemNodes.push( + + { node } + + ); + + flattenedIndex++; + } + else if(type === 'separator') { + itemNodes.push( + + { node } + + ); + } + else if(type === 'radiogroup') { + const radioNodes = []; + + children.forEach((radioItem, j) => { + const { node, isDisabled, isChecked, value } = radioItem; + + _position = position.slice(0); + _position[level] = i; + _flattenedPosition = flattenedPosition.slice(0); + _flattenedPosition[level] = flattenedIndex; + + radioNodes.push( + + { node } + + ); + + flattenedIndex++; + }); + + itemNodes.push( + + { radioNodes } + + ); + } + }); + + return itemNodes; + + /* eslint-enable react/no-array-index-key */ +} From f306aa8223f3c1a03d05937e22124246e5d805e5 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 12:18:30 -0500 Subject: [PATCH 276/286] naive expand parentmenuitems on root menubar level --- src/Menu/MenuBar.jsx | 25 +++++++++++++++++++++++++ src/Menu/MenuItem.jsx | 4 +++- src/Menu/MenuItemCheckbox.jsx | 4 +++- src/Menu/MenuItemRadio.jsx | 4 +++- src/Menu/ParentMenuItem.jsx | 4 +++- src/Menu/utils.jsx | 8 +++++++- 6 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 28fbc6ab..19963e66 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -43,6 +43,30 @@ class MenuBar extends React.Component { }; //---- Events ---- + onChildClick = (event) => { + const { items, expandItem } = this.props + const { target } = event; + const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radigroups + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type, onActivate } = item; + + console.log(items, expandItem); + + if(type === 'menu') { + expandItem(flattenedIndex); + } + else if(type === 'checkbox') { + } + else if(type === 'radiogroup') { + } + else if(type === 'item') { + } + }; + onChildKeyDown = (event) => { const { items, orientation, @@ -188,6 +212,7 @@ class MenuBar extends React.Component { position: [], flattenedPosition: [], onChildKeyDown: this.onChildKeyDown, + onChildClick: this.onChildClick, }); return ( diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index ef2c6410..d73967cf 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, position, flattenedPosition, onKeyDown, isDisabled, isTabbable } = props; + const { children, position, flattenedPosition, onKeyDown, onClick, isDisabled, isTabbable } = props; return (
                      • Date: Tue, 8 Feb 2022 12:56:57 -0500 Subject: [PATCH 277/286] clicking the same menu closes it --- src/Menu/MenuBar.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 19963e66..a3870279 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -44,7 +44,7 @@ class MenuBar extends React.Component { //---- Events ---- onChildClick = (event) => { - const { items, expandItem } = this.props + const { items, expandedIndex, collapseItem, expandItem } = this.props const { target } = event; const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radigroups const position = target.dataset.position.split(','); @@ -54,10 +54,11 @@ class MenuBar extends React.Component { const item = items[index]; const { type, onActivate } = item; - console.log(items, expandItem); - if(type === 'menu') { - expandItem(flattenedIndex); + if(expandedIndex === flattenedIndex) + collapseItem(); + else + expandItem(flattenedIndex); } else if(type === 'checkbox') { } From fd0e3f38f0df33d2382f1ef318ce5c0c344c1672 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 12:59:14 -0500 Subject: [PATCH 278/286] expand/collapse menu button on clicking --- src/Menu/MenuButton.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index ced9ed55..3468ec95 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -65,6 +65,15 @@ class MenuButton extends React.Component { }; //---- Events ---- + onClick = () => { + const { isExpanded, collapse, expand } = this.props; + + if(isExpanded) + collapse(); + else + expand(); + }; + onKeyDown = (event) => { const { expand, focusFirstItem, focusLastItem } = this.props; const { key } = event; @@ -252,6 +261,7 @@ class MenuButton extends React.Component { id={ id } aria-expanded={ isExpanded } onKeyDown={ this.onKeyDown } + onClick={ this.onClick } ref={ setManagerRef } > { children } From b1cc78807cfbc9930f93afce1279167cd2e58d51 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 13:00:38 -0500 Subject: [PATCH 279/286] click to expand/collapse on first level of a menubutton --- src/Menu/MenuButton.jsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 3468ec95..07decb95 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -101,6 +101,31 @@ class MenuButton extends React.Component { } }; + onChildClick = (event) => { + const { items, expandedIndex, collapseItem, expandItem } = this.props + const { target } = event; + const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radigroups + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type, onActivate } = item; + + if(type === 'menu') { + if(expandedIndex === flattenedIndex) + collapseItem(); + else + expandItem(flattenedIndex); + } + else if(type === 'checkbox') { + } + else if(type === 'radiogroup') { + } + else if(type === 'item') { + } + }; + onChildKeyDown = (event) => { const { items, orientation, collapse, @@ -250,6 +275,7 @@ class MenuButton extends React.Component { position: [ 0 ], flattenedPosition: [ 0 ], onChildKeyDown: this.onChildKeyDown, + onChildClick: this.onChildClick, }); return ( From 0f2e0b998ca3505fc5f1f9bb0bc481bc8fcc8437 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 13:05:34 -0500 Subject: [PATCH 280/286] expand/collapse when clicking submenus --- src/Menu/ParentMenuItem.jsx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 24051a5f..8509b8ad 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -56,6 +56,31 @@ class ParentMenuItem extends React.Component { }; //---- Events ---- + onChildClick = (event) => { + const { items, expandedIndex, collapseItem, expandItem } = this.props + const { target } = event; + const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radigroups + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type, onActivate } = item; + + if(type === 'menu') { + if(expandedIndex === flattenedIndex) + collapseItem(); + else + expandItem(flattenedIndex); + } + else if(type === 'checkbox') { + } + else if(type === 'radiogroup') { + } + else if(type === 'item') { + } + }; + onChildKeyDown = (event) => { const { items, collapse, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation, @@ -238,6 +263,7 @@ class ParentMenuItem extends React.Component { position, flattenedPosition, onChildKeyDown: this.onChildKeyDown, + onChildClick: this.onChildClick, }); return ( From e807ec89581c104ddde1eee593346e1bbc779fcc Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 13:36:04 -0500 Subject: [PATCH 281/286] activate items when you click on them --- src/Menu/MenuBar.jsx | 11 ++++++----- src/Menu/MenuButton.jsx | 17 +++++++++++------ src/Menu/ParentMenuItem.jsx | 20 +++++++++++++------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index a3870279..42d5d05c 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -60,11 +60,12 @@ class MenuBar extends React.Component { else expandItem(flattenedIndex); } - else if(type === 'checkbox') { - } - else if(type === 'radiogroup') { - } - else if(type === 'item') { + else if(type === 'checkbox' || type === 'radiogroup' || type === 'item') { + if(isDisabled) + return; + + if(typeof onActivate === 'function') + onActivate(event); } }; diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 07decb95..702bb9c2 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -102,7 +102,7 @@ class MenuButton extends React.Component { }; onChildClick = (event) => { - const { items, expandedIndex, collapseItem, expandItem } = this.props + const { items, collapse, expandedIndex, collapseItem, expandItem, focus } = this.props const { target } = event; const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radigroups const position = target.dataset.position.split(','); @@ -118,11 +118,16 @@ class MenuButton extends React.Component { else expandItem(flattenedIndex); } - else if(type === 'checkbox') { - } - else if(type === 'radiogroup') { - } - else if(type === 'item') { + else if(type === 'checkbox' || type === 'radiogroup' || type === 'item') { + if(isDisabled) + return; + + if(typeof onActivate === 'function') + onActivate(event); + + collapse(false, () => { + focus(); + }); } }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 8509b8ad..151954f1 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -57,13 +57,14 @@ class ParentMenuItem extends React.Component { //---- Events ---- onChildClick = (event) => { - const { items, expandedIndex, collapseItem, expandItem } = this.props + const { items, collapse, focusRootItem, expandedIndex, collapseItem, expandItem } = this.props const { target } = event; const isDisabled = target.getAttribute('aria-disabled') === 'true'; //can't use isDisabled on the item for radigroups const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); const index = Number.parseInt(position[position.length - 1], 10); const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const flattenedRootIndex = Number.parseInt(flattenedPosition[0], 10); const item = items[index]; const { type, onActivate } = item; @@ -73,12 +74,17 @@ class ParentMenuItem extends React.Component { else expandItem(flattenedIndex); } - else if(type === 'checkbox') { - } - else if(type === 'radiogroup') { - } - else if(type === 'item') { - } + else if(type === 'checkbox' || type === 'radiogroup' || type === 'item') { + if(isDisabled) + return; + + if(typeof onActivate === 'function') + onActivate(event); + + collapse(true, () => { + focusRootItem(flattenedRootIndex); + }); + } }; onChildKeyDown = (event) => { From bce648b41c57f27ae9b333235c83361f5db94659 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 14:22:57 -0500 Subject: [PATCH 282/286] scaffolding for mouse enter/leave events --- src/Menu/MenuBar.jsx | 23 +++++++++++++++++++++++ src/Menu/MenuButton.jsx | 8 ++++++++ src/Menu/MenuItem.jsx | 10 +++++++++- src/Menu/MenuItemCheckbox.jsx | 10 +++++++++- src/Menu/MenuItemRadio.jsx | 10 +++++++++- src/Menu/ParentMenuItem.jsx | 15 ++++++++++++++- src/Menu/utils.jsx | 12 +++++++++++- 7 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 42d5d05c..8aa5f96b 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -196,6 +196,27 @@ class MenuBar extends React.Component { collapseItem(); }; + onChildEnter = (event) => { + const { items, expandedIndex, expandItem, focusItem } = this.props; + const { target } = event; + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type } = item; + + /* + focusItem(flattenedIndex); + + if(type === 'menu' && expandedIndex !== -1) + expandItem(flattenedIndex); + */ + }; + + onChildLeave = () => { + }; + //---- Rendering ---- render() { const { @@ -215,6 +236,8 @@ class MenuBar extends React.Component { flattenedPosition: [], onChildKeyDown: this.onChildKeyDown, onChildClick: this.onChildClick, + onChildEnter: this.onChildEnter, + onChildLeave: this.onChildLeave, }); return ( diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 702bb9c2..69ea092b 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -265,6 +265,12 @@ class MenuButton extends React.Component { collapse(); }; + onChildEnter = () => { + }; + + onChildLeave = () => { + }; + //---- Rendering ---- render() { const { @@ -281,6 +287,8 @@ class MenuButton extends React.Component { flattenedPosition: [ 0 ], onChildKeyDown: this.onChildKeyDown, onChildClick: this.onChildClick, + onChildEnter: this.onChildEnter, + onChildLeave: this.onChildLeave, }); return ( diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index d73967cf..4c898f19 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -2,7 +2,11 @@ import React from 'react'; import PropTypes from 'prop-types'; const MenuItem = React.forwardRef(function MenuItem(props, ref) { - const { children, position, flattenedPosition, onKeyDown, onClick, isDisabled, isTabbable } = props; + const { + children, position, flattenedPosition, + onKeyDown, onClick, onMouseEnter, onMouseLeave, + isDisabled, isTabbable + } = props; return (
                      • { + }; + + onChildLeave = () => { + }; + //---- Rendering ---- render() { const { - children, items, position, flattenedPosition, onKeyDown, onClick, + children, items, position, flattenedPosition, + onKeyDown, onClick, onMouseEnter, onMouseLeave, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation, label, labelId, isExpanded, isDisabled, isTabbable, setManagerRef, setItemRef, expandedIndex, collapseItem, @@ -270,6 +279,8 @@ class ParentMenuItem extends React.Component { flattenedPosition, onChildKeyDown: this.onChildKeyDown, onChildClick: this.onChildClick, + onChildEnter: this.onChildEnter, + onChildLeave: this.onChildLeave, }); return ( @@ -282,6 +293,8 @@ class ParentMenuItem extends React.Component { data-flattenedposition={ flattenedPosition } onKeyDown={ onKeyDown } onClick={ onClick } + onMouseEnter={ onMouseEnter } + onMouseLeave={ onMouseLeave } aria-expanded={ isExpanded } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index 77c44a20..dc645c62 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -24,6 +24,8 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; * @param {number[]} options.flattenedPosition * @param {function} options.onChildKeyDown * @param {function} options.onChildClick + * @param {function} options.onChildEnter + * @param {function} options.onChildLeave * @returns {React.Component[]} */ export function renderItems(options) { @@ -32,7 +34,7 @@ export function renderItems(options) { const { items, setItemRef, tabbableIndex, expandedIndex, collapseItem, focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, - onChildKeyDown, onChildClick, + onChildKeyDown, onChildClick, onChildEnter, onChildLeave } = options; const itemNodes = []; const level = position.length; @@ -56,6 +58,8 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } + onMouseEnter={ onChildEnter } + onMouseLeave={ onChildLeave } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } ref={ setItemRef } @@ -80,6 +84,8 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } + onMouseEnter={ onChildEnter } + onMouseLeave={ onChildLeave } collapse={ collapseItem } focusPrevRootItem={ focusPrevRootItem } focusNextRootItem={ focusNextRootItem } @@ -111,6 +117,8 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } + onMouseEnter={ onChildEnter } + onMouseLeave={ onChildLeave } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } @@ -147,6 +155,8 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } + onMouseEnter={ onChildEnter } + onMouseLeave={ onChildLeave } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } isChecked={ isChecked } From ffc24328c34b3252ba3b1748bec44796aa3baefd Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 14:27:29 -0500 Subject: [PATCH 283/286] retain "wasExpanded" state even for non-menus --- src/Menu/createMenuManager.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Menu/createMenuManager.jsx b/src/Menu/createMenuManager.jsx index 49fa6fe4..e4f65d3a 100644 --- a/src/Menu/createMenuManager.jsx +++ b/src/Menu/createMenuManager.jsx @@ -108,11 +108,10 @@ export default function createMenuManager(Component) { this.setState(state => { const { expandedIndex } = state; const wasExpanded = expandedIndex !== -1; - const _autoExpand = isMenu && (autoExpand || wasExpanded); return { tabbableIndex: index, - expandedIndex: _autoExpand ? index : -1, + expandedIndex: autoExpand || wasExpanded ? index : -1, }; }, () => { if(isMenu) From 7efda1303a3a27ee1489ac941b14cf2a58ba4aa2 Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 14:36:44 -0500 Subject: [PATCH 284/286] replace mouseenter with mouse over, try automatically focusing and expanding menus (if already expanded) --- src/Menu/MenuBar.jsx | 9 +++++---- src/Menu/MenuButton.jsx | 20 ++++++++++++++++++-- src/Menu/MenuItem.jsx | 6 +++--- src/Menu/MenuItemCheckbox.jsx | 6 +++--- src/Menu/MenuItemRadio.jsx | 6 +++--- src/Menu/ParentMenuItem.jsx | 26 +++++++++++++++++++++----- src/Menu/utils.jsx | 12 ++++++------ 7 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index 8aa5f96b..fdf64a1c 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -196,9 +196,12 @@ class MenuBar extends React.Component { collapseItem(); }; - onChildEnter = (event) => { + onChildOver = (event) => { const { items, expandedIndex, expandItem, focusItem } = this.props; const { target } = event; + + console.log(target); + const position = target.dataset.position.split(','); const flattenedPosition = target.dataset.flattenedposition.split(','); const index = Number.parseInt(position[position.length - 1], 10); @@ -206,12 +209,10 @@ class MenuBar extends React.Component { const item = items[index]; const { type } = item; - /* focusItem(flattenedIndex); if(type === 'menu' && expandedIndex !== -1) expandItem(flattenedIndex); - */ }; onChildLeave = () => { @@ -236,7 +237,7 @@ class MenuBar extends React.Component { flattenedPosition: [], onChildKeyDown: this.onChildKeyDown, onChildClick: this.onChildClick, - onChildEnter: this.onChildEnter, + onChildOver: this.onChildOver, onChildLeave: this.onChildLeave, }); diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index 69ea092b..ef1ce3a1 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -265,7 +265,23 @@ class MenuButton extends React.Component { collapse(); }; - onChildEnter = () => { + onChildOver = () => { + const { items, expandedIndex, expandItem, focusItem } = this.props; + const { target } = event; + + console.log(target); + + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type } = item; + + focusItem(flattenedIndex); + + if(type === 'menu' && expandedIndex !== -1) + expandItem(flattenedIndex); }; onChildLeave = () => { @@ -287,7 +303,7 @@ class MenuButton extends React.Component { flattenedPosition: [ 0 ], onChildKeyDown: this.onChildKeyDown, onChildClick: this.onChildClick, - onChildEnter: this.onChildEnter, + onChildOver: this.onChildOver, onChildLeave: this.onChildLeave, }); diff --git a/src/Menu/MenuItem.jsx b/src/Menu/MenuItem.jsx index 4c898f19..03c3e66e 100644 --- a/src/Menu/MenuItem.jsx +++ b/src/Menu/MenuItem.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; const MenuItem = React.forwardRef(function MenuItem(props, ref) { const { children, position, flattenedPosition, - onKeyDown, onClick, onMouseEnter, onMouseLeave, + onKeyDown, onClick, onMouseOver, onMouseLeave, isDisabled, isTabbable } = props; @@ -15,7 +15,7 @@ const MenuItem = React.forwardRef(function MenuItem(props, ref) { data-flattenedposition={ flattenedPosition } onKeyDown={ onKeyDown } onClick={ onClick } - onMouseEnter={ onMouseEnter } + onMouseOver={ onMouseOver } onMouseLeave={ onMouseLeave } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } @@ -32,7 +32,7 @@ MenuItem.propTypes = { flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, + onMouseOver: PropTypes.func.isRequired, onMouseLeave: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, diff --git a/src/Menu/MenuItemCheckbox.jsx b/src/Menu/MenuItemCheckbox.jsx index 3312be56..4e9f07b8 100644 --- a/src/Menu/MenuItemCheckbox.jsx +++ b/src/Menu/MenuItemCheckbox.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox(props, ref) { const { children, position, flattenedPosition, - onKeyDown, onClick, onMouseEnter, onMouseLeave, + onKeyDown, onClick, onMouseOver, onMouseLeave, isDisabled, isTabbable, isChecked } = props; @@ -15,7 +15,7 @@ const MenuItemCheckbox = React.forwardRef(function MenuItemCheckbox(props, ref) data-flattenedposition={ flattenedPosition } onKeyDown={ onKeyDown } onClick={ onClick } - onMouseEnter={ onMouseEnter } + onMouseOver={ onMouseOver } onMouseLeave={ onMouseLeave } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } @@ -33,7 +33,7 @@ MenuItemCheckbox.propTypes = { flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, + onMouseOver: PropTypes.func.isRequired, onMouseLeave: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, diff --git a/src/Menu/MenuItemRadio.jsx b/src/Menu/MenuItemRadio.jsx index ee1c243c..13e85ce6 100644 --- a/src/Menu/MenuItemRadio.jsx +++ b/src/Menu/MenuItemRadio.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; const MenuItemRadio = React.forwardRef(function MenuItemRadio(props, ref) { const { children, position, flattenedPosition, - onKeyDown, onClick, onMouseEnter, onMouseLeave, + onKeyDown, onClick, onMouseOver, onMouseLeave, isDisabled, isTabbable, isChecked, ...rest } = props; @@ -17,7 +17,7 @@ const MenuItemRadio = React.forwardRef(function MenuItemRadio(props, ref) { data-flattenedposition={ flattenedPosition } onKeyDown={ onKeyDown } onClick={ onClick } - onMouseEnter={ onMouseEnter } + onMouseOver={ onMouseOver } onMouseLeave={ onMouseLeave } aria-disabled={ isDisabled } tabIndex={ isTabbable ? '0' : '-1' } @@ -36,7 +36,7 @@ MenuItemRadio.propTypes = { flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, + onMouseOver: PropTypes.func.isRequired, onMouseLeave: PropTypes.func.isRequired, isDisabled: PropTypes.bool, isTabbable: PropTypes.bool, diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 271b07c1..55a1138d 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -19,7 +19,7 @@ class ParentMenuItem extends React.Component { flattenedPosition: PropTypes.arrayOf(PropTypes.number).isRequired, onKeyDown: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, - onMouseEnter: PropTypes.func.isRequired, + onMouseOver: PropTypes.func.isRequired, onMouseLeave: PropTypes.func.isRequired, collapse: PropTypes.func.isRequired, focusPrevRootItem: PropTypes.func, @@ -252,7 +252,23 @@ class ParentMenuItem extends React.Component { collapse(true); }; - onChildEnter = () => { + onChildOver = () => { + const { items, expandedIndex, expandItem, focusItem } = this.props; + const { target } = event; + + console.log(target); + + const position = target.dataset.position.split(','); + const flattenedPosition = target.dataset.flattenedposition.split(','); + const index = Number.parseInt(position[position.length - 1], 10); + const flattenedIndex = Number.parseInt(flattenedPosition[flattenedPosition.length - 1], 10); + const item = items[index]; + const { type } = item; + + focusItem(flattenedIndex); + + if(type === 'menu' && expandedIndex !== -1) + expandItem(flattenedIndex); }; onChildLeave = () => { @@ -262,7 +278,7 @@ class ParentMenuItem extends React.Component { render() { const { children, items, position, flattenedPosition, - onKeyDown, onClick, onMouseEnter, onMouseLeave, + onKeyDown, onClick, onMouseOver, onMouseLeave, focusPrevRootItem, focusNextRootItem, focusRootItem, orientation, label, labelId, isExpanded, isDisabled, isTabbable, setManagerRef, setItemRef, expandedIndex, collapseItem, @@ -279,7 +295,7 @@ class ParentMenuItem extends React.Component { flattenedPosition, onChildKeyDown: this.onChildKeyDown, onChildClick: this.onChildClick, - onChildEnter: this.onChildEnter, + onChildOver: this.onChildOver, onChildLeave: this.onChildLeave, }); @@ -293,7 +309,7 @@ class ParentMenuItem extends React.Component { data-flattenedposition={ flattenedPosition } onKeyDown={ onKeyDown } onClick={ onClick } - onMouseEnter={ onMouseEnter } + onMouseOver={ onMouseOver } onMouseLeave={ onMouseLeave } aria-expanded={ isExpanded } aria-disabled={ isDisabled } diff --git a/src/Menu/utils.jsx b/src/Menu/utils.jsx index dc645c62..ac523278 100644 --- a/src/Menu/utils.jsx +++ b/src/Menu/utils.jsx @@ -24,7 +24,7 @@ import MenuItemRadio from 'src/Menu/MenuItemRadio'; * @param {number[]} options.flattenedPosition * @param {function} options.onChildKeyDown * @param {function} options.onChildClick - * @param {function} options.onChildEnter + * @param {function} options.onChildOver * @param {function} options.onChildLeave * @returns {React.Component[]} */ @@ -34,7 +34,7 @@ export function renderItems(options) { const { items, setItemRef, tabbableIndex, expandedIndex, collapseItem, focusRootItem, focusPrevRootItem, focusNextRootItem, position, flattenedPosition, - onChildKeyDown, onChildClick, onChildEnter, onChildLeave + onChildKeyDown, onChildClick, onChildOver, onChildLeave } = options; const itemNodes = []; const level = position.length; @@ -58,7 +58,7 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } - onMouseEnter={ onChildEnter } + onMouseOver={ onChildOver } onMouseLeave={ onChildLeave } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } @@ -84,7 +84,7 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } - onMouseEnter={ onChildEnter } + onMouseOver={ onChildOver } onMouseLeave={ onChildLeave } collapse={ collapseItem } focusPrevRootItem={ focusPrevRootItem } @@ -117,7 +117,7 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } - onMouseEnter={ onChildEnter } + onMouseOver={ onChildOver } onMouseLeave={ onChildLeave } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } @@ -155,7 +155,7 @@ export function renderItems(options) { flattenedPosition={ _flattenedPosition } onKeyDown={ onChildKeyDown } onClick={ onChildClick } - onMouseEnter={ onChildEnter } + onMouseOver={ onChildOver } onMouseLeave={ onChildLeave } isDisabled={ isDisabled } isTabbable={ flattenedIndex === tabbableIndex } From 9bb919f9d036b8154c8e2d4b0087833a0f1e06fb Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 14:44:04 -0500 Subject: [PATCH 285/286] submenus can be opened automatically on hover without checking --- src/Menu/MenuButton.jsx | 2 +- src/Menu/ParentMenuItem.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Menu/MenuButton.jsx b/src/Menu/MenuButton.jsx index ef1ce3a1..e53c9e45 100644 --- a/src/Menu/MenuButton.jsx +++ b/src/Menu/MenuButton.jsx @@ -280,7 +280,7 @@ class MenuButton extends React.Component { focusItem(flattenedIndex); - if(type === 'menu' && expandedIndex !== -1) + if(type === 'menu') expandItem(flattenedIndex); }; diff --git a/src/Menu/ParentMenuItem.jsx b/src/Menu/ParentMenuItem.jsx index 55a1138d..091eca85 100644 --- a/src/Menu/ParentMenuItem.jsx +++ b/src/Menu/ParentMenuItem.jsx @@ -267,7 +267,7 @@ class ParentMenuItem extends React.Component { focusItem(flattenedIndex); - if(type === 'menu' && expandedIndex !== -1) + if(type === 'menu') expandItem(flattenedIndex); }; From 779b8d3fe0ff2e27f86e82b86e78f8f6f3dda11e Mon Sep 17 00:00:00 2001 From: Charlie Yao Date: Tue, 8 Feb 2022 14:53:55 -0500 Subject: [PATCH 286/286] only focus on hover if the menubar was already expanded --- src/Menu/MenuBar.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Menu/MenuBar.jsx b/src/Menu/MenuBar.jsx index fdf64a1c..66831258 100644 --- a/src/Menu/MenuBar.jsx +++ b/src/Menu/MenuBar.jsx @@ -209,9 +209,12 @@ class MenuBar extends React.Component { const item = items[index]; const { type } = item; + if(expandedIndex === -1) + return; + focusItem(flattenedIndex); - if(type === 'menu' && expandedIndex !== -1) + if(type === 'menu') expandItem(flattenedIndex); };