Polish calculator large number readability
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-08-09 17:59:40 +02:00
parent 9176f3b8a8
commit e4803aa14f
2 changed files with 61 additions and 13 deletions

View file

@ -5,6 +5,8 @@ import { useInlineEdit } from "../../../hooks/useInlineEdit.js";
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js"; import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
import { TextExchangeActions } from "./TextExchangeActions.jsx"; import { TextExchangeActions } from "./TextExchangeActions.jsx";
const DISPLAY_NUMBER_FORMATTER = new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 6 });
function calculateExpression(expression) { function calculateExpression(expression) {
const normalized = String(expression || "").replaceAll(",", ".").trim(); const normalized = String(expression || "").replaceAll(",", ".").trim();
if (!normalized || !/^[\d\s+\-*/().]+$/.test(normalized)) return null; if (!normalized || !/^[\d\s+\-*/().]+$/.test(normalized)) return null;
@ -24,13 +26,13 @@ function formatResult(value) {
function formatDisplayResult(value) { function formatDisplayResult(value) {
if (!Number.isFinite(value)) return ""; if (!Number.isFinite(value)) return "";
return new Intl.NumberFormat("fr-FR", { maximumFractionDigits: 6 }).format(Number.parseFloat(value.toFixed(6))); return DISPLAY_NUMBER_FORMATTER.format(Number.parseFloat(value.toFixed(6)));
} }
function getResultLengthClass(value) { function getResultLengthClass(value) {
const length = formatResult(value).replace(/[^\d]/g, "").length; const length = formatResult(value).replace(/[^\d]/g, "").length;
if (length >= 16) return "is-compact"; if (length >= 13) return "is-compact";
if (length >= 12) return "is-long"; if (length >= 9) return "is-long";
return ""; return "";
} }
@ -47,6 +49,21 @@ function getEntriesInTreeOrder(entries, parentId = "") {
return getChildren(entries, parentId).flatMap((entry) => [entry, ...getEntriesInTreeOrder(entries, entry.id)]); return getChildren(entries, parentId).flatMap((entry) => [entry, ...getEntriesInTreeOrder(entries, entry.id)]);
} }
function FormattedCalculatorNumber({ value }) {
if (!Number.isFinite(value)) return "-";
let integerGroupIndex = 0;
const normalized = Number.parseFloat(value.toFixed(6));
return DISPLAY_NUMBER_FORMATTER.formatToParts(normalized).map((part, index) => {
if (part.type === "integer") {
const className = `calculator-number-group ${integerGroupIndex % 2 ? "is-muted" : "is-strong"}`;
integerGroupIndex += 1;
return <span className={className} key={`${part.type}-${index}`}>{part.value}</span>;
}
if (part.type === "group") return <span className="calculator-number-separator" key={`${part.type}-${index}`}>{part.value}</span>;
return <span key={`${part.type}-${index}`}>{part.value}</span>;
});
}
export function CalculatorModule({ toolboxId, moduleId, context, editing }) { export function CalculatorModule({ toolboxId, moduleId, context, editing }) {
const data = context.normalizeCalculatorData(context.getModuleData(toolboxId, moduleId, { entries: [] })); const data = context.normalizeCalculatorData(context.getModuleData(toolboxId, moduleId, { entries: [] }));
const textContent = context.moduleText?.calculator || {}; const textContent = context.moduleText?.calculator || {};
@ -165,7 +182,9 @@ export function CalculatorModule({ toolboxId, moduleId, context, editing }) {
</label> </label>
<div className={`calculator-result ${result == null ? "" : getResultLengthClass(result)}`} aria-live="polite"> <div className={`calculator-result ${result == null ? "" : getResultLengthClass(result)}`} aria-live="polite">
<span>{textContent.resultLabel || "Résultat"}</span> <span>{textContent.resultLabel || "Résultat"}</span>
<strong>{result == null ? "-" : formatDisplayResult(result)}</strong> <strong aria-label={result == null ? "-" : formatDisplayResult(result)}>
<FormattedCalculatorNumber value={result} />
</strong>
</div> </div>
<label> <label>
<span>{textContent.labelLabel || "Libellé"}</span> <span>{textContent.labelLabel || "Libellé"}</span>
@ -245,14 +264,18 @@ function CalculatorEntries({ entries, parentId, getScopedParentId, activeParentI
{entry.id === editingId ? ( {entry.id === editingId ? (
<> <>
<span className="tool-split-entry-value is-readonly" title={textContent.readonlyValueTitle || "Quantité non modifiable"}> <span className="tool-split-entry-value is-readonly" title={textContent.readonlyValueTitle || "Quantité non modifiable"}>
<strong>{formatDisplayResult(entry.value)}</strong> <strong className={getResultLengthClass(entry.value)} aria-label={formatDisplayResult(entry.value)}>
<FormattedCalculatorNumber value={entry.value} />
</strong>
</span> </span>
<EditableCalculatorLabel entry={entry} textContent={textContent} onRename={onRename} onDone={() => setEditingId("")} /> <EditableCalculatorLabel entry={entry} textContent={textContent} onRename={onRename} onDone={() => setEditingId("")} />
</> </>
) : ( ) : (
<button className="tool-split-entry-summary" type="button" onClick={() => onUse(entry)} title={textContent.useEntryTitle || "Utiliser comme base"}> <button className="tool-split-entry-summary" type="button" onClick={() => onUse(entry)} title={textContent.useEntryTitle || "Utiliser comme base"}>
<span> <span>
<strong>{formatDisplayResult(entry.value)}</strong> <strong className={getResultLengthClass(entry.value)} aria-label={formatDisplayResult(entry.value)}>
<FormattedCalculatorNumber value={entry.value} />
</strong>
{entry.label && <em>{entry.label}</em>} {entry.label && <em>{entry.label}</em>}
</span> </span>
</button> </button>

View file

@ -5216,13 +5216,24 @@ button.combo-input-token.combo-value-grab .ui-icon {
.calculator-entry .tool-split-entry-summary strong, .calculator-entry .tool-split-entry-summary strong,
.calculator-entry .tool-split-entry-value strong { .calculator-entry .tool-split-entry-value strong {
min-width: 0; min-width: 0;
overflow: hidden; overflow: visible;
text-overflow: ellipsis; font-variant-numeric: tabular-nums;
white-space: nowrap; white-space: nowrap;
} }
.calculator-entry .tool-split-entry-summary strong { .calculator-entry .tool-split-entry-summary strong {
flex: 0 1 auto; flex: 0 0 auto;
font-size: var(--font-size-sm);
}
.calculator-entry .tool-split-entry-summary strong.is-long,
.calculator-entry .tool-split-entry-value strong.is-long {
font-size: var(--font-size-xs);
}
.calculator-entry .tool-split-entry-summary strong.is-compact,
.calculator-entry .tool-split-entry-value strong.is-compact {
font-size: 0.68rem;
} }
.calculator-entry .tool-split-entry-summary em { .calculator-entry .tool-split-entry-summary em {
@ -5235,6 +5246,20 @@ button.combo-input-token.combo-value-grab .ui-icon {
white-space: nowrap; white-space: nowrap;
} }
.calculator-number-group {
color: var(--color-text-primary);
}
.calculator-number-group.is-muted,
.calculator-number-separator {
color: rgba(180, 189, 211, 0.68);
}
.calculator-number-separator {
display: inline-block;
min-width: 0.24em;
}
.calculator-form label span, .calculator-form label span,
.calculator-result span, .calculator-result span,
.tool-split-root-button { .tool-split-root-button {
@ -5258,23 +5283,23 @@ button.combo-input-token.combo-value-grab .ui-icon {
display: block; display: block;
min-width: 0; min-width: 0;
max-width: 100%; max-width: 100%;
overflow: hidden; overflow: visible;
color: var(--color-text-primary); color: var(--color-text-primary);
font-size: clamp(1.55rem, 3vw, 2.15rem); font-size: clamp(1.55rem, 3vw, 2.15rem);
font-variant-numeric: tabular-nums;
font-weight: 900; font-weight: 900;
line-height: 0.95; line-height: 0.95;
text-align: right; text-align: right;
text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.calculator-result.is-long strong { .calculator-result.is-long strong {
font-size: 1.55rem; font-size: 1.18rem;
line-height: 1; line-height: 1;
} }
.calculator-result.is-compact strong { .calculator-result.is-compact strong {
font-size: 1.25rem; font-size: 0.92rem;
line-height: 1.05; line-height: 1.05;
} }