Blocklys Textblock input fields can not be edited when in a Material-UI Modal

I only found https://groups.google.com/g/blockly/c/SDUosMpAFAk to my problem, but it has no answers that could help me, so I created a Codesandbox to reproduce the behavior.

https://codesandbox.io/s/gallant-galois-bqjjb

The button in the Sandbox will open a modal with a Blockly Canvas in it. Trying to write something in the "text"- or "math_number"-Block does not work, and when you close the modal, with an outside click, some artifacts are staying.

I would be glad if someone can help me out with this.

EDIT: In case of the CodeSandbox link is not working.

Dependencies:

  • @material-ui/core: 4.12.3
  • @material-ui/styles: 4.11.4
  • blockly: 6.20210701.0 (6.20210701.0)
  • react: 17.0.2
  • react-dom: 17.0.2
  • react-scripts: 4.0.0
  • react-use: 17.3.1

CODE:

index.js

import { StrictMode } from "react";
import ReactDOM from "react-dom";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  rootElement
);

App.js

import React from "react";
import "./styles.css";
import { Modal } from "@material-ui/core";

import BlocklyContainer from "./BlocklyContainer";

export default function App() {
  const [blocklyModalOpenRule, setBlocklyModalOpenRule] = React.useState(false);
  const handleOpenBlocklyModal = () => setBlocklyModalOpenRule(true);
  const handleCloseBlocklyModal = () => setBlocklyModalOpenRule(false);

  return (
    <div className="App">
      <p>The button will open a modal with a Blockly Canvas in it.</p>
      <p>Trying to write something in the "text"- or "math_number"-Block</p>
      <p>does not work, and when you close the modal, with an outside click,</p>
      <p>some artefacts are staying</p>

      <button onClick={handleOpenBlocklyModal}>open</button>

      <Modal
        open={!!blocklyModalOpenRule}
        onClose={handleCloseBlocklyModal}
        aria-labelledby="blockly-modal-label"
        aria-describedby="blockly-modal-description"
        disableEnforceFocus
      >
        <div
          style={{
            top: `50%`,
            left: `50%`,
            transform: `translate(-50%, -50%)`,
            height: "90vh",
            width: "90vw",
            position: "absolute",
            background: "white"
          }}
        >
          <BlocklyContainer />
        </div>
      </Modal>
    </div>
  );
}

BlocklyContainer.js

import React, { useRef } from "react";
import BlocklyComponent, { Block, Category } from "./Blockly";
import { Grid } from "@material-ui/core";

import { useMeasure } from "react-use";

const initialXML = `
    <xml xmlns="http://www.w3.org/1999/xhtml">
      <block type="text" id=".mq~5Vo#Hz32wh/q98Sv" x="10" y="10">
      </block>
      <block type="math_number" id=".mq~5Vo#Hz32wh/q98Sv" x="10" y="40">
      </block>
    </xml>
`;

const BlocklyContainer = () => {
  const simpleWorkspace = useRef();
  const [ref, { height }] = useMeasure();

  return (
    <div
      ref={ref}
      style={{
        flexGrow: 0,
        maxWidth: "100%",
        flexBasis: "100%",
        margin: 0,
        boxSizing: "border-box",
        minHeight: 650,
        height: "100%",
        maxHeight: 700
      }}
    >
      <Grid item md={12} style={{ height }}>
        <div
          style={{
            position: "relative",
            width: "100%",
            height: "100%",
            zIndex: 1400
          }}
        >
          <BlocklyComponent
            ref={(ref) => (simpleWorkspace.current = ref)}
            readOnly={false}
            trashcan={true}
            media="media/"
            move={{
              scrollbars: true,
              drag: true,
              wheel: true
            }}
            initialXml={initialXML}
          >
            <Category name="Control" colour="210">
              <Block type="controls_if" />
              <Block type="controls_ifelse" />
              <Block type="logic_compare" />
              <Block type="logic_boolean" />
              <Block type="logic_negate" />
              <Block type="logic_ternary" />
              <Block type="math_arithmetic" />
              <Block type="text" />
              <Block type="math_number" />
            </Category>
          </BlocklyComponent>
        </div>
      </Grid>
    </div>
  );
};

