|
15 | 15 |
|
16 | 16 | var codeInput = { |
17 | 17 | /** |
18 | | - * A list of attributes that will trigger the |
| 18 | + * A list of attributes that will trigger functionality in the |
19 | 19 | * `codeInput.CodeInput.attributeChangedCallback` |
20 | 20 | * when modified in a code-input element. This |
21 | 21 | * does not include events, which are handled in |
22 | 22 | * `codeInput.CodeInput.addEventListener` and |
23 | 23 | * `codeInput.CodeInput.removeEventListener`. |
| 24 | + * |
| 25 | + * This does not include those listed in `codeInput.textareaSyncAttributes` that only synchronise with the textarea element. |
24 | 26 | */ |
25 | 27 | observedAttributes: [ |
26 | 28 | "value", |
@@ -628,6 +630,7 @@ var codeInput = { |
628 | 630 | let textareaAttributeNames = fallbackTextarea.getAttributeNames(); |
629 | 631 | for(let i = 0; i < textareaAttributeNames.length; i++) { |
630 | 632 | const attr = textareaAttributeNames[i]; |
| 633 | + |
631 | 634 | if(!this.hasAttribute(attr)) { |
632 | 635 | this.setAttribute(attr, fallbackTextarea.getAttribute(attr)); |
633 | 636 | } |
@@ -793,6 +796,11 @@ var codeInput = { |
793 | 796 | return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName)); |
794 | 797 | } |
795 | 798 | } |
| 799 | + for(let i = 0; i < codeInput.textareaSyncAttributes.length; i++) { |
| 800 | + if (mutation.attributeName == codeInput.textareaSyncAttributes[i]) { |
| 801 | + return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName)); |
| 802 | + } |
| 803 | + } |
796 | 804 | if (mutation.attributeName.substring(0, 5) == "aria-") { |
797 | 805 | return this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, super.getAttribute(mutation.attributeName)); |
798 | 806 | } |
@@ -963,117 +971,167 @@ var codeInput = { |
963 | 971 | } |
964 | 972 | } |
965 | 973 |
|
| 974 | + //------------------------------------------- |
| 975 | + //----------- Textarea interface ------------ |
| 976 | + //------------------------------------------- |
| 977 | + // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement |
| 978 | + // Attributes defined at codeInput.textareaSyncAttributes |
| 979 | + |
966 | 980 | /** |
967 | | - * Get the text contents of the code-input element. |
| 981 | + * Get the JavaScript property from the internal textarea |
| 982 | + * element, given its name and a defaultValue to return |
| 983 | + * when no textarea is present (undefined to make it throw |
| 984 | + * an error instead). |
| 985 | + * |
| 986 | + * For internal use - treat the code-input element as a |
| 987 | + * textarea for the standard properties (e.g. document. |
| 988 | + * querySelector("code-input").defaultValue). |
968 | 989 | */ |
969 | | - get value() { |
| 990 | + getTextareaProperty(name, defaultValue=undefined) { |
970 | 991 | if(this.textareaElement) { |
971 | | - // Get from editable textarea element |
972 | | - return this.textareaElement.value; |
| 992 | + return this.textareaElement[name]; |
973 | 993 | } else { |
974 | 994 | // Unregistered |
975 | 995 | const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]"); |
976 | 996 | if(fallbackTextarea) { |
977 | | - return fallbackTextarea.value; |
| 997 | + return fallbackTextarea[name]; |
978 | 998 | } else { |
979 | | - return this.innerHTML; |
| 999 | + if(defaultValue === undefined) { |
| 1000 | + throw new Error("Cannot get "+name+" of an unregistered code-input element without a data-code-input-fallback textarea."); |
| 1001 | + } |
| 1002 | + return defaultValue; |
980 | 1003 | } |
981 | 1004 | } |
982 | 1005 | } |
983 | 1006 | /** |
984 | | - * Set the text contents of the code-input element. |
985 | | - * @param {string} val - New text contents |
| 1007 | + * Set the JavaScript property of the internal textarea |
| 1008 | + * element, given its name and value. |
| 1009 | + * |
| 1010 | + * If there is no registered or fallback textarea and errorIfCannot is |
| 1011 | + * false, return false (otherwise true); If there is no registered or |
| 1012 | + * fallback textarea and errorIfCannot is true, throw an error. |
| 1013 | + * |
| 1014 | + * For internal use - treat the code-input element as a |
| 1015 | + * textarea for the standard properties (e.g. document. |
| 1016 | + * querySelector("code-input").defaultValue). |
986 | 1017 | */ |
987 | | - set value(val) { |
988 | | - if (val === null || val === undefined) { |
989 | | - val = ""; |
990 | | - } |
| 1018 | + setTextareaProperty(name, value, errorIfCannot=true) { |
991 | 1019 | if(this.textareaElement) { |
992 | | - // Save in editable textarea element |
993 | | - this.textareaElement.value = val; |
994 | | - // Trigger highlight |
995 | | - this.scheduleHighlight(); |
| 1020 | + this.textareaElement[name] = value; |
996 | 1021 | } else { |
997 | 1022 | // Unregistered |
998 | 1023 | const fallbackTextarea = this.querySelector("textarea[data-code-input-fallback]"); |
999 | 1024 | if(fallbackTextarea) { |
1000 | | - fallbackTextarea.value = val; |
| 1025 | + fallbackTextarea[name] = value; |
1001 | 1026 | } else { |
1002 | | - this.innerHTML = val; |
| 1027 | + if(!errorIfCannot) return false; |
| 1028 | + throw new Error("Cannot set "+name+" of an unregistered code-input element without a data-code-input-fallback textarea."); |
1003 | 1029 | } |
1004 | 1030 | } |
| 1031 | + return true; |
1005 | 1032 | } |
1006 | 1033 |
|
1007 | | - // Synchronise blur and focus |
1008 | | - focus(options={}) { |
1009 | | - return this.textareaElement.focus(options); |
1010 | | - } |
1011 | | - blur(options={}) { |
1012 | | - return this.textareaElement.blur(options); |
| 1034 | + get autocomplete() { return this.getAttribute("autocomplete"); } |
| 1035 | + set autocomplete(val) { return this.setAttribute("autocomplete", val); } |
| 1036 | + get cols() { return this.getTextareaProperty("cols", Number(this.getAttribute("cols"))); } |
| 1037 | + set cols(val) { this.setAttribute("cols", val); } |
| 1038 | + get defaultValue() { return this.initialValue; } |
| 1039 | + set defaultValue(val) { this.initialValue = val; } |
| 1040 | + get textContent() { return this.initialValue; } |
| 1041 | + set textContent(val) { this.initialValue = val; } |
| 1042 | + get dirName() { return this.getAttribute("dirName") || ""; } |
| 1043 | + set dirName(val) { this.setAttribute("dirname", val); } |
| 1044 | + get disabled() { return this.hasAttribute("disabled"); } |
| 1045 | + set disabled(val) { |
| 1046 | + if(val) { |
| 1047 | + this.setAttribute("disabled", true); |
| 1048 | + } else { |
| 1049 | + this.removeAttribute("disabled"); |
| 1050 | + } |
1013 | 1051 | } |
1014 | | - |
1015 | | - /** |
1016 | | - * Get the placeholder of the code-input element that appears |
1017 | | - * when no code has been entered. |
1018 | | - */ |
1019 | | - get placeholder() { |
1020 | | - return this.getAttribute("placeholder"); |
| 1052 | + get form() { return this.getTextareaProperty("form"); } |
| 1053 | + get labels() { return this.getTextareaProperty("labels"); } |
| 1054 | + get maxLength() { |
| 1055 | + const result = Number(this.getAttribute("maxlength")); |
| 1056 | + if(isNaN(result)) { |
| 1057 | + return -1; |
| 1058 | + } |
| 1059 | + return result; |
1021 | 1060 | } |
1022 | | - /** |
1023 | | - * Set the placeholder of the code-input element that appears |
1024 | | - * when no code has been entered. |
1025 | | - * @param {string} val - New placeholder |
1026 | | - */ |
1027 | | - set placeholder(val) { |
1028 | | - return this.setAttribute("placeholder", val); |
| 1061 | + set maxLength(val) { |
| 1062 | + if(val == -1) { |
| 1063 | + this.removeAttribute("maxlength"); |
| 1064 | + } else { |
| 1065 | + this.setAttribute("maxlength", val); |
| 1066 | + } |
1029 | 1067 | } |
1030 | | - |
1031 | | - /** |
1032 | | - * Returns a ValidityState object that represents the validity states of an element. |
1033 | | - * |
1034 | | - * See `HTMLTextAreaElement.validity` |
1035 | | - */ |
1036 | | - get validity() { |
1037 | | - return this.textareaElement.validity; |
| 1068 | + get minLength() { |
| 1069 | + const result = Number(this.getAttribute("minlength")); |
| 1070 | + if(isNaN(result)) { |
| 1071 | + return -1; |
| 1072 | + } |
| 1073 | + return result; |
1038 | 1074 | } |
1039 | | - |
1040 | | - /** |
1041 | | - * Returns the error message that would be displayed if the user submits the form, or an empty string if no error message. |
1042 | | - * It also triggers the standard error message, such as "this is a required field". The result is that the user sees validation |
1043 | | - * messages without actually submitting. |
1044 | | - * |
1045 | | - * See `HTMLTextAreaElement.validationMessage` |
1046 | | - */ |
1047 | | - get validationMessage() { |
1048 | | - return this.textareaElement.validationMessage; |
| 1075 | + set minLength(val) { |
| 1076 | + if(val == -1) { |
| 1077 | + this.removeAttribute("minlength"); |
| 1078 | + } else { |
| 1079 | + this.setAttribute("minlength", val); |
| 1080 | + } |
1049 | 1081 | } |
1050 | | - |
1051 | | - /** |
1052 | | - * Sets a custom error message that is displayed when a form is submitted. |
1053 | | - * |
1054 | | - * See `HTMLTextAreaElement.setCustomValidity` |
1055 | | - * @param error Sets a custom error message that is displayed when a form is submitted. |
1056 | | - */ |
1057 | | - setCustomValidity(error) { |
1058 | | - return this.textareaElement.setCustomValidity(error); |
| 1082 | + get name() { return this.getAttribute("name") || ""; } |
| 1083 | + set name(val) { this.setAttribute("name", val); } |
| 1084 | + get placeholder() { return this.getAttribute("placeholder") || ""; } |
| 1085 | + set placeholder(val) { this.setAttribute("placeholder", val); } |
| 1086 | + get readOnly() { return this.hasAttribute("readonly"); } |
| 1087 | + set readOnly(val) { |
| 1088 | + if(val) { |
| 1089 | + this.setAttribute("readonly", true); |
| 1090 | + } else { |
| 1091 | + this.removeAttribute("readonly"); |
| 1092 | + } |
1059 | 1093 | } |
1060 | | - |
1061 | | - /** |
1062 | | - * Returns whether a form will validate when it is submitted, |
1063 | | - * without having to submit it. |
1064 | | - * |
1065 | | - * See `HTMLTextAreaElement.checkValidity` |
1066 | | - */ |
1067 | | - checkValidity() { |
1068 | | - return this.textareaElement.checkValidity(); |
| 1094 | + get required() { return this.hasAttribute("readonly"); } |
| 1095 | + set required(val) { |
| 1096 | + if(val) { |
| 1097 | + this.setAttribute("readonly", true); |
| 1098 | + } else { |
| 1099 | + this.removeAttribute("readonly"); |
| 1100 | + } |
1069 | 1101 | } |
1070 | | - |
1071 | | - /** |
1072 | | - * See `HTMLTextAreaElement.reportValidity` |
1073 | | - */ |
1074 | | - reportValidity() { |
1075 | | - return this.textareaElement.reportValidity(); |
| 1102 | + get rows() { return this.getTextareaProperty("rows", Number(this.getAttribute("rows"))); } |
| 1103 | + set rows(val) { this.setAttribute("rows", val); } |
| 1104 | + get selectionDirection() { return this.getTextareaProperty("selectionDirection"); } |
| 1105 | + set selectionDirection(val) { this.setTextareaProperty("selectionDirection", val); } |
| 1106 | + get selectionEnd() { return this.getTextareaProperty("selectionEnd"); } |
| 1107 | + set selectionEnd(val) { this.setTextareaProperty("selectionEnd", val); } |
| 1108 | + get selectionStart() { return this.getTextareaProperty("selectionStart"); } |
| 1109 | + set selectionStart(val) { this.setTextareaProperty("selectionStart", val); } |
| 1110 | + get textLength() { return this.value.length; } |
| 1111 | + get type() { return "textarea"; } // Mimics textarea |
| 1112 | + get validationMessage() { return this.getTextareaProperty("validationMessage"); } |
| 1113 | + get validity() { return this.getTextareaProperty("validationMessage"); } |
| 1114 | + get value() { return this.getTextareaProperty("value", this.getAttribute("value") || this.innerHTML); } |
| 1115 | + set value(val) { |
| 1116 | + val = val || ""; |
| 1117 | + if(this.setTextareaProperty("value", val, false)) { |
| 1118 | + if(this.textareaElement) this.scheduleHighlight(); |
| 1119 | + } else { |
| 1120 | + this.innerHTML = val; |
| 1121 | + } |
1076 | 1122 | } |
| 1123 | + get willValidate() { return this.getTextareaProperty("willValidate", this.disabled || this.readOnly); } |
| 1124 | + get wrap() { return this.getAttribute("wrap") || ""; } |
| 1125 | + set wrap(val) { this.setAttribute("wrap", val); } |
| 1126 | + |
| 1127 | + |
| 1128 | + blur(options={}) { return this.textareaElement.blur(options); } |
| 1129 | + checkValidity() { return this.textareaElement.checkValidity(); } |
| 1130 | + focus(options={}) { return this.textareaElement.focus(options); } |
| 1131 | + reportValidity() { return this.textareaElement.reportValidity(); } |
| 1132 | + setCustomValidity(error) { this.textareaElement.setCustomValidity(error); } |
| 1133 | + setRangeText(replacement, selectionStart=this.selectionStart, selectionEnd=this.selectionEnd, selectMode="preserve") { this.getTextareaProperty("setRangeText")(replacement, selectionStart, selectionEnd, selectMode); } |
| 1134 | + setSelectionRange(selectionStart, selectionEnd, selectionDirection="none") { this.getTextareaProperty("setSelectionRange")(selectionStart, selectionEnd, selectionDirection); } |
1077 | 1135 |
|
1078 | 1136 | /** |
1079 | 1137 | * Allows plugins to store data in the scope of a single element. |
|
0 commit comments