fix value display node
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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' },
|
||||
|
||||
Reference in New Issue
Block a user