Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract delegate for TrixEditorElement #1132

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,33 @@ To populate a `<trix-editor>` with stored content, include that content in the a

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.

## Providing an Accessible Name

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:

1. render the `<trix-editor>` element with an `[id]` attribute that the `<label>` element references through its `[for]` attribute:

```html
<label for="editor">Editor</label>
<trix-editor id="editor"></trix-editor>
```

2. render the `<trix-editor>` element as a child of the `<label>` element:

```html
<trix-toolbar id="editor-toolbar"></trix-toolbar>
<label>
Editor

<trix-editor toolbar="editor-toolbar"></trix-editor>
</label>
```

> [!WARNING]
> 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.

In addition to integrating with `<label>` elements, `<trix-editor>` elements support `[aria-label]` and `[aria-labelledby]` attributes.

## Styling Formatted Content

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.
Expand Down
3 changes: 2 additions & 1 deletion assets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
</head>
<body>
<main>
<trix-editor autofocus class="trix-content" input="input"></trix-editor>
<label for="editor">Input</label>
<trix-editor autofocus class="trix-content" input="input" id="editor"></trix-editor>
<details id="output">
<summary>Output</summary>
<textarea readonly id="input"></textarea>
Expand Down
2 changes: 1 addition & 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 Down
41 changes: 36 additions & 5 deletions src/test/system/custom_element_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {

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

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

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

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

test("editor respects preventDefault on form reset", async () => {
const element = getEditorElement()
const { form } = element.inputElement
const { form } = element
const preventDefault = (event) => event.preventDefault()

await typeCharacters("hello")
Expand All @@ -495,12 +495,43 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
form.removeEventListener("reset", preventDefault, false)
expectDocument("hello\n")
})

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

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

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

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

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

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

testGroup("HTML sanitization", { template: "editor_html" }, () => {
test("ignores text nodes in script elements", () => {
const element = getEditorElement()
element.value = "<div>safe</div><script>alert(\"unsafe\")</script>"

expectDocument("safe\n")
assert.equal(element.innerHTML, "<div><!--block-->safe</div>")
assert.equal(element.value, "<div>safe</div>")
})
})

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 @@ -528,7 +559,7 @@ testGroup("form property references its <form>", { template: "editors_with_forms
assert.equal(editor.form, form)
})

test("transitively accesses its related <input> element's <form>", () => {
test("transitively accesses its related <form>", () => {
const form = document.getElementById("input-form")
const editor = document.getElementById("editor-with-input-form")
assert.equal(editor.form, form)
Expand Down
10 changes: 7 additions & 3 deletions src/test/system/installation_process_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,27 @@ testGroup("Installation process", { template: "editor_html" }, () => {
})
})

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

const toolbarId = editorElement.getAttribute("toolbar")
assert.ok(/trix-toolbar-\d+/.test(toolbarId), `toolbar id not assert.ok ${JSON.stringify(toolbarId)}`)
const toolbarElement = document.getElementById(toolbarId)
assert.ok(toolbarElement, "toolbar element not assert.ok")
assert.equal(editorElement.toolbarElement, toolbarElement)
})

test("creates identified input elements", () => {
const editorElement = getEditorElement()

const inputId = editorElement.getAttribute("input")
assert.ok(/trix-input-\d+/.test(inputId), `input id not assert.ok ${JSON.stringify(inputId)}`)
const inputElement = document.getElementById(inputId)
assert.ok(inputElement, "input element not assert.ok")
assert.equal(editorElement.inputElement, inputElement)
})
)
})

testGroup("Installation process with specified elements", { template: "editor_with_toolbar_and_input" }, () => {
test("uses specified elements", () => {
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
Loading