Skip to content

Commit 2b7f980

Browse files
Merge pull request basecamp#1192 from basecamp/revert-1188-editor-element-internals
Revert "Integrate with `ElementInternals`"
2 parents 2f8c59d + a44dad9 commit 2b7f980

File tree

5 files changed

+59
-183
lines changed

5 files changed

+59
-183
lines changed

README.md

-57
Original file line numberDiff line numberDiff line change
@@ -148,63 +148,6 @@ 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-
## Disabling the Editor
152-
153-
To disable the `<trix-editor>`, render it with the `[disabled]` attribute:
154-
155-
```html
156-
<trix-editor disabled></trix-editor>
157-
```
158-
159-
Disabled editors are not editable, cannot receive focus, and their values will
160-
be ignored when their related `<form>` element is submitted.
161-
162-
To change whether or not an editor is disabled, either toggle the `[disabled]`
163-
attribute or assign a boolean to the `.disabled` property:
164-
165-
```html
166-
<trix-editor id="editor" disabled></trix-editor>
167-
168-
<script>
169-
const editor = document.getElementById("editor")
170-
171-
editor.toggleAttribute("disabled", false)
172-
editor.disabled = true
173-
</script>
174-
```
175-
176-
When disabled, the editor will match the [:disabled CSS
177-
pseudo-class][:disabled].
178-
179-
[:disabled]: https://developer.mozilla.org/en-US/docs/Web/CSS/:disabled
180-
181-
## Providing an Accessible Name
182-
183-
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:
184-
185-
1. render the `<trix-editor>` element with an `[id]` attribute that the `<label>` element references through its `[for]` attribute:
186-
187-
```html
188-
<label for="editor">Editor</label>
189-
<trix-editor id="editor"></trix-editor>
190-
```
191-
192-
2. render the `<trix-editor>` element as a child of the `<label>` element:
193-
194-
```html
195-
<trix-toolbar id="editor-toolbar"></trix-toolbar>
196-
<label>
197-
Editor
198-
199-
<trix-editor toolbar="editor-toolbar"></trix-editor>
200-
</label>
201-
```
202-
203-
> [!WARNING]
204-
> 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.
205-
206-
In addition to integrating with `<label>` elements, `<trix-editor>` elements support `[aria-label]` and `[aria-labelledby]` attributes.
207-
208151
## Styling Formatted Content
209152

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

src/test/system/custom_element_test.js

+2-94
Original file line numberDiff line numberDiff line change
@@ -495,32 +495,12 @@ testGroup("Custom element API", { template: "editor_empty" }, () => {
495495
form.removeEventListener("reset", preventDefault, false)
496496
expectDocument("hello\n")
497497
})
498-
499-
test("editor resets to its original value on element reset", async () => {
500-
const element = getEditorElement()
501-
502-
await typeCharacters("hello")
503-
element.reset()
504-
expectDocument("\n")
505-
})
506-
507-
test("element returns empty string when value is missing", () => {
508-
const element = getEditorElement()
509-
510-
assert.equal(element.value, "")
511-
})
512-
513-
test("editor returns its type", () => {
514-
const element = getEditorElement()
515-
516-
assert.equal("trix-editor", element.type)
517-
})
518498
})
519499

