diff --git a/karma.conf.js b/karma.conf.js index 9fe317624..da533c33b 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -53,25 +53,12 @@ if (process.env.SAUCE_ACCESS_KEY) { browserVersion: "latest", "moz:debuggerAddress": true }, - sl_safari_12_1: { - base: "SauceLabs", - browserName: "safari", - platform: "macOS 10.13", - version: "12.1" - }, sl_edge_latest: { base: "SauceLabs", browserName: "microsoftedge", platform: "Windows 10", version: "latest" }, - sl_ios_latest: { - base: "SauceLabs", - browserName: "safari", - platform: "ios", - device: "iPhone X Simulator", - version: "13.0" - }, sl_android_9: { base: "SauceLabs", browserName: "chrome", diff --git a/src/test/system.js b/src/test/system.js index 513f7a4f8..68fbd7d0b 100644 --- a/src/test/system.js +++ b/src/test/system.js @@ -15,6 +15,7 @@ import "test/system/html_replacement_test" import "test/system/installation_process_test" import "test/system/level_2_input_test" import "test/system/list_formatting_test" +import "test/system/morphing_test" import "test/system/mutation_input_test" import "test/system/pasting_test" import "test/system/text_formatting_test" diff --git a/src/test/system/morphing_test.js b/src/test/system/morphing_test.js new file mode 100644 index 000000000..c4a0d52af --- /dev/null +++ b/src/test/system/morphing_test.js @@ -0,0 +1,36 @@ +import { assert, test, testGroup } from "test/test_helper" +import { nextFrame } from "../test_helpers/timing_helpers" + +testGroup("morphing with internal toolbar", { template: "editor_empty" }, () => { + test("removing the 'connected' attribute will reset the editor and recreate toolbar", async () => { + const element = getEditorElement() + + assert.ok(element.hasAttribute("connected")) + + const originalToolbar = element.toolbarElement + element.toolbarElement.remove() + element.removeAttribute("toolbar") + element.removeAttribute("connected") + await nextFrame() + + assert.ok(element.hasAttribute("connected")) + assert.ok(element.toolbarElement) + assert.notEqual(originalToolbar, element.toolbarElement) + }) +}) + +testGroup("morphing with external toolbar", { template: "editor_with_toolbar_and_input" }, () => { + test("removing the 'connected' attribute will reset the editor leave the toolbar untouched", async () => { + const element = getEditorElement() + + assert.ok(element.hasAttribute("connected")) + + const originalToolbar = element.toolbarElement + element.removeAttribute("connected") + await nextFrame() + + assert.ok(element.hasAttribute("connected")) + assert.ok(element.toolbarElement) + assert.equal(originalToolbar, element.toolbarElement) + }) +}) diff --git a/src/trix/elements/trix_editor_element.js b/src/trix/elements/trix_editor_element.js index 846c8dc61..40d9e9a76 100644 --- a/src/trix/elements/trix_editor_element.js +++ b/src/trix/elements/trix_editor_element.js @@ -348,6 +348,8 @@ class LegacyDelegate { export default class TrixEditorElement extends HTMLElement { static formAssociated = "ElementInternals" in window + static observedAttributes = [ "connected" ] + #delegate constructor() { @@ -410,9 +412,9 @@ export default class TrixEditorElement extends HTMLElement { } else if (this.parentNode) { const toolbarId = `trix-toolbar-${this.trixId}` this.setAttribute("toolbar", toolbarId) - const element = makeElement("trix-toolbar", { id: toolbarId }) - this.parentNode.insertBefore(element, this) - return element + this.internalToolbar = makeElement("trix-toolbar", { id: toolbarId }) + this.parentNode.insertBefore(this.internalToolbar, this) + return this.internalToolbar } else { return undefined } @@ -453,6 +455,14 @@ export default class TrixEditorElement extends HTMLElement { this.editor?.loadHTML(this.defaultValue) } + // Element callbacks + + attributeChangedCallback(name, oldValue, newValue) { + if (name === "connected" && this.isConnected && oldValue != null && oldValue !== newValue) { + requestAnimationFrame(() => this.reconnect()) + } + } + // Controller delegate methods notify(message, data) { @@ -485,6 +495,8 @@ export default class TrixEditorElement extends HTMLElement { } this.editorController.registerSelectionManager() this.#delegate.connectedCallback() + + this.toggleAttribute("connected", true) autofocus(this) } } @@ -492,6 +504,18 @@ export default class TrixEditorElement extends HTMLElement { disconnectedCallback() { this.editorController?.unregisterSelectionManager() this.#delegate.disconnectedCallback() + this.toggleAttribute("connected", false) + } + + reconnect() { + this.removeInternalToolbar() + this.disconnectedCallback() + this.connectedCallback() + } + + removeInternalToolbar() { + this.internalToolbar?.remove() + this.internalToolbar = null } // Form support