Skip to content

Commit

Permalink
Re-submit disabled and validations with backwards compatible support
Browse files Browse the repository at this point in the history
  • Loading branch information
seanpdoyle committed Oct 4, 2024
1 parent 58e18b0 commit 5e9cecc
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 62 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ on:
jobs:
build:
name: Browser tests
strategy:
matrix:
formAssociated: [true, false]
env:
FORM_ASSOCIATED: "${{ matrix.formAssociated }}"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This is the approach that all modern, production ready, WYSIWYG editors now take

<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>

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).
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).

# Getting Started

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

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

## Integrating with Element Internals

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`:

```js
import Trix from "trix"

Trix.config.editor.formAssociated = false
```

## Invoking Internal Trix Actions

Internal actions are defined in `controllers/editor_controller.js` and consist of:
Expand Down
10 changes: 9 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const config = {
frameworks: [ "qunit" ],
files: [
{ pattern: "dist/test.js", watched: false },
{ pattern: "src/test_helpers/fixtures/*.png", watched: false, included: false, served: true }
{ pattern: "src/test/test_helpers/fixtures/*.png", watched: false, included: false, served: true }
],
proxies: {
"/test_helpers/fixtures/": "/base/src/test_helpers/fixtures/"
Expand All @@ -29,6 +29,14 @@ const config = {

/* eslint camelcase: "off", */

if (process.env.FORM_ASSOCIATED === "false") {
config.files.push({
pattern: "src/test/test_helpers/fixtures/form_associated_false.js",
watched: false,
included: true
})
}

if (process.env.SAUCE_ACCESS_KEY) {
config.customLaunchers = {
sl_chrome_latest: {
Expand Down
47 changes: 38 additions & 9 deletions src/test/system/custom_element_test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as config from "trix/config"
import { rangesAreEqual } from "trix/core/helpers"

import {
Expand All @@ -19,6 +20,14 @@ import {
} from "test/test_helper"
import { delay, nextFrame } from "../test_helpers/timing_helpers"

const testIfFormAssociated = (name, callback) => {
test(name, async () => {
if (config.editor.formAssociated) {
await callback()
}
})
}

testGroup("Custom element API", { template: "editor_empty" }, () => {
test("element triggers trix-initialize on first connect", async () => {
const container = document.getElementById("trix-container")
Expand Down Expand Up @@ -500,7 +509,7 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
testGroup("<label> support", { template: "editor_with_labels" }, () => {
test("associates all label elements", () => {
const labels = [ document.getElementById("label-1"), document.getElementById("label-3") ]
assert.deepEqual(getEditorElement().labels, labels)
assert.deepEqual(Array.from(getEditorElement().labels), labels)
})

test("focuses when <label> clicked", () => {
Expand Down Expand Up @@ -539,6 +548,26 @@ testGroup("form property references its <form>", { template: "editors_with_forms
assert.equal(editor.form, null)
})

test("editor resets to its original value on element reset", async () => {
const element = getEditorElement()

await typeCharacters("hello")
element.reset()
expectDocument("\n")
})

test("element returns empty string when value is missing", () => {
const element = getEditorElement()

assert.equal(element.value, "")
})

test("editor returns its type", () => {
const element = getEditorElement()

assert.equal("trix-editor", element.type)
})

test("adds [disabled] attribute based on .disabled property", () => {
const editor = document.getElementById("editor-with-ancestor-form")

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

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

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

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

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

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

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

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

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

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

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

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

test("re-validates when the value changes", async () => {
testIfFormAssociated("re-validates when the value changes", async () => {
const editor = document.getElementById("editor-with-ancestor-form")
editor.required = true
editor.focus()
Expand All @@ -656,7 +685,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
assert.equal(editor.validationMessage, "", "clears the validationMessage")
})

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

editor.setCustomValidity("A custom validation message")
Expand Down
8 changes: 3 additions & 5 deletions src/test/test_helpers/fixtures/editor_with_labels.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
export default () =>
`<label id="label-1" for="editor"><span>Label 1</span></label>
<label id="label-2">
Label 2
<trix-editor id="editor"></trix-editor>
</label>
<label id="label-3" for="editor">Label 3</label>`
<label id="label-2">Label 2</label>
<trix-editor id="editor"></trix-editor>
<label id="label-3" for="editor">Label 3</label>`
5 changes: 3 additions & 2 deletions src/test/test_helpers/fixtures/editors_with_forms.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export default () =>
`<form id="ancestor-form">
<trix-editor id="editor-with-ancestor-form"></trix-editor>
<trix-editor id="editor-with-ancestor-form" name="editor-with-ancestor-form"></trix-editor>
</form>
<form id="input-form">
<input type="hidden" id="hidden-input">
</form>
<trix-editor id="editor-with-input-form" input="hidden-input"></trix-editor>
<trix-editor id="editor-with-no-form"></trix-editor>`
<trix-editor id="editor-with-no-form"></trix-editor>
<fieldset id="fieldset"><trix-editor id="editor-within-fieldset"></fieldset>`
1 change: 1 addition & 0 deletions src/test/test_helpers/fixtures/form_associated_false.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
window.Trix.config.editor.formAssociated = false
3 changes: 3 additions & 0 deletions src/trix/config/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
formAssociated: "ElementInternals" in window
}
1 change: 1 addition & 0 deletions src/trix/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { default as attachments } from "./attachments"
export { default as blockAttributes } from "./block_attributes"
export { default as browser } from "./browser"
export { default as css } from "./css"
export { default as editor } from "./editor"
export { default as fileSize } from "./file_size_formatting"
export { default as input } from "./input"
export { default as keyNames } from "./key_names"
Expand Down
2 changes: 1 addition & 1 deletion src/trix/controllers/editor_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ export default class EditorController extends Controller {
updateInputElement() {
const element = this.compositionController.getSerializableElement()
const value = serializeToContentType(element, "text/html")
return this.editorElement.setInputElementValue(value)
return this.editorElement.setFormValue(value)
}

notifyEditorElement(message, data) {
Expand Down
Loading

0 comments on commit 5e9cecc

Please sign in to comment.