export default React.memo(BlocklyContainer);

Blockly/index.js

/**
 * @license
 *
 * Copyright 2019 Google LLC
 *
 * 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.
 */

/**
 * @fileoverview XML wrappers for block, category, value, field and shadow.
 * @author [email protected] (Sam El-Husseini)
 */

import React from "react";
import BlocklyComponent from "./BlocklyComponent";

export default BlocklyComponent;

const Block = (p) => {
  const { children, ...props } = p;
  props.is = "blockly";
  return React.createElement("block", props, children);
};

const Category = (p) => {
  const { children, ...props } = p;
  props.is = "blockly";
  return React.createElement("category", props, children);
};

const Value = (p) => {
  const { children, ...props } = p;
  props.is = "blockly";
  return React.createElement("value", props, children);
};

const Field = (p) => {
  const { children, ...props } = p;
  props.is = "blockly";
  return React.createElement("field", props, children);
};

const Shadow = (p) => {
  const { children, ...props } = p;
  props.is = "blockly";
  return React.createElement("shadow", props, children);
};

export { Block, Category, Value, Field, Shadow };

Blocky/BlocklyComponent.js

/**
 * @license
 *
 * Copyright 2019 Google LLC
 *
 * 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.
 */

/**
 * @fileoverview Blockly React Component.
 * @author [email protected] (Sam El-Husseini)
 */

import React from "react";
import "./BlocklyComponent.css";

import Blockly from "blockly/core";
import locale from "blockly/msg/en";
import "blockly/blocks";

Blockly.setLocale(locale);

class BlocklyComponent extends React.Component {
  blocklyDiv = React.createRef();
  toolbox = React.createRef();
  workspace;

  setWorkSpace(xml) {
    if (this.workspace) {
      const dom = Blockly.Xml.textToDom(xml);
      Blockly.Xml.domToWorkspace(dom, this.workspace);
    }
  }

  componentDidMount() {
    const { initialXml, children, ...rest } = this.props;
    this.workspace = Blockly.inject(this.blocklyDiv.current, {
      toolbox: this.toolbox.current,
      ...rest
    });

    if (initialXml) {
      this.setWorkSpace(initialXml);
    }
  }

  // setXml(xml: string) {
  //     this.setWorkSpace(xml)
  // }

  render() {
    const { children } = this.props;

    return (
      <React.Fragment>
        <div ref={this.blocklyDiv} id="blocklyDiv" />
        <xml
          xmlns="https://developers.google.com/blockly/xml"
          is="blockly"
          style={{ display: "none" }}
          ref={this.toolbox}
        >
          {children}
        </xml>
      </React.Fragment>
    );
  }
}

export default BlocklyComponent;

styles.css

body {
  background: greenyellow;
}

.App {
  font-family: sans-serif;
  text-align: center;
}

Blockly/BlocklyComponent.css

#blocklyDiv {
  height: 100%;
  width: 100%;
  position: absolute;
  bottom: 0;
}

.blocklySvg {
  height: 100%;
}

.blocklyFlyout {
  transform: translate(90px, 0px) !important;
  height: 100%;
}

.blocklyFlyoutBackground {
  height: 100%;
  transform: scaleY(99999999999);
}

.blocklyDropDownDiv {
  z-index: 9999999;
}

Solution 1:

You can set the property disableEnforceFocus to true, and that will solve the problem for the input text/number blocks. However the problem persists for blocks using selection elements (e.g. logic_compare, math_arithmetic).

<Modal
       ...
        disableEnforceFocus
>
....
</Modal>

To solve the second problem I changed the z-index for the blocklyDropDownDiv and now everything works perfectly.

.blocklyDropDownDiv {
    z-index: 5000;
}