Skip to content

Commit a3cbff8

Browse files
committed
Re-submit disabled and validations with backwards compatible support
1 parent 58e18b0 commit a3cbff8

File tree

11 files changed

+211
-63
lines changed

11 files changed

+211
-63
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ on:
88

99
jobs:
1010
build:
11-
name: Browser tests
11+
name: "Browser tests (Trix.config.editor.formAssociated = ${{ matrix.formAssociated }})"
12+
strategy:
13+
matrix:
14+
formAssociated: [true, false]
15+
env:
16+
FORM_ASSOCIATED: "${{ matrix.formAssociated }}"
1217
runs-on: ubuntu-latest
1318
steps:
1419
- uses: actions/checkout@v3

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ This is the approach that all modern, production ready, WYSIWYG editors now take
1919

2020
<details><summary>Trix supports all evergreen, self-updating desktop and mobile browsers.</summary><img src="https://app.saucelabs.com/browser-matrix/basecamp_trix.svg"></details>
2121

22-
Trix is built with established web standards, notably [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements), [Mutation Observer](https://dom.spec.whatwg.org/#mutation-observers), and [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
22+
Trix is built with established web standards, notably [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements), [Element Internals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), [Mutation Observer](https://dom.spec.whatwg.org/#mutation-observers), and [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
2323

2424
# Getting Started
2525

@@ -88,6 +88,16 @@ If the attribute is defined in `Trix.config.blockAttributes`, Trix will apply th
8888

8989
Clicking the quote button toggles whether the block should be rendered with `<blockquote>`.
9090

91+
## Integrating with Element Internals
92+
93+
Trix will integrate `<trix-editor>` elements with forms depending on the browser's support for [Element Internals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals). By default, Trix will enable support for `ElementInternals` when the feature is enabled in the browser. If there is a need to disable support for `ElementInternals`, set `Trix.config.editor.formAssociated = false`:
94+
95+
```js
96+
import Trix from "trix"
97+
98+
Trix.config.editor.formAssociated = false
99+
```
100+
91101
## Invoking Internal Trix Actions
92102

93103
Internal actions are defined in `controllers/editor_controller.js` and consist of:

karma.conf.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const config = {
33
frameworks: [ "qunit" ],
44
files: [
55
{ pattern: "dist/test.js", watched: false },
6-
{ pattern: "src/test_helpers/fixtures/*.png", watched: false, included: false, served: true }
6+
{ pattern: "src/test/test_helpers/fixtures/*.png", watched: false, included: false, served: true }
77
],
88
proxies: {
99
"/test_helpers/fixtures/": "/base/src/test_helpers/fixtures/"
@@ -29,6 +29,14 @@ const config = {
2929

3030
/* eslint camelcase: "off", */
3131

32+
if (process.env.FORM_ASSOCIATED === "false") {
33+
config.files.push({
34+
pattern: "src/test/test_helpers/fixtures/form_associated_false.js",
35+
watched: false,
36+
included: true
37+
})
38+
}
39+
3240
if (process.env.SAUCE_ACCESS_KEY) {
3341
config.customLaunchers = {
3442
sl_chrome_latest: {

src/test/system/custom_element_test.js

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as config from "trix/config"
12
import { rangesAreEqual } from "trix/core/helpers"
23

34
import {
@@ -500,7 +501,7 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
500501
testGroup("<label> support", { template: "editor_with_labels" }, () => {
501502
test("associates all label elements", () => {
502503
const labels = [ document.getElementById("label-1"), document.getElementById("label-3") ]
503-
assert.deepEqual(getEditorElement().labels, labels)
504+
assert.deepEqual(Array.from(getEditorElement().labels), labels)
504505
})
505506

506507
test("focuses when <label> clicked", () => {
@@ -539,6 +540,26 @@ testGroup("form property references its <form>", { template: "editors_with_forms
539540
assert.equal(editor.form, null)
540541
})
541542

543+
test("editor resets to its original value on element reset", async () => {
544+
const element = getEditorElement()
545+
546+
await typeCharacters("hello")
547+
element.reset()
548+
expectDocument("\n")
549+
})
550+
551+
test("element returns empty string when value is missing", () => {
552+
const element = getEditorElement()
553+
554+
assert.equal(element.value, "")
555+
})
556+
557+
test("editor returns its type", () => {
558+
const element = getEditorElement()
559+
560+
assert.equal("trix-editor", element.type)
561+
})
562+
542563
test("adds [disabled] attribute based on .disabled property", () => {
543564
const editor = document.getElementById("editor-with-ancestor-form")
544565

@@ -551,7 +572,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
551572
assert.equal(editor.hasAttribute("disabled"), false, "removes [disabled] attribute")
552573
})
553574

554-
test("removes [contenteditable] and disables input when editor element has [disabled]", () => {
575+
testIfFormAssociated("removes [contenteditable] and disables input when editor element has [disabled]", () => {
555576
const editor = document.getElementById("editor-with-no-form")
556577

557578
editor.setAttribute("disabled", "")
@@ -569,7 +590,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
569590
assert.equal(editor.hasAttribute("contenteditable"), true, "adds [contenteditable] attribute")
570591
})
571592

572-
test("removes [contenteditable] and disables input when editor element is :disabled", () => {
593+
testIfFormAssociated("removes [contenteditable] and disables input when editor element is :disabled", () => {
573594
const editor = document.getElementById("editor-within-fieldset")
574595
const fieldset = document.getElementById("fieldset")
575596

@@ -590,7 +611,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
590611
assert.equal(editor.hasAttribute("contenteditable"), true, "adds [contenteditable] attribute")
591612
})
592613

593-
test("does not receive focus when :disabled", () => {
614+
testIfFormAssociated("does not receive focus when :disabled", () => {
594615
const activeEditor = document.getElementById("editor-with-input-form")
595616
const editor = document.getElementById("editor-within-fieldset")
596617

@@ -601,7 +622,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
601622
assert.equal(activeEditor, document.activeElement, "disabled editor does not receive focus")
602623
})
603624

604-
test("disabled editor does not encode its value when the form is submitted", () => {
625+
testIfFormAssociated("disabled editor does not encode its value when the form is submitted", () => {
605626
const editor = document.getElementById("editor-with-ancestor-form")
606627
const form = editor.form
607628

@@ -611,7 +632,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
611632
assert.deepEqual({}, Object.fromEntries(new FormData(form).entries()), "does not write to FormData")
612633
})
613634

614-
test("validates with [required] attribute as invalid", () => {
635+
testIfFormAssociated("validates with [required] attribute as invalid", () => {
615636
const editor = document.getElementById("editor-with-ancestor-form")
616637
const form = editor.form
617638
let invalidEvent, submitEvent = null
@@ -630,7 +651,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
630651
assert.equal(submitEvent, null, "does not dispatch a 'submit' event")
631652
})
632653

633-
test("does not validate with [disabled] attribute", () => {
654+
testIfFormAssociated("does not validate with [disabled] attribute", () => {
634655
const editor = document.getElementById("editor-with-ancestor-form")
635656
let invalidEvent = null
636657

@@ -642,7 +663,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
642663
assert.equal(invalidEvent, null, "does not dispatch an 'invalid' event")
643664
})
644665

645-
test("re-validates when the value changes", async () => {
666+
testIfFormAssociated("re-validates when the value changes", async () => {
646667
const editor = document.getElementById("editor-with-ancestor-form")
647668
editor.required = true
648669
editor.focus()
@@ -656,7 +677,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
656677
assert.equal(editor.validationMessage, "", "clears the validationMessage")
657678
})
658679

659-
test("accepts a customError validation message", () => {
680+
testIfFormAssociated("accepts a customError validation message", () => {
660681
const editor = document.getElementById("editor-with-ancestor-form")
661682

662683
editor.setCustomValidity("A custom validation message")
@@ -666,3 +687,13 @@ testGroup("form property references its <form>", { template: "editors_with_forms
666687
assert.equal(editor.validationMessage, "A custom validation message")
667688
})
668689
})
690+
691+
function testIfFormAssociated(name, callback) {
692+
test(name, async () => {
693+
if (config.editor.formAssociated) {
694+
await callback()
695+
} else {
696+
assert.equal(config.editor.formAssociated, true, "skipping test that requires ElementInternals")
697+
}
698+
})
699+
}
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
export default () =>
22
`<label id="label-1" for="editor"><span>Label 1</span></label>
3-
<label id="label-2">
4-
Label 2
5-
<trix-editor id="editor"></trix-editor>
6-
</label>
7-
<label id="label-3" for="editor">Label 3</label>`
3+
<label id="label-2">Label 2</label>
4+
<trix-editor id="editor"></trix-editor>
5+
<label id="label-3" for="editor">Label 3</label>`
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
export default () =>
22
`<form id="ancestor-form">
3-
<trix-editor id="editor-with-ancestor-form"></trix-editor>
3+
<trix-editor id="editor-with-ancestor-form" name="editor-with-ancestor-form"></trix-editor>
44
</form>
55
66
<form id="input-form">
77
<input type="hidden" id="hidden-input">
88
</form>
99
<trix-editor id="editor-with-input-form" input="hidden-input"></trix-editor>
1010
11-
<trix-editor id="editor-with-no-form"></trix-editor>`
11+
<trix-editor id="editor-with-no-form"></trix-editor>
12+
<fieldset id="fieldset"><trix-editor id="editor-within-fieldset"></fieldset>`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
window.Trix.config.editor.formAssociated = false

src/trix/config/editor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
formAssociated: "ElementInternals" in window
3+
}

src/trix/config/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { default as attachments } from "./attachments"
22
export { default as blockAttributes } from "./block_attributes"
33
export { default as browser } from "./browser"
44
export { default as css } from "./css"
5+
export { default as editor } from "./editor"
56
export { default as fileSize } from "./file_size_formatting"
67
export { default as input } from "./input"
78
export { default as keyNames } from "./key_names"

src/trix/controllers/editor_controller.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ export default class EditorController extends Controller {
503503
updateInputElement() {
504504
const element = this.compositionController.getSerializableElement()
505505
const value = serializeToContentType(element, "text/html")
506-
return this.editorElement.setInputElementValue(value)
506+
return this.editorElement.setFormValue(value)
507507
}
508508

509509
notifyEditorElement(message, data) {

0 commit comments

Comments
 (0)