1

I have a program that includes some nested custom elements. The leaves of one of these component's shadowRoot contains instances of an element like <div contentEditable>. In Chrome79 and Chromium-Edge-Beta the contentEditable feature works as one would expect it to - that is, the elements focus when you click or tab to them, show a focus outline, and are editable. In FireFox72 they behave erratically, mainly in that clicking on one will focus on it only some of the time, and that while they can be tabbed to, they do not focus such that they can be typed into.

After some whittling, I think I've arrived at a minimal reproduction. It is two custom elements: A root element ce-main and the leaf element ce-leaf that is instantiated arbitrarily many times from within ce-main and attached to ce-main's shadowRoot.

class Main extends HTMLElement {
    constructor() { super(); }

    connectedCallback() {
        this.attachShadow({mode: "open"});
        this.shadowRoot.innerHTML = `
        <style>
            [contentEditable] {
                min-height:2em;
                padding:.5em;
                border:1px dashed rgba(0,0,0,.0625);
            }
            [contentEditable]:empty::before {
                color: rgba(0,0,0,.15);
                content: "You should be able to focus and type here.";
                cursor:text;
            }
        </style>
        <div id="container" style=""></div>`;
        customElements.whenDefined("ce-leaf").then(
            () => this.constructFromSomeDataSource()
        );
    }

    constructFromSomeDataSource() {
        let rows = [];
        for (let i = 0; i < 4; i++) {
            let leaf = document.createElement("ce-leaf");
            this.shadowRoot.querySelector("#container").appendChild(leaf);
        }; 
    }
}

class Leaf extends HTMLElement {
    constructor() {
        super();        
    }

    connectedCallback() {
        this.innerHTML = `
            <div contentEditable></div>
        `;
    }
}

customElements.define("ce-main", Main);
customElements.define("ce-leaf", Leaf);
<ce-main></ce-main>

If we do without the shadowRoot, everything is nicely focusable in Chrome/EdgeBeta/Firefox:

class Main extends HTMLElement {
    constructor() { super(); }

    connectedCallback() {
        customElements.whenDefined("ce-leaf").then(
          () => this.constructFromSomeDataSource()
        );
    }

    constructFromSomeDataSource() {
        let rows = [];
        for (let i = 0; i < 4; i++) {
            let leaf = document.createElement("ce-leaf");
            this.appendChild(leaf);
        }; 
    }
}

class Leaf extends HTMLElement {
    constructor() {
        super();        
    }

    connectedCallback() {
        this.innerHTML = `
            <div contentEditable></div>
        `;
    }
}

customElements.define("ce-main", Main);
customElements.define("ce-leaf", Leaf);
[contentEditable] {
    min-height:2em;
    padding:.5em;
    border:1px dashed rgba(0,0,0,.0625);
}
[contentEditable]:empty::before {
    color: rgba(0,0,0,.15);
    content: "You should be able to focus and type here.";
    cursor:text;
}
<ce-main></ce-main>

Can anyone verify if this is a bug in FF, or if I am simply doing something that is not in line with how it should be done in FF?

DWR
  • 888
  • 1
  • 8
  • 15

1 Answers1

0

Had to dig through many Firefox/Focus posts.

Similar behaviour in a FireFox bug going back some 6 years: https://bugzilla.mozilla.org/show_bug.cgi?id=904846

Workaround

Found the best approach here: Clicking outside a contenteditable div stills give focus to it?

Handle the contenteditable attribute and setting focus() yourself with click & blur events:

(note: leafCounter is valid CSS, just does not work in StackOverflow inline code, works in JSFiddle)

class Main extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback() {
    this.attachShadow({ mode: "open" })
        .innerHTML = `<style>
            ce-leaf div {
                padding: .5em;
                cursor: text;
                counter-increment: leafCounter;
            }
            ce-leaf div:empty::before {
                  color: lightgrey;
                content: "placeholder text #" counter(leafCounter);
            }      
            [contenteditable]:focus{
                background: lightgreen;
            }
        </style>` +  "<ce-leaf></ce-leaf>".repeat(5);
  }
}
class Leaf extends HTMLElement {
  constructor() {
    super();
    let div = this.appendChild(document.createElement("div"));
    div.addEventListener("click", evt => {
      evt.target.contentEditable = true;
      evt.target.focus();
    });
    div.addEventListener("blur", evt => {
      evt.target.contentEditable = false;
    });
  }
}
customElements.define("ce-main", Main);
customElements.define("ce-leaf", Leaf);
<ce-main></ce-main>

<ce-leaf> IS an element!

You don't need that DIV inside a <ce-leaf> Custom Element...

JSFiddle version does the contenteditable on <ce-leaf>

https://jsfiddle.net/dannye/udmgL03p/

  constructor() {
    super();
    this.addEventListener("click", evt => {
      this.contentEditable = true;
      this.focus();
    });
    this.addEventListener("blur", evt => {
      this.contentEditable = false;
    });
  }

Update: Alas another Firefox with contenteditable bug: You can't select part of a text and replace it in the JSfiddle..
stick with the DIV inside an element solution.

Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • Your suggested method works well for desktop, and it is interesting that omitting the manual handling of the `contentEditable` attribute stops the work-around from working. It's still a bit wonky on mobile FF - it behaves better than it previously did, but tapping back and forth between input elements and entering text into them as you hop among them, will sometimes leave the cursor in an unintuitive / non-functional state. – DWR Jan 13 '20 at 20:57