UTKInput Validation 기능 추가

This commit is contained in:
logonkhi
2026-02-12 18:04:38 +09:00
parent df6d3e3b5a
commit 93e1423525
54 changed files with 2900 additions and 188 deletions

View File

@@ -9,6 +9,15 @@ namespace UVC.UIToolkit
/// 입력 필드 컴포넌트.
/// Unity TextField를 래핑하여 커스텀 스타일을 적용합니다.
/// </summary>
/// <remarks>
/// <para><b>주요 기능:</b></para>
/// <list type="bullet">
/// <item><description>플레이스홀더, 비밀번호, 멀티라인 지원</description></item>
/// <item><description>스타일 변형 (Default, Filled, Outlined)</description></item>
/// <item><description>Validation 함수를 통한 입력 검증 (Submit/FocusOut 시 자동 호출)</description></item>
/// <item><description>에러 상태 시 붉은 외곽선 + 에러 메시지 표시</description></item>
/// </list>
/// </remarks>
/// <example>
/// <para><b>C# 코드에서 사용:</b></para>
/// <code>
@@ -17,39 +26,62 @@ namespace UVC.UIToolkit
/// input.label = "이름";
/// input.Placeholder = "이름을 입력하세요";
/// input.OnValueChanged += (value) => Debug.Log($"입력값: {value}");
///
///
/// // 비밀번호 입력 필드
/// var password = new UTKInputField();
/// password.label = "비밀번호";
/// password.isPasswordField = true;
///
/// // 검증 오류 표시
/// input.ErrorMessage = "이름은 필수입니다.";
/// // 오류 제거
/// input.ErrorMessage = "";
///
///
/// // 변형 스타일
/// input.Variant = UTKInputField.InputFieldVariant.Outlined;
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <para><b>Validation (입력 검증):</b></para>
/// <code>
/// // 검증 함수 설정 (Func&lt;bool&gt;)
/// var emailInput = new UTKInputField("이메일", "example@email.com");
/// emailInput.ErrorMessage = "올바른 이메일 형식이 아닙니다.";
/// emailInput.Validation = () => emailInput.Value.Contains("@");
/// // → Submit(Enter) 또는 FocusOut 시 자동으로 검증
/// // → 실패 시 붉은 외곽선 + 에러 메시지 표시, 통과 시 자동 해제
///
/// // 필수 입력 검증
/// var nameInput = new UTKInputField("이름");
/// nameInput.ErrorMessage = "이름은 필수 항목입니다.";
/// nameInput.Validation = () => !string.IsNullOrWhiteSpace(nameInput.Value);
///
/// // 강제 검증 호출 (예: 폼 제출 버튼 클릭 시)
/// bool isValid = nameInput.Validate();
/// if (!isValid) return; // 검증 실패
///
/// // 에러 수동 해제
/// nameInput.ClearError();
///
/// // 에러 메시지 직접 설정 (Validation 없이)
/// input.ErrorMessage = "서버 오류가 발생했습니다.";
/// input.ErrorMessage = ""; // 오류 제거
/// </code>
/// <para><b>UXML에서 사용:</b></para>
/// <code><![CDATA[
/// <ui:UXML xmlns:utk="UVC.UIToolkit">
/// <!-- 기본 입력 필드 -->
/// <utk:UTKInputField label="이름" />
///
///
/// <!-- 플레이스홀더 -->
/// <utk:UTKInputField label="이메일" placeholder="example@email.com" />
///
///
/// <!-- 비밀번호 필드 -->
/// <utk:UTKInputField label="비밀번호" is-password-field="true" />
///
///
/// <!-- 여러 줄 입력 -->
/// <utk:UTKInputField label="설명" multiline="true" />
///
///
/// <!-- 에러 메시지 (C#에서 Validation 설정 권장) -->
/// <utk:UTKInputField label="이메일" error-message="올바른 이메일 형식이 아닙니다." />
///
/// <!-- 비활성화 -->
/// <utk:UTKInputField label="읽기전용" is-enabled="false" value="수정 불가" />
/// </ui:UXML>
/// </code>
/// ]]></code>
/// </example>
[UxmlElement]
public partial class UTKInputField : TextField, IDisposable
@@ -63,6 +95,8 @@ namespace UVC.UIToolkit
private bool _isEnabled = true;
private string _errorMessage = "";
private InputFieldVariant _variant = InputFieldVariant.Default;
private Func<bool>? _validation;
private Label? _errorLabel;
#endregion
#region Events
@@ -92,7 +126,7 @@ namespace UVC.UIToolkit
set => textEdition.placeholder = value;
}
/// <summary>에러 메시지</summary>
/// <summary>에러 메시지. 비어있지 않으면 에러 상태로 표시</summary>
[UxmlAttribute("error-message")]
public string ErrorMessage
{
@@ -100,10 +134,19 @@ namespace UVC.UIToolkit
set
{
_errorMessage = value;
EnableInClassList("utk-input--error", !string.IsNullOrEmpty(value));
var hasError = !string.IsNullOrEmpty(value);
EnableInClassList("utk-input--error", hasError);
UpdateErrorLabel(hasError ? value : null);
}
}
/// <summary>검증 함수. Submit/FocusOut 시 호출되어 false 반환 시 ErrorMessage 표시</summary>
public Func<bool>? Validation
{
get => _validation;
set => _validation = value;
}
/// <summary>활성화 상태</summary>
[UxmlAttribute("is-enabled")]
public bool IsEnabled
@@ -199,26 +242,9 @@ namespace UVC.UIToolkit
private void SetupEvents()
{
RegisterCallback<ChangeEvent<string>>(OnTextValueChanged);
RegisterCallback<FocusInEvent>(_ =>
{
EnableInClassList("utk-input--focused", true);
OnFocused?.Invoke();
});
RegisterCallback<FocusOutEvent>(_ =>
{
EnableInClassList("utk-input--focused", false);
OnBlurred?.Invoke();
});
RegisterCallback<KeyDownEvent>(evt =>
{
if (evt.keyCode == KeyCode.Return && !multiline)
{
OnSubmit?.Invoke(value);
}
}, TrickleDown.TrickleDown);
RegisterCallback<FocusInEvent>(OnFocusIn);
RegisterCallback<FocusOutEvent>(OnFocusOut);
RegisterCallback<KeyDownEvent>(OnKeyDown, TrickleDown.TrickleDown);
}
private void SubscribeToThemeChanges()
@@ -263,6 +289,28 @@ namespace UVC.UIToolkit
{
OnValueChanged?.Invoke(evt.newValue);
}
private void OnFocusIn(FocusInEvent evt)
{
EnableInClassList("utk-input--focused", true);
OnFocused?.Invoke();
}
private void OnFocusOut(FocusOutEvent evt)
{
EnableInClassList("utk-input--focused", false);
OnBlurred?.Invoke();
RunValidation();
}
private void OnKeyDown(KeyDownEvent evt)
{
if (evt.keyCode == KeyCode.Return && !multiline)
{
OnSubmit?.Invoke(value);
RunValidation();
}
}
#endregion
#region Methods
@@ -283,6 +331,15 @@ namespace UVC.UIToolkit
}
}
/// <summary>
/// 강제로 Validation을 실행하여 에러 상태를 업데이트합니다.
/// </summary>
/// <returns>Validation이 null이면 true, 아니면 Validation 결과</returns>
public bool Validate()
{
return RunValidation();
}
/// <summary>
/// 선택 영역 설정
/// </summary>
@@ -291,6 +348,57 @@ namespace UVC.UIToolkit
textSelection.SelectAll();
}
/// <summary>에러 상태를 수동으로 해제합니다.</summary>
public void ClearError()
{
ErrorMessage = "";
}
private bool RunValidation()
{
if (_validation == null) return true;
var isValid = _validation.Invoke();
if (isValid)
{
// 검증 통과 시 에러 상태 해제
EnableInClassList("utk-input--error", false);
UpdateErrorLabel(null);
}
else
{
// 검증 실패 시 에러 상태 표시
EnableInClassList("utk-input--error", true);
UpdateErrorLabel(_errorMessage);
}
return isValid;
}
private void UpdateErrorLabel(string? message)
{
if (string.IsNullOrEmpty(message))
{
// 에러 라벨 숨기기 (존재하면)
if (_errorLabel != null)
{
_errorLabel.style.display = DisplayStyle.None;
}
return;
}
// 에러 라벨 생성 (지연 생성 - 필요할 때만)
if (_errorLabel == null)
{
_errorLabel = new Label();
_errorLabel.AddToClassList("utk-input__error-message");
_errorLabel.style.display = DisplayStyle.None;
Add(_errorLabel);
}
_errorLabel.text = message;
_errorLabel.style.display = DisplayStyle.Flex;
}
private void UpdateVariant()
{
RemoveFromClassList("utk-input--default");
@@ -318,10 +426,16 @@ namespace UVC.UIToolkit
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
UnregisterCallback<ChangeEvent<string>>(OnTextValueChanged);
UnregisterCallback<FocusInEvent>(OnFocusIn);
UnregisterCallback<FocusOutEvent>(OnFocusOut);
UnregisterCallback<KeyDownEvent>(OnKeyDown);
OnValueChanged = null;
OnFocused = null;
OnBlurred = null;
OnSubmit = null;
_validation = null;
_errorLabel = null;
}
#endregion
}