Skip to content

Commit 5e8a868

Browse files
committed
Extract delegate for TrixEditorElement
In preparation for [basecamp#1128][], this commit introduces a module-private `Delegate` class to serve as a representation of what form integration requires for the `<trix-editor>` custom element. The structure of the `Delegate` class mirrors that of the `TrixEditorElement` from which its contents are extracted. First, there are the properties that mimic those of most form controls, including: * `labels` * `form` * `name` * `value` * `defaultValue` * `type` With the exception of `labels`, property access is mostly proxied through the associated `<input type="hidden">` element (accessed through its own `inputElement` property). Next, the `Delegate` defines methods that correspond to the Custom Element lifecycle events, including: * `connectedCallback` * `disconnectedCallback` * `setFormValue` The connected and disconnected callbacks mirror that of the `TrixEditorElement` itself. These callbacks attach and remove event listeners for `click` and `reset` events. The `setFormValue` is named to correspond with [ElementInternals.setFormValue][]. Along with introducing this callback method, this commit renames the `TrixEditorElement.setInputElementValue` method to `TrixEditorElement.setFormValue`. In addition to renaming `setInputElementValue`, this commit also defines `TrixEditorElement.formResetCallback` (along with other [empty form callbacks][]), then implements `TrixEditorElement.reset` as an alias. The name mirrors the [ElementInternals.formResetCallback][]. [basecamp#1128]: basecamp#1128 [ElementInternals.setFormValue]: https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setFormValue [empty form callbacks]: https://webkit.org/blog/13711/elementinternals-and-form-associated-custom-elements/ [ElementInternals.formResetCallback]: https://web.dev/articles/more-capable-form-controls#void_formresetcallback
1 parent 2b7f980 commit 5e8a868

File tree

7 files changed

+196
-76
lines changed

7 files changed

+196
-76
lines changed

README.md

+27
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,33 @@ To populate a `<trix-editor>` with stored content, include that content in the a
148148

149149
Always use an associated input element to safely populate an editor. Trix won’t load any HTML content inside a `<trix-editor>…</trix-editor>` tag.
150150

151+
## Providing an Accessible Name
152+
153+
Like other form controls, `<trix-editor>` elements should have an accessible name. The `<trix-editor>` element integrates with `<label>` elements and The `<trix-editor>` supports two styles of integrating with `<label>` elements:
154+
155+
1. render the `<trix-editor>` element with an `[id]` attribute that the `<label>` element references through its `[for]` attribute:
156+
157+
```html
158+
<label for="editor">Editor</label>
159+
<trix-editor id="editor"></trix-editor>
160+
```
161+
162+
2. render the `<trix-editor>` element as a child of the `<label>` element:
163+
164+
```html
165+
<trix-toolbar id="editor-toolbar"></trix-toolbar>
166+
<label>
167+
Editor
168+
169+
<trix-editor toolbar="editor-toolbar"></trix-editor>
170+
</label>
171+
```
172+
173+
> [!WARNING]
174+
> When rendering the `<trix-editor>` element as a child of the `<label>` element, [explicitly render](#creating-an-editor) the corresponding `<trix-toolbar>` element outside of the `<label>` element.
175+
176+
In addition to integrating with `<label>` elements, `<trix-editor>` elements support `[aria-label]` and `[aria-labelledby]` attributes.
177+
151178
## Styling Formatted Content
152179

153180
To ensure what you see when you edit is what you see when you save, use a CSS class name to scope styles for Trix formatted content. Apply this class name to your `<trix-editor>` element, and to a containing element when you render stored Trix content for display in your application.

assets/index.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
</head>
7373
<body>
7474
<main>
75-
<trix-editor autofocus class="trix-content" input="input"></trix-editor>
75+
<label for="editor">Input</label>
76+
<trix-editor autofocus class="trix-content" input="input" id="editor"></trix-editor>
7677
<details id="output">
7778
<summary>Output</summary>
7879
<textarea readonly id="input"></textarea>

karma.conf.js

+1-1
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/"

src/test/system/custom_element_test.js

+36-5
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
466466

467467
test("editor resets to its original value on form reset", async () => {
468468
const element = getEditorElement()
469-
const { form } = element.inputElement
469+
const { form } = element
470470

471471
await typeCharacters("hello")
472472
form.reset()
@@ -475,7 +475,7 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
475475

476476
test("editor resets to last-set value on form reset", async () => {
477477
const element = getEditorElement()
478-
const { form } = element.inputElement
478+
const { form } = element
479479

480480
element.value = "hi"
481481
await typeCharacters("hello")
@@ -485,7 +485,7 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
485485

486486
test("editor respects preventDefault on form reset", async () => {
487487
const element = getEditorElement()
488-
const { form } = element.inputElement
488+
const { form } = element
489489
const preventDefault = (event) => event.preventDefault()
490490

491491
await typeCharacters("hello")
@@ -495,12 +495,43 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
495495
form.removeEventListener("reset", preventDefault, false)
496496
expectDocument("hello\n")
497497
})
498+
499+
test("element returns empty string when value is missing", async () => {
500+
const element = getEditorElement()
501+
502+
assert.equal(element.value, "")
503+
})
504+
505+
test("editor resets to its original value on element reset", async () => {
506+
const element = getEditorElement()
507+
508+
await typeCharacters("hello")
509+
element.reset()
510+
expectDocument("\n")
511+
})
512+
513+
test("editor returns its type", async() => {
514+
const element = getEditorElement()
515+
516+
assert.equal("trix-editor", element.type)
517+
})
518+
})
519+
520+
testGroup("HTML sanitization", { template: "editor_html" }, () => {
521+
test("ignores text nodes in script elements", () => {
522+
const element = getEditorElement()
523+
element.value = "<div>safe</div><script>alert(\"unsafe\")</script>"
524+
525+
expectDocument("safe\n")
526+
assert.equal(element.innerHTML, "<div><!--block-->safe</div>")
527+
assert.equal(element.value, "<div>safe</div>")
528+
})
498529
})
499530

500531
testGroup("<label> support", { template: "editor_with_labels" }, () => {
501532
test("associates all label elements", () => {
502533
const labels = [ document.getElementById("label-1"), document.getElementById("label-3") ]
503-
assert.deepEqual(getEditorElement().labels, labels)
534+
assert.deepEqual(Array.from(getEditorElement().labels), labels)
504535
})
505536

506537
test("focuses when <label> clicked", () => {
@@ -528,7 +559,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
528559
assert.equal(editor.form, form)
529560
})
530561

531-
test("transitively accesses its related <input> element's <form>", () => {
562+
test("transitively accesses its related <form>", () => {
532563
const form = document.getElementById("input-form")
533564
const editor = document.getElementById("editor-with-input-form")
534565
assert.equal(editor.form, form)

src/test/system/installation_process_test.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,27 @@ testGroup("Installation process", { template: "editor_html" }, () => {
2020
})
2121
})
2222

23-
testGroup("Installation process without specified elements", { template: "editor_empty" }, () =>
24-
test("creates identified toolbar and input elements", () => {
23+
testGroup("Installation process without specified elements", { template: "editor_empty" }, () => {
24+
test("creates identified toolbar elements", () => {
2525
const editorElement = getEditorElement()
2626

2727
const toolbarId = editorElement.getAttribute("toolbar")
2828
assert.ok(/trix-toolbar-\d+/.test(toolbarId), `toolbar id not assert.ok ${JSON.stringify(toolbarId)}`)
2929
const toolbarElement = document.getElementById(toolbarId)
3030
assert.ok(toolbarElement, "toolbar element not assert.ok")
3131
assert.equal(editorElement.toolbarElement, toolbarElement)
32+
})
33+
34+
test("creates identified input elements", () => {
35+
const editorElement = getEditorElement()
3236

3337
const inputId = editorElement.getAttribute("input")
3438
assert.ok(/trix-input-\d+/.test(inputId), `input id not assert.ok ${JSON.stringify(inputId)}`)
3539
const inputElement = document.getElementById(inputId)
3640
assert.ok(inputElement, "input element not assert.ok")
3741
assert.equal(editorElement.inputElement, inputElement)
3842
})
39-
)
43+
})
4044

4145
testGroup("Installation process with specified elements", { template: "editor_with_toolbar_and_input" }, () => {
4246
test("uses specified elements", () => {

src/trix/controllers/editor_controller.js

+1-1
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)