전략편집기 복합지표 기능 반영

This commit is contained in:
Macbook
2026-05-25 01:34:52 +09:00
parent ca249ad318
commit 20dd257c29
21 changed files with 1781 additions and 183 deletions
@@ -0,0 +1,92 @@
import React, { useEffect, useId, useState } from 'react';
interface Props {
value: number;
options: number[];
min?: number;
max?: number;
allowDecimal?: boolean;
onChange: (value: number) => void;
}
function sanitizeDraft(raw: string, allowDecimal: boolean): string {
if (allowDecimal) {
let s = raw.replace(/[^\d.-]/g, '');
const minus = s.startsWith('-') ? '-' : '';
s = s.replace(/-/g, '');
const dot = s.indexOf('.');
if (dot !== -1) {
s = s.slice(0, dot + 1) + s.slice(dot + 1).replace(/\./g, '');
}
return minus + s;
}
return raw.replace(/\D/g, '');
}
function parseDraft(raw: string, allowDecimal: boolean): number | null {
const trimmed = raw.trim();
if (!trimmed || trimmed === '-' || trimmed === '.') return null;
const n = allowDecimal ? parseFloat(trimmed) : parseInt(trimmed, 10);
return Number.isFinite(n) ? n : null;
}
export default function ComboNumberInput({
value,
options,
min = 1,
max = 500,
allowDecimal = false,
onChange,
}: Props) {
const listId = useId().replace(/:/g, '');
const [draft, setDraft] = useState(String(value));
const [focused, setFocused] = useState(false);
useEffect(() => {
if (!focused) setDraft(String(value));
}, [value, focused]);
const commit = () => {
const parsed = parseDraft(draft, allowDecimal);
if (parsed == null) {
setDraft(String(value));
return;
}
const clamped = allowDecimal
? Math.max(min, Math.min(max, parsed))
: Math.max(min, Math.min(max, Math.round(parsed)));
onChange(clamped);
setDraft(String(clamped));
};
const uniqueOptions = [...new Set([...options, value])].sort((a, b) => a - b);
return (
<div className="se-combo-num">
<input
type="text"
className="se-combo-num-input"
inputMode={allowDecimal ? 'decimal' : 'numeric'}
list={listId}
value={draft}
onChange={e => setDraft(sanitizeDraft(e.target.value, allowDecimal))}
onFocus={() => setFocused(true)}
onBlur={() => {
setFocused(false);
commit();
}}
onKeyDown={e => {
if (e.key === 'Enter') {
e.preventDefault();
(e.target as HTMLInputElement).blur();
}
}}
/>
<datalist id={listId}>
{uniqueOptions.map(opt => (
<option key={opt} value={String(opt)} />
))}
</datalist>
</div>
);
}