in src/display/annotation_layer.js [1238:1575]
render() {
const storage = this.annotationStorage;
const id = this.data.id;
this.container.classList.add("textWidgetAnnotation");
let element = null;
if (this.renderForms) {
// NOTE: We cannot set the values using `element.value` below, since it
// prevents the AnnotationLayer rasterizer in `test/driver.js`
// from parsing the elements correctly for the reference tests.
const storedData = storage.getValue(id, {
value: this.data.fieldValue,
});
let textContent = storedData.value || "";
const maxLen = storage.getValue(id, {
charLimit: this.data.maxLen,
}).charLimit;
if (maxLen && textContent.length > maxLen) {
textContent = textContent.slice(0, maxLen);
}
let fieldFormattedValues =
storedData.formattedValue || this.data.textContent?.join("\n") || null;
if (fieldFormattedValues && this.data.comb) {
fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, "");
}
const elementData = {
userValue: textContent,
formattedValue: fieldFormattedValues,
lastCommittedValue: null,
commitKey: 1,
focused: false,
};
if (this.data.multiLine) {
element = document.createElement("textarea");
element.textContent = fieldFormattedValues ?? textContent;
if (this.data.doNotScroll) {
element.style.overflowY = "hidden";
}
} else {
element = document.createElement("input");
element.type = this.data.password ? "password" : "text";
element.setAttribute("value", fieldFormattedValues ?? textContent);
if (this.data.doNotScroll) {
element.style.overflowX = "hidden";
}
}
if (this.data.hasOwnCanvas) {
element.hidden = true;
}
GetElementsByNameSet.add(element);
element.setAttribute("data-element-id", id);
element.disabled = this.data.readOnly;
element.name = this.data.fieldName;
element.tabIndex = DEFAULT_TAB_INDEX;
this._setRequired(element, this.data.required);
if (maxLen) {
element.maxLength = maxLen;
}
element.addEventListener("input", event => {
storage.setValue(id, { value: event.target.value });
this.setPropertyOnSiblings(
element,
"value",
event.target.value,
"value"
);
elementData.formattedValue = null;
});
element.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue ?? "";
element.value = elementData.userValue = defaultValue;
elementData.formattedValue = null;
});
let blurListener = event => {
const { formattedValue } = elementData;
if (formattedValue !== null && formattedValue !== undefined) {
event.target.value = formattedValue;
}
// Reset the cursor position to the start of the field (issue 12359).
event.target.scrollLeft = 0;
};
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("focus", event => {
if (elementData.focused) {
return;
}
const { target } = event;
if (elementData.userValue) {
target.value = elementData.userValue;
}
elementData.lastCommittedValue = target.value;
elementData.commitKey = 1;
if (!this.data.actions?.Focus) {
elementData.focused = true;
}
});
element.addEventListener("updatefromsandbox", jsEvent => {
this.showElementAndHideCanvas(jsEvent.target);
const actions = {
value(event) {
elementData.userValue = event.detail.value ?? "";
storage.setValue(id, { value: elementData.userValue.toString() });
event.target.value = elementData.userValue;
},
formattedValue(event) {
const { formattedValue } = event.detail;
elementData.formattedValue = formattedValue;
if (
formattedValue !== null &&
formattedValue !== undefined &&
event.target !== document.activeElement
) {
// Input hasn't the focus so display formatted string
event.target.value = formattedValue;
}
storage.setValue(id, {
formattedValue,
});
},
selRange(event) {
event.target.setSelectionRange(...event.detail.selRange);
},
charLimit: event => {
const { charLimit } = event.detail;
const { target } = event;
if (charLimit === 0) {
target.removeAttribute("maxLength");
return;
}
target.setAttribute("maxLength", charLimit);
let value = elementData.userValue;
if (!value || value.length <= charLimit) {
return;
}
value = value.slice(0, charLimit);
target.value = elementData.userValue = value;
storage.setValue(id, { value });
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
willCommit: true,
commitKey: 1,
selStart: target.selectionStart,
selEnd: target.selectionEnd,
},
});
},
};
this._dispatchEventFromSandbox(actions, jsEvent);
});
// Even if the field hasn't any actions
// leaving it can still trigger some actions with Calculate
element.addEventListener("keydown", event => {
elementData.commitKey = 1;
// If the key is one of Escape, Enter then the data are committed.
// If we've a Tab then data will be committed on blur.
let commitKey = -1;
if (event.key === "Escape") {
commitKey = 0;
} else if (event.key === "Enter" && !this.data.multiLine) {
// When we've a multiline field, "Enter" key is a key as the other
// hence we don't commit the data (Acrobat behaves the same way)
// (see issue #15627).
commitKey = 2;
} else if (event.key === "Tab") {
elementData.commitKey = 3;
}
if (commitKey === -1) {
return;
}
const { value } = event.target;
if (elementData.lastCommittedValue === value) {
return;
}
elementData.lastCommittedValue = value;
// Save the entered value
elementData.userValue = value;
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
willCommit: true,
commitKey,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd,
},
});
});
const _blurListener = blurListener;
blurListener = null;
element.addEventListener("blur", event => {
if (!elementData.focused || !event.relatedTarget) {
return;
}
if (!this.data.actions?.Blur) {
elementData.focused = false;
}
const { value } = event.target;
elementData.userValue = value;
if (elementData.lastCommittedValue !== value) {
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
willCommit: true,
commitKey: elementData.commitKey,
selStart: event.target.selectionStart,
selEnd: event.target.selectionEnd,
},
});
}
_blurListener(event);
});
if (this.data.actions?.Keystroke) {
element.addEventListener("beforeinput", event => {
elementData.lastCommittedValue = null;
const { data, target } = event;
const { value, selectionStart, selectionEnd } = target;
let selStart = selectionStart,
selEnd = selectionEnd;
switch (event.inputType) {
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
case "deleteWordBackward": {
const match = value
.substring(0, selectionStart)
.match(/\w*[^\w]*$/);
if (match) {
selStart -= match[0].length;
}
break;
}
case "deleteWordForward": {
const match = value
.substring(selectionStart)
.match(/^[^\w]*\w*/);
if (match) {
selEnd += match[0].length;
}
break;
}
case "deleteContentBackward":
if (selectionStart === selectionEnd) {
selStart -= 1;
}
break;
case "deleteContentForward":
if (selectionStart === selectionEnd) {
selEnd += 1;
}
break;
}
// We handle the event ourselves.
event.preventDefault();
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id,
name: "Keystroke",
value,
change: data || "",
willCommit: false,
selStart,
selEnd,
},
});
});
}
this._setEventListeners(
element,
elementData,
[
["focus", "Focus"],
["blur", "Blur"],
["mousedown", "Mouse Down"],
["mouseenter", "Mouse Enter"],
["mouseleave", "Mouse Exit"],
["mouseup", "Mouse Up"],
],
event => event.target.value
);
}
if (blurListener) {
element.addEventListener("blur", blurListener);
}
if (this.data.comb) {
const fieldWidth = this.data.rect[2] - this.data.rect[0];
const combWidth = fieldWidth / maxLen;
element.classList.add("comb");
element.style.letterSpacing = `calc(${combWidth}px * var(--total-scale-factor) - 1ch)`;
}
} else {
element = document.createElement("div");
element.textContent = this.data.fieldValue;
element.style.verticalAlign = "middle";
element.style.display = "table-cell";
if (this.data.hasOwnCanvas) {
element.hidden = true;
}
}
this._setTextStyle(element);
this._setBackgroundColor(element);
this._setDefaultPropertiesFromJS(element);
this.container.append(element);
return this.container;
}