520500
testGroup("<label> support", { template: "editor_with_labels" }, () => {
521501
test("associates all label elements", () => {
522502
const labels = [ document.getElementById("label-1"), document.getElementById("label-3") ]
523-
assert.deepEqual(Array.from(getEditorElement().labels), labels)
503+
assert.deepEqual(getEditorElement().labels, labels)
524504
})
525505

526506
test("focuses when <label> clicked", () => {
@@ -541,7 +521,7 @@ testGroup("<label> support", { template: "editor_with_labels" }, () => {
541521
})
542522
})
543523

544-
testGroup("integrates with its <form>", { template: "editors_with_forms", container: "div" }, () => {
524+
testGroup("form property references its <form>", { template: "editors_with_forms", container: "div" }, () => {
545525
test("accesses its ancestor form", () => {
546526
const form = document.getElementById("ancestor-form")
547527
const editor = document.getElementById("editor-with-ancestor-form")
@@ -558,76 +538,4 @@ testGroup("integrates with its <form>", { template: "editors_with_forms", contai
558538
const editor = document.getElementById("editor-with-no-form")
559539
assert.equal(editor.form, null)
560540
})
561-
562-
test("adds [disabled] attribute based on .disabled property", () => {
563-
const editor = document.getElementById("editor-with-ancestor-form")
564-
565-
editor.disabled = true
566-
567-
assert.equal(editor.hasAttribute("disabled"), true, "adds [disabled] attribute")
568-
569-
editor.disabled = false
570-
571-
assert.equal(editor.hasAttribute("disabled"), false, "removes [disabled] attribute")
572-
})
573-
574-
test("removes [contenteditable] and disables input when editor element has [disabled]", () => {
575-
const editor = document.getElementById("editor-with-no-form")
576-
577-
editor.setAttribute("disabled", "")
578-
579-
assert.equal(editor.matches(":disabled"), true, "sets :disabled CSS pseudostate")
580-
assert.equal(editor.inputElement.disabled, true, "disables input")
581-
assert.equal(editor.disabled, true, "exposes [disabled] attribute as .disabled property")
582-
assert.equal(editor.hasAttribute("contenteditable"), false, "removes [contenteditable] attribute")
583-
584-
editor.removeAttribute("disabled")
585-
586-
assert.equal(editor.matches(":disabled"), false, "removes sets :disabled pseudostate")
587-
assert.equal(editor.inputElement.disabled, false, "enabled input")
588-
assert.equal(editor.disabled, false, "updates .disabled property")
589-
assert.equal(editor.hasAttribute("contenteditable"), true, "adds [contenteditable] attribute")
590-
})
591-
592-
test("removes [contenteditable] and disables input when editor element is :disabled", () => {
593-
const editor = document.getElementById("editor-within-fieldset")
594-
const fieldset = document.getElementById("fieldset")
595-
596-
fieldset.disabled = true
597-
598-
assert.equal(editor.matches(":disabled"), true, "sets :disabled CSS pseudostate")
599-
assert.equal(editor.inputElement.disabled, true, "disables input")
600-
assert.equal(editor.disabled, true, "infers disabled state from ancestor")
601-
assert.equal(editor.hasAttribute("disabled"), false, "does not set [disabled] attribute")
602-
assert.equal(editor.hasAttribute("contenteditable"), false, "removes [contenteditable] attribute")
603-
604-
fieldset.disabled = false
605-
606-
assert.equal(editor.matches(":disabled"), false, "removes sets :disabled pseudostate")
607-
assert.equal(editor.inputElement.disabled, false, "enabled input")
608-
assert.equal(editor.disabled, false, "updates .disabled property")
609-
assert.equal(editor.hasAttribute("disabled"), false, "does not set [disabled] attribute")
610-
assert.equal(editor.hasAttribute("contenteditable"), true, "adds [contenteditable] attribute")
611-
})
612-
613-
test("does not receive focus when :disabled", () => {
614-
const activeEditor = document.getElementById("editor-with-input-form")
615-
const editor = document.getElementById("editor-within-fieldset")
616-
617-
activeEditor.focus()
618-
editor.disabled = true
619-
editor.focus()
620-
621-
assert.equal(activeEditor, document.activeElement, "disabled editor does not receive focus")
622-
})
623-
624-
test("disabled editor does not encode its value when the form is submitted", () => {
625-
const editor = document.getElementById("editor-with-ancestor-form")
626-
const form = editor.form
627-
628-
editor.inputElement.value = "Hello world"
629-
editor.disabled = true
630-
631-
assert.deepEqual({}, Object.fromEntries(new FormData(form).entries()), "does not write to FormData")
632-
})
633541
})
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export default () =>
22
`<label id="label-1" for="editor"><span>Label 1</span></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>`
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>`
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
export default () =>
22
`<form id="ancestor-form">
3-
<trix-editor id="editor-with-ancestor-form" name="editor-with-ancestor-form"></trix-editor>
3+
<trix-editor id="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>
12-
<fieldset id="fieldset"><trix-editor id="editor-within-fieldset"></fieldset>`
11+
<trix-editor id="editor-with-no-form"></trix-editor>`

src/trix/elements/trix_editor_element.js

+50-26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as config from "trix/config"
22

33
import {
4+
findClosestElementFromNode,
45
handleEvent,
56
handleEventOnce,
67
installDefaultCSSForTagName,
@@ -160,14 +161,6 @@ installDefaultCSSForTagName("trix-editor", `\
160161
}`)
161162

162163
export default class TrixEditorElement extends HTMLElement {
163-
static formAssociated = true
164-
165-
#internals
166-
167-
constructor() {
168-
super()
169-
this.#internals = this.attachInternals()
170-
}
171164

172165
// Properties
173166

@@ -181,7 +174,19 @@ export default class TrixEditorElement extends HTMLElement {
181174
}
182175

183176
get labels() {
184-
return this.#internals.labels
177+
const labels = []
178+
if (this.id && this.ownerDocument) {
179+
labels.push(...Array.from(this.ownerDocument.querySelectorAll(`label[for='${this.id}']`) || []))
180+
}
181+
182+
const label = findClosestElementFromNode(this, { matchingSelector: "label" })
183+
if (label) {
184+
if ([ this, null ].includes(label.control)) {
185+
labels.push(label)
186+
}
187+
}
188+
189+
return labels
185190
}
186191

187192
get toolbarElement() {
@@ -233,18 +238,6 @@ export default class TrixEditorElement extends HTMLElement {
233238
this.editor?.loadHTML(this.defaultValue)
234239
}
235240

236-
get disabled() {
237-
return this.inputElement.disabled
238-
}
239-
240-
set disabled(value) {
241-
this.toggleAttribute("disabled")
242-
}
243-
244-
get type() {
245-
return this.localName
246-
}
247-
248241
// Controller delegate methods
249242

250243
notify(message, data) {
@@ -276,23 +269,54 @@ export default class TrixEditorElement extends HTMLElement {
276269
requestAnimationFrame(() => triggerEvent("trix-initialize", { onElement: this }))
277270
}
278271
this.editorController.registerSelectionManager()
272+
this.registerResetListener()
273+
this.registerClickListener()
279274
autofocus(this)
280275
}
281276
}
282277

283278
disconnectedCallback() {
284279
this.editorController?.unregisterSelectionManager()
280+
this.unregisterResetListener()
281+
return this.unregisterClickListener()
285282
}
286283

287284
// Form support
288285

289-
formDisabledCallback(disabled) {
290-
this.inputElement.disabled = disabled
291-
this.toggleAttribute("contenteditable", !disabled)
286+
registerResetListener() {
287+
this.resetListener = this.resetBubbled.bind(this)
288+
return window.addEventListener("reset", this.resetListener, false)
289+
}
290+
291+
unregisterResetListener() {
292+
return window.removeEventListener("reset", this.resetListener, false)
293+
}
294+
295+
registerClickListener() {
296+
this.clickListener = this.clickBubbled.bind(this)
297+
return window.addEventListener("click", this.clickListener, false)
298+
}
299+
300+
unregisterClickListener() {
301+
return window.removeEventListener("click", this.clickListener, false)
302+
}
303+
304+
resetBubbled(event) {
305+
if (event.defaultPrevented) return
306+
if (event.target !== this.form) return
307+
return this.reset()
292308
}
293309

294-
formResetCallback() {
295-
this.reset()
310+
clickBubbled(event) {
311+
if (event.defaultPrevented) return
312+
if (this.contains(event.target)) return
313+
314+
const label = findClosestElementFromNode(event.target, { matchingSelector: "label" })
315+
if (!label) return
316+
317+
if (!Array.from(this.labels).includes(label)) return
318+
319+
return this.focus()
296320
}
297321

298322
reset() {

0 commit comments

Comments
 (0)