"use strict";
const HTMLElementImpl = require("./HTMLElement-impl").implementation;
const DefaultConstraintValidationImpl =
require("../constraint-validation/DefaultConstraintValidation-impl").implementation;
const ValidityState = require("../generated/ValidityState");
const { mixin } = require("../../utils");
const DOMException = require("domexception/webidl2js-wrapper");
const { cloningSteps } = require("../helpers/internal-constants");
const { isDisabled, getLabelsForLabelable, formOwner } = require("../helpers/form-controls");
const { childTextContent } = require("../helpers/text");
const { fireAnEvent } = require("../helpers/events");
class HTMLTextAreaElementImpl extends HTMLElementImpl {
constructor(globalObject, args, privateData) {
super(globalObject, args, privateData);
this._selectionStart = this._selectionEnd = 0;
this._selectionDirection = "none";
this._rawValue = "";
this._dirtyValue = false;
this._customValidityErrorMessage = "";
this._labels = null;
}
_formReset() {
this._rawValue = childTextContent(this);
this._dirtyValue = false;
}
_getAPIValue() {
return this._rawValue.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
}
// https://html.spec.whatwg.org/multipage/form-elements.html#textarea-wrapping-transformation
_getValue() {
const apiValue = this._getAPIValue();
const wrap = this.getAttributeNS(null, "wrap");
return wrap === "hard" ?
textareaWrappingTransformation(apiValue, this.cols) :
apiValue;
}
_childTextContentChangeSteps() {
super._childTextContentChangeSteps();
if (this._dirtyValue === false) {
this._rawValue = childTextContent(this);
}
}
get labels() {
return getLabelsForLabelable(this);
}
get form() {
return formOwner(this);
}
get defaultValue() {
return childTextContent(this);
}
set defaultValue(val) {
this.textContent = val;
}
get value() {
return this._getAPIValue();
}
set value(val) {
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-value
const oldAPIValue = this._getAPIValue();
this._rawValue = val;
this._dirtyValue = true;
if (oldAPIValue !== this._getAPIValue()) {
this._selectionStart = this._selectionEnd = this._getValueLength();
this._selectionDirection = "none";
}
}
get textLength() {
return this.value.length; // code unit length (16 bit)
}
get type() {
return "textarea";
}
_dispatchSelectEvent() {
fireAnEvent("select", this, undefined, { bubbles: true, cancelable: true });
}
_getValueLength() {
return typeof this.value === "string" ? this.value.length : 0;
}
select() {
this._selectionStart = 0;
this._selectionEnd = this._getValueLength();
this._selectionDirection = "none";
this._dispatchSelectEvent();
}
get selectionStart() {
return this._selectionStart;
}
set selectionStart(start) {
this.setSelectionRange(start, Math.max(start, this._selectionEnd), this._selectionDirection);
}
get selectionEnd() {
return this._selectionEnd;
}
set selectionEnd(end) {
this.setSelectionRange(this._selectionStart, end, this._selectionDirection);
}
get selectionDirection() {
return this._selectionDirection;
}
set selectionDirection(dir) {
this.setSelectionRange(this._selectionStart, this._selectionEnd, dir);
}
setSelectionRange(start, end, dir) {
this._selectionEnd = Math.min(end, this._getValueLength());
this._selectionStart = Math.min(start, this._selectionEnd);
this._selectionDirection = dir === "forward" || dir === "backward" ? dir : "none";
this._dispatchSelectEvent();
}
setRangeText(repl, start, end, selectionMode = "preserve") {
if (arguments.length < 2) {
start = this._selectionStart;
end = this._selectionEnd;
} else if (start > end) {
throw DOMException.create(this._globalObject, ["The index is not in the allowed range.", "IndexSizeError"]);
}
start = Math.min(start, this._getValueLength());
end = Math.min(end, this._getValueLength());
const val = this.value;
let selStart = this._selectionStart;
let selEnd = this._selectionEnd;
this.value = val.slice(0, start) + repl + val.slice(end);
const newEnd = start + this.value.length;
if (selectionMode === "select") {
this.setSelectionRange(start, newEnd);
} else if (selectionMode === "start") {
this.setSelectionRange(start, start);
} else if (selectionMode === "end") {
this.setSelectionRange(newEnd, newEnd);
} else { // preserve
const delta = repl.length - (end - start);
if (selStart > end) {
selStart += delta;
} else if (selStart > start) {
selStart = start;
}
if (selEnd > end) {
selEnd += delta;
} else if (selEnd > start) {
selEnd = newEnd;
}
this.setSelectionRange(selStart, selEnd);
}
}
get cols() {
if (!this.hasAttributeNS(null, "cols")) {
return 20;
}
return parseInt(this.getAttributeNS(null, "cols"));
}
set cols(value) {
if (value <= 0) {
throw DOMException.create(this._globalObject, ["The index is not in the allowed range.", "IndexSizeError"]);
}
this.setAttributeNS(null, "cols", String(value));
}
get rows() {
if (!this.hasAttributeNS(null, "rows")) {
return 2;
}
return parseInt(this.getAttributeNS(null, "rows"));
}
set rows(value) {
if (value <= 0) {
throw DOMException.create(this._globalObject, ["The index is not in the allowed range.", "IndexSizeError"]);
}
this.setAttributeNS(null, "rows", String(value));
}
_barredFromConstraintValidationSpecialization() {
return this.hasAttributeNS(null, "readonly");
}
get _mutable() {
return !isDisabled(this) && !this.hasAttributeNS(null, "readonly");
}
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-required
get validity() {
if (!this._validity) {
const state = {
valueMissing: () => this.hasAttributeNS(null, "required") && this._mutable && this.value === ""
};
this._validity = ValidityState.createImpl(this._globalObject, [], {
element: this,
state
});
}
return this._validity;
}
[cloningSteps](copy, node) {
copy._dirtyValue = node._dirtyValue;
copy._rawValue = node._rawValue;
}
}
mixin(HTMLTextAreaElementImpl.prototype, DefaultConstraintValidationImpl.prototype);
module.exports = {
implementation: HTMLTextAreaElementImpl
};
function textareaWrappingTransformation(text, cols) {
let lineStart = 0;
let lineEnd = text.indexOf("\n");
if (lineEnd === -1) {
lineEnd = text.length;
}
while (lineStart < text.length) {
const lineLength = lineEnd - lineStart;
if (lineLength > cols) {
// split the line
lineEnd = lineStart + cols;
text = text.slice(0, lineEnd) + "\n" + text.slice(lineEnd);
}
// move to next line
lineStart = lineEnd + 1; // step over the newline
lineEnd = text.indexOf("\n", lineStart);
if (lineEnd === -1) {
lineEnd = text.length;
}
}
return text;
}