MathJax icon indicating copy to clipboard operation
MathJax copied to clipboard

Help Dialog is not modal

Open salbeira opened this issue 6 months ago • 2 comments

Issue Summary

When opening the help dialog with H or by clicking the ( i ), it is neither focused nor brought in front of the entire document. If anything has manually adjusted z-indices it hovers in front of the dialog. I am unable to click, scroll or interact with it because other absolutely positioned elements stay in front of the dialog, eating events.

Steps to Reproduce:

  1. Open the help dialog in something like a Reveal.js slide container that has a high z-index.
  2. Despair.

Technical details:

  • MathJax Version: 4.0
  • Client OS: Linux
  • Browser: Firefox & Chrome

I am using the following MathJax configuration:

Too much custom adjustments that are probably not relevant to the issue.

Supporting information:

The issue was solvable by inserting this into my CSS:

        mjx-help-background {
            z-index: 128;
        }

Though I suggest to not do that as a solution but to make the help dialog an actual dialog element and bring it front and center with dialog.showModal();

Image

salbeira avatar Aug 05 '25 14:08 salbeira

You are right, it would be good to use a dialog element for this. Actually, I was not aware of this element, or would have used it. I will need to make a PR for it. There are other dialogs that would also benefit from this treatment.

dpvc avatar Aug 05 '25 14:08 dpvc

I have made a PR to resolve the issue. Since <dialog> is supported in some browsers for only a few years, I've included code that uses the current method if the browser doesn't implement <dialog>.

In the meantime, you can use the following configuration to get the new functionality.

MathJax = {
  startup: {
    ready() {
      MathJax.startup.defaultReady();
      const {SpeechExplorer} = MathJax._.a11y.explorer.KeyExplorer;
      const styles = MathJax.startup.document.styles[0];
      styles['mjx-help-sizer'].width = '45%';
      Object.assign(styles, {
        'mjx-help-sizer > .mjx-help-dialog': {
          position: 'absolute',
          width: '200%',
          left: '-100%',
          'max-width': 'initial',
        },
        '.mjx-help-dialog': {
          'max-width': 'calc(min(60em, 90%))',
          border: '3px outset',
          'border-radius': '15px',
          color: 'black',
          'background-color': '#DDDDDD',
          'box-shadow': '0px 10px 20px #808080',
          'text-align': 'right',
        },
        '.mjx-help-dialog::backdrop': {
          opacity: .75,
        },
        'mjx-help-dialog': {
          'font-style': 'normal',
          'text-indent': 0,
          'text-transform': 'none',
          'line-height': 'normal',
          'letter-spacing': 'normal',
          'word-spacing': 'normal',
          'word-wrap': 'normal',
          float: 'none',
        },
      });
      const help = SpeechExplorer.prototype.help;
      let message = '';
      MathJax.startup.promise.then(() => {
        help.call({
          node: document.body,
          stopEvent() {},
          document: MathJax.startup.document
        });
        message = document.querySelector('mjx-help-dialog > div').innerHTML;
        document.body.lastChild.remove();
      });
      SpeechExplorer.prototype.help = function help() {
        const isDialog = !!window.HTMLDialogElement;
        const adaptor = this.document.adaptor;
        const helpBackground = isDialog ? null : adaptor.node('mjx-help-background');
        const close = (event) => {
          if (isDialog) {
            helpDialog.close();
            helpDialog.remove();
          } else {
            helpBackground.remove();
          }
          this.node.focus();
          this.stopEvent(event);
        };
        const helpDialog = adaptor.node('dialog', {closedby: 'any', class: 'mjx-help-dialog'}, [
        adaptor.node(
          'mjx-help-dialog',
            { role: 'dialog', 'aria-labeledby': 'mjx-help-label' },
            [
              adaptor.node('h1', { id: 'mjx-help-label' }, [
                adaptor.text('MathJax Expression Explorer Help'),
              ]),
              adaptor.node('div'),
              adaptor.node('input', { type: 'button', value: 'Close' }),
            ]
          )
        ]);
        const help = helpDialog.firstChild;
        help.childNodes[1].innerHTML = message;
        help.lastChild.addEventListener('click', close);
        helpDialog.addEventListener('keydown', (event) => {
          if (event.code === 'Escape') {
            close(event);
          }
        });
        if (isDialog) {
          document.body.append(helpDialog);
          helpDialog.showModal();
        } else {
          helpDialog.setAttribute('tabindex', '0');
          help.addEventListener('click', (event) => this.stopEvent(event));
          helpBackground.addEventListener('click', close);
          const helpSizer = adaptor.node('mjx-help-sizer', {}, [helpDialog]);
          helpBackground.append(helpSizer);
          document.body.append(helpBackground);
        }
        helpDialog.focus();
      };
    }
  }
}

This has to do a little bit of hackery to get the actual dialog content, since that is not available directly. (Here, we open the dialog temporarily and grab the contents so that we can use it later when we create the modern version.)

dpvc avatar Aug 08 '25 12:08 dpvc