Automattic/harper · error · Error

TextFieldRange expects an HTMLTextAreaElement or HTMLInputEl

Error message

TextFieldRange expects an HTMLTextAreaElement or HTMLInputElement

What it means

The TextFieldRange constructor validates its first argument with instanceof, requiring an HTMLTextAreaElement or HTMLInputElement. Any other element (div, contenteditable span, etc.) cannot back a text-field range, so the constructor fails fast instead of misbehaving later.

Source

Thrown at packages/lint-framework/src/lint/TextFieldRange.ts:35

		}
	> = new WeakMap();

	private arena: { mirror: HTMLDivElement; text: Text; refs: number };

	/**
	 * Create a range-like object for a given text input field.
	 * @param field - A HTMLTextAreaElement or a HTMLInputElement (of type "text").
	 * @param startOffset - The starting character index.
	 * @param endOffset - The ending character index.
	 */
	constructor(
		field: HTMLTextAreaElement | HTMLInputElement,
		startOffset: number,
		endOffset: number,
	) {
		// In this case we assume the caller provided a text field
		if (!(field instanceof HTMLTextAreaElement || field instanceof HTMLInputElement)) {
			throw new Error('TextFieldRange expects an HTMLTextAreaElement or HTMLInputElement');
		}
		this.field = field;
		this.startOffset = startOffset;
		this.endOffset = endOffset;
		this.arena = TextFieldRange.ensureArena(this.field);
		this.arena.refs++;
	}

	/**
	 * Creates (or reuses) an off-screen mirror element that mimics the field's styles
	 * and positions it exactly over the field.
	 */
	private static ensureArena(field: HTMLTextAreaElement | HTMLInputElement): {
		mirror: HTMLDivElement;
		text: Text;
		refs: number;
	} {
		const existing = TextFieldRange.arenas.get(field);

View on GitHub (pinned to 5fe7d5ab76)

Solutions

  1. Pass the actual <textarea> or <input> element to the constructor.
  2. If you have a contenteditable surface, use the framework's non-text-field API (addTarget with an HTMLElement) instead of TextFieldRange.
  3. Narrow your element selection (querySelector('textarea, input')) before constructing.

Example fix

// before
const range = new TextFieldRange(document.querySelector('.editor'), 0, 10);
// after
const el = document.querySelector('textarea.editor, input.editor');
if (!el) throw new Error('No text field found');
const range = new TextFieldRange(el, 0, 10);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(el instanceof HTMLTextAreaElement || el instanceof HTMLInputElement)) {
  throw new TypeError('TextFieldRange requires a textarea or input element');
}

Type guard

function isTextField(el: Element): el is HTMLTextAreaElement | HTMLInputElement {
  return el instanceof HTMLTextAreaElement || el instanceof HTMLInputElement;
}

Try / catch

try {
  const range = new TextFieldRange(field, start, end);
} catch (e) {
  console.error('TextFieldRange requires a textarea/input; got', field);
}

Prevention

When it happens

Trigger: new TextFieldRange(el, start, end) where el is not a <textarea> or <input>; common with contenteditable divs, generic HTMLElements, or elements fetched by a broad selector.

Common situations: Attaching the linter to a rich-text/contenteditable editor instead of a plain text field, passing a wrapper div, or running in a non-DOM environment (SSR) where instanceof checks fail or the element is a stub.

Related errors


AI-assisted analysis of Automattic/harper@5fe7d5ab76 (2026-09-06). Data as JSON: /api/errors/8ac82ece220057cc. Report an issue: GitHub.