public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Timo Meschkat <timo.meschkat@datenzelt.de>
To: pve-devel@lists.proxmox.com
Subject: [RFC PATCH widget-toolkit 2/3] form: size field: accept a unit suffix like 8G
Date: Mon, 26 Oct 2026 23:04:18 +0100	[thread overview]
Message-ID: <20261026220423.127776-3-timo.meschkat@datenzelt.de> (raw)
In-Reply-To: <20261026220423.127776-1-timo.meschkat@datenzelt.de>

Selecting the unit first is an extra step for keyboard users. Allow
typing it right away, e.g. '8G', '512M' or '1.5 GiB', like the backend
accepts it. The unit selector follows while typing, the text itself is
only normalized on blur, so typing is not interrupted.

SI suffixes like 'GB' are rejected, they would be ambiguous.

Signed-off-by: Timo Meschkat <timo.meschkat@datenzelt.de>
---
 src/form/SizeUnitField.js | 38 +++++++++++++++++++++++++++-----------
 1 file changed, 27 insertions(+), 11 deletions(-)

diff --git a/src/form/SizeUnitField.js b/src/form/SizeUnitField.js
index e103b59..9fd7308 100644
--- a/src/form/SizeUnitField.js
+++ b/src/form/SizeUnitField.js
@@ -41,6 +41,12 @@ Ext.define('Proxmox.form.SizeUnitField', {
         me.unit ??= me.units[0];
         me.initialUnit = me.unit;
 
+        // e.g. '512M, 8G'
+        let examples = me.units.slice(0, 2).map((unit, i) => `${i === 0 ? 512 : 8}${unit[0]}`);
+        let hint = Ext.htmlEncode(
+            Ext.String.format(gettext('Also accepts a unit suffix, e.g. {0}'), examples.join(', ')),
+        );
+
         me.items = [
             {
                 // plain text, parsing and validation happen in this field
@@ -50,6 +56,7 @@ Ext.define('Proxmox.form.SizeUnitField', {
                 flex: 1,
                 // the field label belongs to the container, not the inputs
                 ariaLabel: me.fieldLabel,
+                inputAttrTpl: `data-qtip="${hint}" aria-description="${hint}"`,
                 fieldStyle: 'text-align: right',
                 allowBlank: me.allowBlank,
                 emptyText: me.emptyText,
@@ -122,18 +129,20 @@ Ext.define('Proxmox.form.SizeUnitField', {
         return !!this.userChanged;
     },
 
-    // '8' or '1,5' in the current unit; returns undefined if empty, null if invalid
+    // '8', '1,5', '8G' or '1.5 GiB'; returns undefined if empty, null if invalid
     parseText: function (text) {
         let me = this;
         text = String(text ?? '').trim();
         if (text === '') {
             return undefined;
         }
-        if (!/^\d+(?:[.,]\d*)?$/.test(text)) {
+        let match = text.match(/^(\d+(?:[.,]\d*)?)\s*(?:([KMGT])(?:i|iB)?)?$/i);
+        let unit = match?.[2] ? `${match[2].toUpperCase()}iB` : me.unit;
+        if (!match || !me.units.includes(unit)) {
             return null;
         }
-        let value = parseFloat(text.replace(',', '.')) * me.getUnitFactor();
-        return me.allowFractionalValue ? value : Math.round(value);
+        let value = parseFloat(match[1].replace(',', '.')) * me.getUnitFactor(unit);
+        return { value: me.allowFractionalValue ? value : Math.round(value), unit };
     },
 
     parseValue: function (value) {
@@ -209,25 +218,32 @@ Ext.define('Proxmox.form.SizeUnitField', {
 
     onTextChange: function (text) {
         let me = this;
+        let input = me.parseText(text);
         me.userChanged = true;
-        me.mixins.field.setValue.call(me, me.parseText(text) ?? null);
+        if (input && input.unit !== me.unit) {
+            me.changeUnit(input.unit); // the typed text stays until blur
+        }
+        me.mixins.field.setValue.call(me, input ? input.value : null);
         me.updateAriaValueText();
     },
 
     validateText: function (text) {
         let me = this;
-        let value = me.parseText(text);
-        if (value === undefined) {
+        let input = me.parseText(text);
+        if (input === undefined) {
             return true; // allowBlank handles this
-        } else if (value === null) {
-            return Ext.String.format(Ext.form.field.Number.prototype.nanText, text);
+        } else if (input === null) {
+            return Ext.String.format(
+                gettext('Invalid size, allowed units: {0}'),
+                me.units.join(', '),
+            );
         }
         let format = (v) => Proxmox.Utils.format_size(v * Proxmox.Utils.SizeUnits[me.backendUnit]);
         let numberField = Ext.form.field.Number.prototype;
-        if (me.minValue !== undefined && value < me.minValue) {
+        if (me.minValue !== undefined && input.value < me.minValue) {
             return Ext.String.format(numberField.minText, format(me.minValue));
         }
-        if (me.maxValue !== undefined && value > me.maxValue) {
+        if (me.maxValue !== undefined && input.value > me.maxValue) {
             return Ext.String.format(numberField.maxText, format(me.maxValue));
         }
         return true;
-- 
2.47.3




  parent reply	other threads:[~2026-10-26 22:04 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26 22:04 [RFC PATCH widget-toolkit/manager 0/7] ui: allow selecting the unit for memory and disk sizes Timo Meschkat
2026-10-26 22:04 ` [RFC PATCH widget-toolkit 1/3] form: add size field with selectable unit Timo Meschkat
2026-10-26 22:04 ` Timo Meschkat [this message]
2026-10-26 22:04 ` [RFC PATCH widget-toolkit 3/3] form: size field: add optional field description Timo Meschkat
2026-10-26 22:04 ` [RFC PATCH manager 1/4] fix #3760: ui: qemu: allow selecting the memory unit Timo Meschkat
2026-10-26 22:04 ` [RFC PATCH manager 2/4] ui: lxc: allow selecting the unit for memory and swap Timo Meschkat
2026-10-26 22:04 ` [RFC PATCH manager 3/4] ui: disk storage selector: allow selecting the size unit Timo Meschkat
2026-10-26 22:04 ` [RFC PATCH manager 4/4] ui: disk resize: " Timo Meschkat

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261026220423.127776-3-timo.meschkat@datenzelt.de \
    --to=timo.meschkat@datenzelt.de \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal