Proposal: compositionborder attribute
Nobody has claimed this yet.
- Dominant language
- HTML
- Stars
- 202
- Forks
- 43
- PR merge metrics
- No merged PRs in 30d
Description
The idea is to allow a compositionborder=true attribute to elements within a content editable container. This signals to the browser that composition should not cross the element's boundary. When IME composition begins, the initial composition text is bounded by compositionborder. Input event Ranges will necessarily be within those bounds. An input event Range outside the range would necessarily trigger a compositionend event first.
Purpose
Composition events cannot be handled manually (specifically preventDefault is a not allowed in the InputEvents spec). During composition editing, browsers may add/delete nodes, or move textContent to adjacent nodes. This can cause a "style jank" where a style is modified and we must wait until composition has ended to repair the styles. This is only a problem with styled text, as with plain text we can repair the DOM afterwards without any visible jank. This also only affects compositions, since regular events we can cancel and manually edit the DOM.
Here are some examples of unpredictable browser behavior around compositions. Each of these result in style jank, but could be solved by adding compositionborder=true to one or more elements.
(Vertical bar indicates caret position)
1)
the suffix -esque, as in pictur<u>esqu|</u>
Autocomplete "picturesque" in Chrome, or typing "e" in Firefox Android will result in:
the suffix -esque, as in <u>picturesque</u>
ruining our intended styling.
2)
In a code editor, the same might occur with:
<var>let</var> x = <var>shor</var>-<var>ter|</var>
The browser does not know that we're dealing with programming code, and so thinks shor-ter is a single word/phrase that should be composed together with the IME.
This particular example can be observed in CodeMirror when editing on Android; when typing this-some_var, syntax highlighting is not properly applied until after composition finishes.
3)
<b>some text </b><u>|</u>
Typing "here" will result in
<b>some text here|</b><u></u>
The browser treats the contents as one style-agnostic text phrase, ignoring that the cursor is inside <u> and retargeting it to <b> instead. While normal input events we can cancel and retarget it back to <u>, with composition events we can't edit the DOM until after composition has completed; and by that point, it is too late, since the text has already been styled bold.
4)
<template id="custom-element">
<img src="avatar.png>
<b>~ <slot></slot> ~</b>
</template>
<custom-element>John Doe</custom-element>is| a programmer from Orlando
(Assuming custom-element has a shadow root given by the template). Here, browsers interpret "Doeis" as the phrase for composition, even though the actual text being displayed is "~ John Doe ~ is...". A similar example can be crafted for before/after CSS pseudo elements.
Usage
Add compositionborder=true to an element to restrict IME composition. For example, given a caret given by vertical bar, the brackets indicate the composition bounds:
<div compositionborder=true>[
<span>foo| bar<span>
<div>]<span compositionborder=true>baz</span></div>
</div>
In this example, the browser would insert text inside <u> instead of crossing to another element:
<b>foo</b><u compositionborder=true>[|]</u>
Using the syntax of insertAdjacentElement, the composition boundary is the first position preceding/following the anchor whose element compositionborder is true or isContentEditable is false.
Using the node-boundary package, code for the range would look like:
import {Boundary, BoundaryRange} from "node-boundary";
/** Get the range for composition given an anchor point
* @param {Boundary} anchor - the DOM position whose composition range we want to find
* @param {Node} [root] a root element that is an additional bounds constraint (e.g. the root
* contenteditable); anchor should be inside root
* @returns {BoundaryRange} a range giving the composition boundary start/end; can be collapsed,
* in which case composition would insert a new Text node at that point
*/
function composition_bounds(anchor, root){
// to detect whether isContentEditable changes, we need the initial state at this anchor
const is_boundary = n => {
return n === root || (!(n instanceof CharacterData) && (
n.compositionBorder === "true" ||
!n.isContentEditable !== anchor_editable));
};
const range = new BoundaryRange();
// traverse left/right to get bounds
const lanchor = anchor.clone();
for (const _ of lanchor.previousNodes()){
if (is_boundary(lanchor.node))
break;
}
range.start = lanchor;
const ranchor = anchor.clone();
for (const _ of ranchor.nextNodes()){
if (is_boundary(ranchor.node))
break;
}
range.end = ranchor;
return range;
}
Implementation
The idea here is a simple change to encapsulate composition events so they are more controllable. While browser devs have made clear full composition handling cannot be supported easily, this should be a simple addition which I believe solves the majority of complaints surrounding composition events. Browsers already seem to implement some logic surrounding this, although it is not exposed; for example, Firefox appears to exhibit compositionborder=true behavior for <article>.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No repository files or tests are named. Start with the Usage and Implementation sections, then review the referenced InputEvents behavior, insertAdjacentElement boundary model, and node-boundary example. Done would require a settled, implementable specification for the compositionborder attribute and its composition range semantics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- html, javascript
- Domain
- frontend, web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100