fix value display node

This commit is contained in:
2026-03-29 12:13:08 -07:00
parent 80b74dfdfd
commit e3c381ee07
9 changed files with 207 additions and 31 deletions

View File

@@ -15,7 +15,7 @@ import {
} from './constants';
import { getGroupMinimumSize } from './groupSizing.js';
import { buildCombinedInputNameByWidgetName, formatUiLabel } from './nodeWidgetLayout.js';
import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns } from './valueFormatting.js';
import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns, parseNumberWithUnit } from './valueFormatting.js';
// ── Context (provided by App) ─────────────────────────────────────────
@@ -443,7 +443,11 @@ function getScalarPayload(scalarValue) {
return Number.isFinite(scalarValue) ? { value: scalarValue, unit: '' } : null;
}
if (!scalarValue || typeof scalarValue !== 'object') return null;
const numeric = Number(scalarValue.value);
const raw = scalarValue.value;
if (typeof raw === 'string') {
return { valueText: raw, unitText: typeof scalarValue.unit === 'string' ? scalarValue.unit : '' };
}
const numeric = Number(raw);
if (!Number.isFinite(numeric)) return null;
return {
value: numeric,
@@ -454,6 +458,7 @@ function getScalarPayload(scalarValue) {
function formatScalarDisplay(scalarValue) {
const payload = getScalarPayload(scalarValue);
if (!payload) return null;
if ('valueText' in payload) return payload;
if (payload.unit) {
const prefixed = applySIPrefix(payload.value, payload.unit);
@@ -1471,6 +1476,55 @@ function CustomNode({ id, data }) {
);
}
// ── Editable value-box for text_input FLOAT widgets ──────────────────
function TextInputValueBox({ val, placeholder, nodeId, name, label, hideLabel, onChange }) {
const [editing, setEditing] = useState(false);
const parsed = parseNumberWithUnit(val);
const display = parsed ? formatScalarDisplay({ value: parsed.numeric, unit: parsed.unit }) : null;
return (
<>
{!hideLabel && <label>{label}</label>}
<div
className="node-value-box nodrag"
style={{ cursor: editing ? 'text' : 'pointer' }}
onClick={() => !editing && setEditing(true)}
>
{editing ? (
<input
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
className="nodrag"
type="text"
value={val}
placeholder={placeholder}
onChange={(e) => onChange(nodeId, name, e.target.value)}
onBlur={() => setEditing(false)}
style={{
background: 'transparent',
border: 'none',
outline: 'none',
color: 'inherit',
font: 'inherit',
textAlign: 'center',
width: '100%',
padding: 0,
}}
/>
) : display ? (
<>
<span className="node-value-box-number">{display.valueText}</span>
{display.unitText && <span className="node-value-box-unit">{display.unitText}</span>}
</>
) : (
<span className="node-value-box-number" style={{ opacity: 0.4 }}>{placeholder || '0'}</span>
)}
</div>
</>
);
}
// ── Widget renderer ───────────────────────────────────────────────────
function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFileBrowser, hideLabel = false, measurementChoices }) {
@@ -1701,6 +1755,20 @@ function WidgetControl({ widget, nodeId, value, widgetValues, onChange, openFile
);
}
if (opts?.text_input) {
return (
<TextInputValueBox
val={val}
placeholder={placeholder || opts?.placeholder || ''}
nodeId={nodeId}
name={name}
label={label}
hideLabel={hideLabel || !!opts.hide_label}
onChange={onChange}
/>
);
}
if (type === 'FLOAT') {
if (opts?.slider) {
const rawMin = opts?.min_widget ? widgetValues?.[opts.min_widget] : opts?.min;

View File

@@ -1,3 +1,41 @@
const SI_PREFIX_MULTIPLIERS = {
Y: 1e24, Z: 1e21, E: 1e18, P: 1e15, T: 1e12,
G: 1e9, M: 1e6, k: 1e3,
m: 1e-3, u: 1e-6, µ: 1e-6, n: 1e-9, p: 1e-12,
f: 1e-15, a: 1e-18, z: 1e-21, y: 1e-24,
};
const NUMBER_WITH_UNIT_RE = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*(.*)?$/;
/**
* Parse a string like "1.5 nm" into { numeric: 1.5e-9, unit: "m" }.
* Returns null if the string does not start with a valid number.
* The numeric value is scaled to the base SI unit via the prefix.
*/
export function parseNumberWithUnit(text) {
const s = String(text ?? '').trim();
if (!s) return { numeric: 0, unit: '' };
const m = s.match(NUMBER_WITH_UNIT_RE);
if (!m) return null;
const numeric = parseFloat(m[1]);
const unitStr = (m[2] ?? '').trim();
if (!unitStr) return { numeric, unit: '' };
if (unitStr.length >= 2) {
const prefix = unitStr[0];
const rest = unitStr.slice(1);
const multiplier = SI_PREFIX_MULTIPLIERS[prefix];
if (multiplier !== undefined && PREFIXABLE_UNITS.has(rest)) {
return { numeric: numeric * multiplier, unit: rest };
}
}
return { numeric, unit: unitStr };
}
const SI_PREFIXES = [
{ exp: -24, prefix: 'y' },
{ exp: -21, prefix: 'z' },