diff --git a/components/mjs/core/config.json b/components/mjs/core/config.json
index 7f032dbc4..87e95dc00 100644
--- a/components/mjs/core/config.json
+++ b/components/mjs/core/config.json
@@ -4,6 +4,8 @@
     "targets": [
       "mathjax.ts",
       "core", "util", "handlers",
+      "ui/dialog/DraggableDialog.ts",
+      "ui/dialog/InfoDialog.ts",
       "adaptors/HTMLAdaptor.ts",
       "adaptors/browserAdaptor.ts",
       "components/global.ts"
diff --git a/ts/a11y/explorer.ts b/ts/a11y/explorer.ts
index 7964807c0..8df549bc6 100644
--- a/ts/a11y/explorer.ts
+++ b/ts/a11y/explorer.ts
@@ -321,6 +321,7 @@ export function ExplorerMathDocumentMixin<
     public static OPTIONS: OptionList = {
       ...BaseDocument.OPTIONS,
       enableExplorer: hasWindow,           // only activate in interactive contexts
+      enableExplorerHelp: true,            // help dialog is enabled
       renderActions: expandable({
         ...BaseDocument.OPTIONS.renderActions,
         explorable: [STATE.EXPLORER]
@@ -417,78 +418,6 @@ export function ExplorerMathDocumentMixin<
         display: 'inline-flex',
         'align-items': 'center',
       },
-
-      'mjx-help-sizer': {
-        position: 'fixed',
-        width: '40%',
-        'max-width': '30em',
-        top: '3em',
-        left: '50%',
-      },
-      'mjx-help-dialog': {
-        position: 'absolute',
-        width: '200%',
-        left: '-100%',
-        border: '3px outset',
-        'border-radius': '15px',
-        color: 'black',
-        'background-color': '#DDDDDD',
-        'z-index': '301',
-        'text-align': 'right',
-        'font-style': 'normal',
-        'text-indent': 0,
-        'text-transform': 'none',
-        'line-height': 'normal',
-        'letter-spacing': 'normal',
-        'word-spacing': 'normal',
-        'word-wrap': 'normal',
-        float: 'none',
-        'box-shadow': '0px 10px 20px #808080',
-        outline: 'none',
-      },
-      'mjx-help-dialog > h1': {
-        'font-size': '24px',
-        'text-align': 'center',
-        margin: '.5em 0',
-      },
-      'mjx-help-dialog > div': {
-        margin: '0 1em',
-        padding: '3px',
-        overflow: 'auto',
-        height: '20em',
-        border: '2px inset black',
-        'background-color': 'white',
-        'text-align': 'left',
-      },
-      'mjx-help-dialog > input': {
-        margin: '.5em 2em',
-      },
-      'mjx-help-dialog kbd': {
-        display: 'inline-block',
-        padding: '3px 5px',
-        'font-size': '11px',
-        'line-height': '10px',
-        color: '#444d56',
-        'vertical-align': 'middle',
-        'background-color': '#fafbfc',
-        border: 'solid 1.5px #c6cbd1',
-        'border-bottom-color': '#959da5',
-        'border-radius': '3px',
-        'box-shadow': 'inset -.5px -1px 0 #959da5',
-      },
-      'mjx-help-dialog ul': {
-        'list-style-type': 'none',
-      },
-      'mjx-help-dialog li': {
-        'margin-bottom': '.5em',
-      },
-      'mjx-help-background': {
-        position: 'fixed',
-        top: 0,
-        left: 0,
-        right: 0,
-        bottom: 0,
-      },
     };
 
     /**
@@ -556,7 +485,7 @@ export function ExplorerMathDocumentMixin<
           SVGNS
         ),
       ]);
-      this.tmpFocus = this.adaptor.node('mjx-focus', {
+      this.tmpFocus = adaptor.node('mjx-focus', {
         tabIndex: 0,
         style: {
           outline: 'none',
diff --git a/ts/a11y/explorer/KeyExplorer.ts b/ts/a11y/explorer/KeyExplorer.ts
index 943def94d..e333db800 100644
--- a/ts/a11y/explorer/KeyExplorer.ts
+++ b/ts/a11y/explorer/KeyExplorer.ts
@@ -30,6 +30,9 @@ import { MmlNode } from '../../core/MmlTree/MmlNode.js';
 import { honk, SemAttr } from '../speech/SpeechUtil.js';
 import { GeneratorPool } from '../speech/GeneratorPool.js';
 import { context } from '../../util/context.js';
+import { InfoDialog } from '../../ui/dialog/InfoDialog.js';
+
+/**********************************************************************/
 
 /**
  * Interface for keyboard explorers. Adds the necessary keyboard events.
@@ -70,7 +73,7 @@ export interface KeyExplorer extends Explorer {
 /**
  * Type of function that implements a key press action
  */
-type keyMapping = (
+export type keyMapping = (
   explorer: SpeechExplorer,
   event: KeyboardEvent
 ) => boolean | void;
@@ -106,149 +109,162 @@ export function hasModifiers(
   );
 }
 
+/**********************************************************************/
 /**********************************************************************/
 
 /**
- * Creates a customized help dialog
+ * @class
+ * @augments {AbstractExplorer}
  *
- * @param {string} title   The title to use for the message
- * @param {string} select  Additional ways to select the typeset math
- * @returns {string}       The customized message
+ * @template T  The type that is consumed by the Region of this explorer.
  */
-function helpMessage(title: string, select: string): string {
-  return `
-
Exploring expressions ${title}
+export class SpeechExplorer
+  extends AbstractExplorer
+  implements KeyExplorer
+{
+  /**
+   * Creates a customized help dialog
+   *
+   * @param {string} title   The title to use for the message
+   * @param {string} select  Additional ways to select the typeset math
+   * @returns {string}       The customized message
+   */
+  protected static helpMessage(title: string, select: string): string {
+    return `
+      Exploring expressions ${title}
 
-The mathematics on this page is being rendered by MathJax, which
-generates both the text spoken by screen readers, as well as the
-visual layout for sighted users.
+      The mathematics on this page is being rendered by MathJax, which
+      generates both the text spoken by screen readers, as well as the
+      visual layout for sighted users.
 
-Expressions typeset by MathJax can be explored interactively, and
-are focusable.  You can use the Tab key to move to a typeset
-expression${select}.  Initially, the expression will be read in full,
-but you can use the following keys to explore the expression
-further:
+      
Expressions typeset by MathJax can be explored interactively, and
+      are focusable.  You can use the Tab key to move to a typeset
+      expression${select}.  Initially, the expression will be read in full,
+      but you can use the following keys to explore the expression
+      further:
 
-
+      
 
-- Down Arrow moves one level deeper into the expression to
-allow you to explore the current subexpression term by term.+
- Down Arrow moves one level deeper into the
+      expression to allow you to explore the current subexpression term by
+      term.-
- Up Arrow moves back up a level within the expression.+
- Up Arrow moves back up a level within the
+      expression.-
- Right Arrow moves to the next term in the current
-subexpression.+
- Right Arrow moves to the next term in the
+      current subexpression.-
- Left Arrow moves to the next term in the current
-subexpression.+
- Left Arrow moves to the next term in the
+      current subexpression.-
- Shift+Arrow moves to a neighboring cell within a table.
+      
- Shift+Arrow moves to a
+      neighboring cell within a table.-
- 0-9+0-9 jumps to a cell by its index in the table, where 0 = 10.
+      
- 0-9+0-9 jumps to a cell
+      by its index in the table, where 0 = 10.-
- Home takes you to the top of the expression.+
- Home takes you to the top of the
+      expression.-
- Enter or Return clicks a link or activates an active
-subexpression.+
- Enter or Return clicks a
+      link or activates an active subexpression.-
- Space opens the MathJax contextual menu where you can view
-or copy the source format of the expression, or modify MathJax's
-settings.+
- Space opens the MathJax contextual menu
+      where you can view or copy the source format of the expression, or
+      modify MathJax's settings.-
- Escape exits the expression explorer.+
- Escape exits the expression
+      explorer.-
- x gives a summary of the current subexpression.+
- x gives a summary of the current
+      subexpression.-
- z gives the full text of a collapsed expression.+
- z gives the full text of a collapsed
+      expression.-
- d gives the current depth within the expression.+
- d gives the current depth within the
+      expression.-
- s starts or stops auto-voicing with synchronized highlighting.+
- s starts or stops auto-voicing with
+      synchronized highlighting.-
- v marks the current position in the expression.+
- v marks the current position in the
+      expression.-
- p cycles through the marked positions in the expression.+
- p cycles through the marked positions in
+      the expression.-
- u clears all marked positions and returns to the starting position.+
- u clears all marked positions and returns
+      to the starting position.-
- > cycles through the available speech rule sets
-(MathSpeak, ClearSpeak).+
- > cycles through the available speech
+      rule sets (MathSpeak, ClearSpeak).-
- < cycles through the verbosity levels for the current
-rule set.+
- < cycles through the verbosity levels
+      for the current rule set.-
- h produces this help listing.-
+- h produces this help listing.+
-The MathJax contextual menu allows you to enable or disable speech
-or Braille generation for mathematical expressions, the language to
-use for the spoken mathematics, and other features of MathJax.  In
-particular, the Explorer submenu allows you to specify how the
-mathematics should be identified in the page (e.g., by saying "math"
-when the expression is spoken), and whether or not to include a
-message about the letter "h" bringing up this dialog box.
+      The MathJax contextual menu allows you to enable or disable speech
+      or Braille generation for mathematical expressions, the language to
+      use for the spoken mathematics, and other features of MathJax.  In
+      particular, the Explorer submenu allows you to specify how the
+      mathematics should be identified in the page (e.g., by saying "math"
+      when the expression is spoken), and whether or not to include a
+      message about the letter "h" bringing up this dialog box.  Turning off
+      speech and Braille will disable the expression explorer, its
+      highlighting, and its help icon.
 
-The contextual menu also provides options for viewing or copying a
-MathML version of the expression or its original source format,
-creating an SVG version of the expression, and viewing various other
-information.
+      The contextual menu also provides options for viewing or copying a
+      MathML version of the expression or its original source format,
+      creating an SVG version of the expression, and viewing various other
+      information.
 
-For more help, see the MathJax accessibility documentation.
-`;
-}
+      For more help, see the MathJax accessibility documentation.
+    `;
+  }
 
-/**
- * Help for the different OS versions
- */
-const helpData: Map = new Map([
-  [
-    'MacOS',
+  /**
+   * Help for the different OS versions
+   */
+  protected static helpData: Map = new Map([
     [
-      'on MacOS and iOS using VoiceOver',
-      ', or the VoiceOver arrow keys to select an expression',
+      'MacOS',
+      [
+        'on MacOS and iOS using VoiceOver',
+        ', or the VoiceOver arrow keys to select an expression',
+      ],
     ],
-  ],
-  [
-    'Windows',
     [
-      'in Windows using NVDA or JAWS',
-      `. The screen reader should enter focus or forms mode automatically
-when the expression gets the browser focus, but if not, you can toggle
-focus mode using NVDA+space in NVDA; for JAWS, Enter should start
-forms mode while Numpad Plus leaves it.  Also note that you can use
-the NVDA or JAWS key plus the arrow keys to explore the expression
-even in browse mode, and you can use NVDA+shift+arrow keys to
-navigate out of an expression that has the focus in NVDA`,
+      'Windows',
+      [
+        'in Windows using NVDA or JAWS',
+        `. The screen reader should enter focus or forms mode automatically
+        when the expression gets the browser focus, but if not, you can toggle
+        focus mode using NVDA+space in NVDA; for JAWS, Enter should start
+        forms mode while Numpad Plus leaves it.  Also note that you can use
+        the NVDA or JAWS key plus the arrow keys to explore the expression
+        even in browse mode, and you can use NVDA+shift+arrow keys to
+        navigate out of an expression that has the focus in NVDA`,
+      ],
     ],
-  ],
-  [
-    'Unix',
     [
-      'in Unix using Orca',
-      `, and Orca should enter focus mode automatically.  If not, use the
-Orca+a key to toggle focus mode on or off.  Also note that you can use
-Orca+arrow keys to explore expressions even in browse mode`,
+      'Unix',
+      [
+        'in Unix using Orca',
+        `, and Orca should enter focus mode automatically.  If not, use the
+        Orca+a key to toggle focus mode on or off.  Also note that you can use
+        Orca+arrow keys to explore expressions even in browse mode`,
+      ],
     ],
-  ],
-  ['unknown', ['with a Screen Reader.', '']],
-]);
-
-/**********************************************************************/
-/**********************************************************************/
+    ['unknown', ['with a Screen Reader.', '']],
+  ]);
 
-/**
- * @class
- * @augments {AbstractExplorer}
- *
- * @template T  The type that is consumed by the Region of this explorer.
- */
-export class SpeechExplorer
-  extends AbstractExplorer
-  implements KeyExplorer
-{
   /*
    * The explorer key mapping
    */
@@ -605,8 +621,13 @@ export class SpeechExplorer
 
   /**
    * Open the help dialog, and refocus when it closes.
+   *
+   * @returns {boolean | void}  True cancels the event
    */
-  protected hKey() {
+  protected hKey(): boolean | void {
+    if (!this.document.options.enableExplorerHelp) {
+      return true;
+    }
     this.refocus = this.current;
     this.help();
   }
@@ -933,40 +954,36 @@ export class SpeechExplorer
    * Displays the help dialog.
    */
   protected help() {
-    const adaptor = this.document.adaptor;
-    const helpBackground = adaptor.node('mjx-help-background');
-    const close = (event: Event) => {
-      helpBackground.remove();
-      this.node.focus();
-      this.stopEvent(event);
-    };
-    helpBackground.addEventListener('click', close);
-    const helpSizer = adaptor.node('mjx-help-sizer', {}, [
-      adaptor.node(
-        'mjx-help-dialog',
-        { tabindex: 0, 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' }),
-        ]
-      ),
-    ]);
-    helpBackground.append(helpSizer);
-    const help = helpSizer.firstChild as HTMLElement;
-    help.addEventListener('click', (event) => this.stopEvent(event));
-    help.lastChild.addEventListener('click', close);
-    help.addEventListener('keydown', (event: KeyboardEvent) => {
-      if (event.code === 'Escape') {
-        close(event);
-      }
+    if (!this.document.options.enableExplorerHelp) {
+      return;
+    }
+    const CLASS = this.constructor as typeof SpeechExplorer;
+    const [title, select] = CLASS.helpData.get(context.os);
+    InfoDialog.post({
+      title: 'MathJax Expression Explorer Help',
+      message: CLASS.helpMessage(title, select),
+      node: this.node,
+      adaptor: this.document.adaptor,
+      styles: {
+        '.mjx-dialog': {
+          'max-height': 'calc(min(35em, 90%))',
+        },
+        'mjx-dialog mjx-title': {
+          'font-size': '133%',
+          margin: '.5em 1.75em',
+        },
+        'mjx-dialog h2': {
+          'font-size': '20px',
+          margin: '.5em 0',
+        },
+        'mjx-dialog ul': {
+          'list-style-type': 'none',
+        },
+        'mjx-dialog li': {
+          'margin-bottom': '.5em',
+        },
+      },
     });
-    const [title, select] = helpData.get(context.os);
-    (help.childNodes[1] as HTMLElement).innerHTML = helpMessage(title, select);
-    document.body.append(helpBackground);
-    help.focus();
   }
 
   /********************************************************************/
@@ -1062,7 +1079,10 @@ export class SpeechExplorer
     if (describe) {
       let description =
         this.description === this.none ? '' : ', ' + this.description;
-      if (this.document.options.a11y.help) {
+      if (
+        this.document.options.a11y.help &&
+        this.document.options.enableExplorerHelp
+      ) {
         description += ', press h for help';
       }
       speech += description;
@@ -1538,7 +1558,9 @@ export class SpeechExplorer
     // and add the info icon.
     //
     this.node.classList.add('mjx-explorer-active');
-    this.node.append(this.document.infoIcon);
+    if (this.document.options.enableExplorerHelp) {
+      this.node.append(this.document.infoIcon);
+    }
     //
     // Get the node to make current, and determine if we need to add a
     // speech node (or just use the top-level node), then set the
@@ -1574,7 +1596,9 @@ export class SpeechExplorer
         this.node.setAttribute('aria-roledescription', description);
       }
       this.node.classList.remove('mjx-explorer-active');
-      this.document.infoIcon.remove();
+      if (this.document.options.enableExplorerHelp) {
+        this.document.infoIcon.remove();
+      }
       this.pool.unhighlight();
       this.magnifyRegion.Hide();
       this.region.Hide();
diff --git a/ts/a11y/speech/SpeechMenu.ts b/ts/a11y/speech/SpeechMenu.ts
index 97663197d..e76a927cc 100644
--- a/ts/a11y/speech/SpeechMenu.ts
+++ b/ts/a11y/speech/SpeechMenu.ts
@@ -22,7 +22,12 @@
  */
 
 import { ExplorerMathItem } from '../explorer.js';
-import { MJContextMenu } from '../../ui/menu/MJContextMenu.js';
+import { MJContextMenu, SubmenuCallback } from '../../ui/menu/MJContextMenu.js';
+import {
+  SelectionDialog,
+  SelectionOrder,
+  SelectionGrid,
+} from '../../ui/dialog/SelectionDialog.js';
 import { SubMenu, Submenu } from '../../ui/menu/mj-context-menu.js';
 import * as Sre from '../sre.js';
 
@@ -113,7 +118,7 @@ let counter = 0;
 function csSelectionBox(menu: MJContextMenu, locale: string): object {
   const props = localePreferences.get(locale);
   csPrefsVariables(menu, Object.keys(props));
-  const items = [];
+  const items: any[] = [];
   for (const prop of Object.getOwnPropertyNames(props)) {
     items.push({
       title: prop,
@@ -121,22 +126,19 @@ function csSelectionBox(menu: MJContextMenu, locale: string): object {
       variable: 'csprf_' + prop,
     });
   }
-  const sb = menu.factory.get('selectionBox')(
-    menu.factory,
-    {
-      title: 'Clearspeak Preferences',
-      signature: '',
-      order: 'alphabetic',
-      grid: 'square',
-      selections: items,
-    },
+  const sb = new SelectionDialog(
+    'Clearspeak Preferences',
+    '',
+    items,
+    SelectionOrder.ALPHABETICAL,
+    SelectionGrid.SQUARE,
     menu
   );
   return {
     type: 'command',
     id: 'ClearspeakPreferences',
     content: 'Select Preferences',
-    action: () => sb.post(0, 0),
+    action: () => sb.post(),
   };
 }
 
@@ -223,13 +225,13 @@ function smartPreferences(
  *
  * @param {MJContextMenu} menu The context menu.
  * @param {Submenu} sub The submenu to attach elements to.
- * @param {(sub: SubMenu) => void} callback Callback to apply on the constructed
+ * @param {SubmenuCallback} callback Callback to apply on the constructed
  *   submenu.
  */
 export async function clearspeakMenu(
   menu: MJContextMenu,
   sub: Submenu,
-  callback: (sub: SubMenu) => void
+  callback: SubmenuCallback
 ) {
   const exit = (items: object[]) => {
     callback(
@@ -288,13 +290,13 @@ let LOCALE_MENU: SubMenu = null;
  *
  * @param {MJContextMenu} menu The context menu.
  * @param {Submenu} sub The submenu to attach elements to.
- * @param {(sub: SubMenu) => void} callback Callback to apply on the constructed
+ * @param {SubmenuCallback} callback Callback to apply on the constructed
  *   submenu.
  */
 export function localeMenu(
   menu: MJContextMenu,
   sub: Submenu,
-  callback: (sub: SubMenu) => void
+  callback: SubmenuCallback
 ) {
   if (LOCALE_MENU) {
     callback(LOCALE_MENU);
diff --git a/ts/ui/dialog/CopyDialog.ts b/ts/ui/dialog/CopyDialog.ts
new file mode 100644
index 000000000..d31caebc3
--- /dev/null
+++ b/ts/ui/dialog/CopyDialog.ts
@@ -0,0 +1,77 @@
+/*************************************************************
+ *
+ *  Copyright (c) 2025 The MathJax Consortium
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+/**
+ * @file  Implements the CopyDialog class (InfoDialog with copy button).
+ *
+ * @author dpvc@mathjax.org (Davide Cervone)
+ */
+
+import { InfoDialog, InfoDialogArgs } from './InfoDialog.js';
+
+/**
+ * The args for a CopyDialog
+ */
+export type CopyDialogArgs = InfoDialogArgs & { code?: boolean };
+
+/**
+ * The CopyDialog subclass of InfoDialog
+ */
+export class CopyDialog extends InfoDialog {
+  /**
+   * @override
+   */
+  public static post(args: CopyDialogArgs) {
+    return super.post(args);
+  }
+
+  /**
+   * @override
+   */
+  protected html(args: CopyDialogArgs) {
+    //
+    // Add a copy-to-clipboard button
+    //
+    args.extraNodes ??= [];
+    const copy = args.adaptor.node('input', {
+      type: 'button',
+      value: 'Copy to Clipboard',
+      'data-drag': 'none',
+    });
+    copy.addEventListener('click', this.copyToClipboard.bind(this));
+    args.extraNodes.push(copy);
+    //
+    // If this is a code dialog, format the source and set in a pre element
+    //
+    if (args.code) {
+      args.message = '' + this.formatSource(args.message) + '
';
+    }
+    return super.html(args);
+  }
+
+  /**
+   * @param {string} text   The text to be displayed in the Info box
+   * @returns {string}      The text with HTML specials being escaped
+   */
+  protected formatSource(text: string): string {
+    return text
+      .trim()
+      .replace(/&/g, '&')
+      .replace(//g, '>');
+  }
+}
diff --git a/ts/ui/dialog/DraggableDialog.ts b/ts/ui/dialog/DraggableDialog.ts
new file mode 100644
index 000000000..6575376e9
--- /dev/null
+++ b/ts/ui/dialog/DraggableDialog.ts
@@ -0,0 +1,1114 @@
+/*************************************************************
+ *
+ *  Copyright (c) 2025 The MathJax Consortium
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+/**
+ * @file  Implements a draggable dialog class.
+ *
+ * @author dpvc@mathjax.org (Davide Cervone)
+ */
+
+import { DOMAdaptor } from '../../core/DOMAdaptor.js';
+import { StyleJson, StyleJsonSheet } from '../../util/StyleJson.js';
+import { context } from '../../util/context.js';
+
+export type ADAPTOR = DOMAdaptor;
+
+export type Action = (
+  dialog: DraggableDialog,
+  event: MouseEvent
+) => void | number[];
+export type ActionList = { [action: string]: Action };
+export type ActionMap = { [type: string]: ActionList };
+
+/**
+ * The arguments that control the dialog contents
+ */
+export type DialogArgs = {
+  title?: string; //             // the dialog title HTML
+  message: string; //            // the dialog message HTML
+  adaptor: ADAPTOR; //           // the adaptor to use to create the dialog
+  node?: HTMLElement; //         // the node to focus when the dialog closes, if any
+  styles?: StyleJson; //         // extra styles to use for the dialog
+  extraNodes?: HTMLElement[]; // // extra HTML nodes to put at the bottom of the dialog
+  className?: string; //         // optional class to apply to the dialog
+};
+
+/**
+ * Type of function that implements a key press action
+ */
+export type keyMapping = (
+  dialog: DraggableDialog,
+  event: KeyboardEvent
+) => void;
+
+/**
+ * True if we can rely on an HTML dialog element.
+ */
+export const isDialog: boolean = !!context.window?.HTMLDialogElement;
+
+/*========================================================================*/
+
+/**
+ * The draggable dialog class
+ */
+export class DraggableDialog {
+  /**
+   * The minimum width of the dialog
+   */
+  protected minW = 200;
+  /**
+   * The maximum width of the dialog
+   */
+  protected minH = 80;
+
+  /**
+   * The current x translation of the dialog
+   */
+  protected tx: number = 0;
+  /**
+   * The current y translation of the dialog
+   */
+  protected ty: number = 0;
+
+  /**
+   * The current mouse x position
+   */
+  protected x: number;
+  /**
+   * The current mouse y position
+   */
+  protected y: number;
+  /**
+   * The current dialog width
+   */
+  protected w: number;
+  /**
+   * The current dialog height
+   */
+  protected h: number;
+  /**
+   * True when the dialog is being dragged or sized
+   */
+  protected dragging: boolean = false;
+  /**
+   * The drag acction being taken (move, left, right, top, bottom, etc.)
+   */
+  protected action: string;
+
+  /**
+   * Elements where clicking doesn't cause dragging
+   */
+  protected noDrag: HTMLElement[];
+  /**
+   * The title element
+   */
+  protected title: HTMLElement;
+  /**
+   * The content div element
+   */
+  protected content: HTMLElement;
+
+  /**
+   * The node to focus when dialog closes
+   */
+  protected node: HTMLElement;
+  /**
+   * The background element when