From d02c26772b448747a5d4249819957f564a19926a Mon Sep 17 00:00:00 2001
From: Jo-Philipp Wich <jo@mein.io>
Date: Tue, 28 Apr 2020 23:34:33 +0200
Subject: [PATCH] luci-base: ui.js: rework password input handling

 - Get rid of dummy element
 - Render password field as text input initially and later transform
   it to a password field
 - Use .control-group markup to allow better styling

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
---
 .../htdocs/luci-static/resources/ui.js        | 53 +++++++++----------
 1 file changed, 25 insertions(+), 28 deletions(-)

diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js
index 612f74776..73f5b90bc 100644
--- a/modules/luci-base/htdocs/luci-static/resources/ui.js
+++ b/modules/luci-base/htdocs/luci-static/resources/ui.js
@@ -310,48 +310,45 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
 	/** @override */
 	render: function() {
 		var frameEl = E('div', { 'id': this.options.id });
-
-		if (this.options.password) {
-			frameEl.classList.add('nowrap');
-			frameEl.appendChild(E('input', {
-				'type': 'password',
-				'style': 'position:absolute; left:-100000px',
-				'aria-hidden': true,
-				'tabindex': -1,
-				'name': this.options.name ? 'password.%s'.format(this.options.name) : null
-			}));
-		}
-
-		frameEl.appendChild(E('input', {
+		var inputEl = E('input', {
 			'id': this.options.id ? 'widget.' + this.options.id : null,
 			'name': this.options.name,
-			'type': this.options.password ? 'password' : 'text',
+			'type': 'text',
 			'class': this.options.password ? 'cbi-input-password' : 'cbi-input-text',
 			'readonly': this.options.readonly ? '' : null,
 			'disabled': this.options.disabled ? '' : null,
 			'maxlength': this.options.maxlength,
 			'placeholder': this.options.placeholder,
 			'value': this.value,
-		}));
+		});
 
-		if (this.options.password)
-			frameEl.appendChild(E('button', {
-				'class': 'cbi-button cbi-button-neutral',
-				'title': _('Reveal/hide password'),
-				'aria-label': _('Reveal/hide password'),
-				'click': function(ev) {
-					var e = this.previousElementSibling;
-					e.type = (e.type === 'password') ? 'text' : 'password';
-					ev.preventDefault();
-				}
-			}, '∗'));
+		if (this.options.password) {
+			frameEl.appendChild(E('div', { 'class': 'control-group' }, [
+				inputEl,
+				E('button', {
+					'class': 'cbi-button cbi-button-neutral',
+					'title': _('Reveal/hide password'),
+					'aria-label': _('Reveal/hide password'),
+					'click': function(ev) {
+						var e = this.previousElementSibling;
+						e.type = (e.type === 'password') ? 'text' : 'password';
+						ev.preventDefault();
+					}
+				}, '∗')
+			]));
+
+			window.requestAnimationFrame(function() { inputEl.type = 'password' });
+		}
+		else {
+			frameEl.appendChild(inputEl);
+		}
 
 		return this.bind(frameEl);
 	},
 
 	/** @private */
 	bind: function(frameEl) {
-		var inputEl = frameEl.childNodes[+!!this.options.password];
+		var inputEl = frameEl.querySelector('input');
 
 		this.node = frameEl;
 
@@ -365,7 +362,7 @@ var UITextfield = UIElement.extend(/** @lends LuCI.ui.Textfield.prototype */ {
 
 	/** @override */
 	getValue: function() {
-		var inputEl = this.node.childNodes[+!!this.options.password];
+		var inputEl = this.node.querySelector('input');
 		return inputEl.value;
 	},
 
-- 
2.25.1