Formatter
텍스트 입력 포매팅 유틸리티 (정수·소수·대소문자·시간·수식 마스킹)
목차
TextField 의 inputFormatters 에 넘기는 입력 포매터 팩토리 모음입니다. 같은 변환 로직(CoreInputFormatters)을 양 플랫폼이 공유하며, 클래스 이름(TextInputFormatters)과 팩토리 표면(integerOnly·decimalOnly·toUpperCase·toLowerCase·time·mathExpression)이 동일합니다 — Flutter 는
TextInputFormatter 로, Web <input> 은 평문 문자열에 직접 적용합니다.
Live Preview#
코드 보기
class FormatterDefaultExample extends StatelessComponent {
const FormatterDefaultExample({super.key});
@override
Component build(BuildContext context) {
return div(
styles: Styles(raw: {'width': '280px'}),
[
TextField(
placeholder: Text('숫자만 입력 (0–100)'),
inputFormatters: [TextInputFormatters.integerOnly(min: 0, max: 100)],
),
],
);
}
}
class FormatterDefaultExample extends StatelessWidget {
const FormatterDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 280,
child: TextField(
placeholder: const Text('숫자만 입력 (0–100)'),
inputFormatters: [TextInputFormatters.integerOnly(min: 0, max: 100)],
),
);
}
}
사용법#
// Flutter / Web 동일 — TextInputFormatters
TextField(
placeholder: const Text('숫자만 입력 (0–100)'),
inputFormatters: [TextInputFormatters.integerOnly(min: 0, max: 100)],
)
Props / Parameters#
TextInputFormatters 는 위젯이 아니라 정적 팩토리 홀더라 생성자가 없습니다 (TextInputFormatters._()
는 private). 노출되는 표면은 아래 팩토리와 그 named 파라미터뿐이며, 각 팩토리가 돌려준 값을 TextField.inputFormatters 에 넘겨 씁니다.
| 팩토리 | 파라미터 | 기본값 | 설명 |
|---|---|---|---|
integerOnly |
int? min, int? max |
둘 다 null (무제한) |
파싱 불가능한 입력은 이전 값, 범위 밖 정수는 min/max로 클램핑 |
decimalOnly |
int? decimalDigits, double? min, double? max |
모두 null (자릿수·범위 무제한) |
소수를 허용하고 소수점 이하 자릿수와 범위를 제한합니다 |
toUpperCase | 없음 | — | 입력을 대문자로 강제합니다 |
toLowerCase | 없음 | — | 입력을 소문자로 강제합니다 |
time |
required int length |
필수 | 지정한 길이까지 앞을 0 으로 채우는 시간 형식 마스킹 |
mathExpression |
Map<String, Object?>? context |
null |
입력을 수식으로 평가합니다. context 는 식에서 참조할 변수 맵 |
allow¹ |
String pattern |
필수 | 정규식 소스에 매칭되는 문자만 남김 |
phoneText¹ | 없음 | — | 숫자와 선두 + 를 허용 |
InputFormatter (Flutter 전용 어댑터)
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
formatter |
CoreInputFormatter |
필수 (positional) | 감쌀 플랫폼 무관 변환. Flutter TextInputFormatter 로 어댑트하며 재작성 후 캐럿 위치를 보존합니다 |
¹ 현재 Flutter는 두 팩토리를 노출하고 Web TextInputFormatters에는 노출이 빠져 있습니다. 공유 CoreInputFormatters에는 같은 변환이 있어 Web에서 직접 사용할 수 있습니다. 이는 수용된 플랫폼 전용 기능이 아니라 공개 팩토리 배선 결손입니다.
동작 스펙 (Behavior)#
- 입력의 이전 문자열·새 문자열을 받아 변환하는 정적 팩토리입니다. 필드 렌더 · 콜백 · 자체 상태 · 비활성 기능은 없으며 반환값을 TextField.inputFormatters에 넣어 적용합니다.
- integerOnly / decimalOnly의 min/max는 숫자 범위, decimalDigits는 소수 자릿수, time.length는 문자 길이입니다. 입력 변환은 검증 메시지를 만들지 않으므로 필수·도메인 규칙에는 FormField.validator를 사용합니다.
- 마스크 자리·리터럴과 함께 단일 입력을 표시하려면 FormattedInput, 자유 입력의 변환만 필요하면 이 팩토리를 씁니다.
사용 가이드라인 (Usage Guidelines)#
✅ Do#
숫자 입력은 min/max로 범위를 clamp
TextField(
placeholder: const Text('나이 (0–120)'),
inputFormatters: [TextInputFormatters.integerOnly(min: 0, max: 120)],
)
min/max는 범위를 벗어난 값을 자동으로 경계값으로 보정합니다(예: 130 입력 시 120으로) — 범위가 있는 숫자 입력은 별도 검증 로직 없이 포매터만으로 값을 안전한 범위 안에 유지할 수 있습니다.
❌ Don't#
TextInputFormatters.allow가 Web에도 있다고 가정하지 않기
// ❌ Web 의 TextInputFormatters 에는 allow 가 없음 — 컴파일 에러
TextInputFormatters.allow(r'[0-9]')
// ✅ Web 은 공유 로직인 CoreInputFormatters.allow 를 직접 사용
CoreInputFormatters.allow(r'[0-9]')
allow 팩토리는 Flutter의 TextInputFormatters에만 있고 Web TextInputFormatters에는 없습니다 — 양 플랫폼에서 같은 코드를 쓰려면 Web에서는 공유 로직인 CoreInputFormatters.allow를 직접 호출해야 합니다.
접근성 (Accessibility)#
이 절은 Formatter 자신의 동작만 다룹니다. 전 컴포넌트에 공통으로 적용되는 축은 전역 접근성 축을 참고하세요.
Formatter 는 아무것도 그리지 않습니다. Flutter 쪽은 TextInputFormatter 를 상속한 InputFormatter
와 팩토리 홀더 TextInputFormatters 이고, Web 쪽은 CoreInputFormatter 를 돌려주는 팩토리 홀더 TextInputFormatters
하나뿐입니다. 어느 파일도 Widget / Component 도 build 도 선언하지 않으므로 role 을 실을 엘리먼트도 시맨틱 노드도 존재하지 않습니다 — 아래 네 축이 "없음" 인 이유는 전부 이것 하나입니다.
역할#
없음. 양 플랫폼 모두 Semantics 호출도, role / aria-* 를 붙일 엘리먼트도 없습니다. 접근성 트리에 나타나는 것은 포매터를 소비하는 호스트 필드(TextField)의 노드뿐이고, 그 노드의 역할은 필드가 정합니다.
키보드#
처리하는 키가 없습니다. 두 플랫폼 다 키 이벤트를 구독하지 않고, 이미 확정된 텍스트를 받아 문자열을 되돌려 줄 뿐입니다 — Flutter 는 플랫폼 텍스트 입력이 키를 처리한 뒤 프레임워크가 호출하는
formatEditUpdate(oldValue, newValue) 안에서, Web 은 호스트 TextField 가 <input>
의 input 이벤트에서 format(old, new) 를 목록 순서대로 돌려 실행합니다.
스크린 리더#
이 클래스들이 직접 읽히는 것은 없습니다. 간접적으로는 사용자가 입력하는 동안 필드의 텍스트를 다시 쓰므로 리더가 다시 읽는 것은 호스트 필드의 값 변경입니다. 포매터 자신은 live region 도, status 도, "문자가 거부되었다 / 다시 포맷되었다" 는 안내도 내보내지 않습니다.
포커스 관리#
없음. 포커스 가능한 노드를 만들지 않으므로 진입·이탈·트랩이 성립하지 않습니다 — Flutter 에 FocusNode / Focus 가 없고, Web 에는
tabindex 를 실을 엘리먼트 자체가 없습니다. 포커스는 처음부터 끝까지 호스트 필드가 갖고 있습니다.
알려진 제약#
- 거부·재작성이 조용히 일어납니다. 허용되지 않는 문자를 입력한 스크린 리더 사용자는 무언가 버려졌다는 피드백을 전혀 받지 못합니다(양 플랫폼 동일). 입력 규칙을 알려야 한다면 호스트 필드 쪽에서 안내 문구나 live region 을 직접 제공해야 합니다.
-
Web 은 재작성이 일어날 때마다 캐럿이 문자열 끝으로 밀립니다. Flutter 의
InputFormatter는 재작성 후 선택 범위를 새 길이에 clamp 해 캐럿 위치를 보존하지만, Web 은 결과 문자열을<input>의value에 직접 대입하므로 브라우저가 캐럿을 끝으로 옮깁니다. 그래서 Web 에서는 이미 입력한 값의 중간을 고칠 때마다 커서가 끝으로 튀어, 키보드·스크린 리더만 쓰는 사용자가 앞부분을 수정하기 어렵습니다. 대안은 없습니다 — 두 경로 모두 컴포넌트 내부이고 캐럿 정책을 여는 파라미터가 없습니다. -
접근성과 직접 관련은 없지만 팩토리 표면이 실제로는 완전히 같지 않습니다 — Flutter 에는
TextInputFormatters.allow(pattern)이 있고 Web 의TextInputFormatters에는 없어, Web 호출자는CoreInputFormatters.allow를 직접 씁니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 반환 타입 | TextInputFormatter 어댑터 | CoreInputFormatter |
| 적용 값 | TextEditingValue의 텍스트·선택 | 이전·새 String |
| 재작성 후 선택 | 기존 선택 offset을 새 길이에 클램핑 | 실제 DOM 캐럿 보정은 입력 컴포넌트가 담당 |
| 공유 변환 | CoreInputFormatters에 위임 | 같은 CoreInputFormatters에 위임 |