컴포넌트/필드 컴포넌트

TextareaField (텍스트영역필드)

VanillaFront 2026. 9. 13. 23:57

TextareaField 정리해 드릴게요.


Va.TextareaField — 라벨 + 여러 줄 텍스트 입력

Va.Textarea가 순수 여러 줄 입력이라면, Va.TextareaField는 그 위에 폼 라벨·필수 표시·검증 메시지를 얹은 완성 폼 필드입니다. Field 계열 아키텍처 그대로, 내부에 Va.Textarea를 소유하는 Composition 구조. 게시글·리뷰·메모 폼에 정석.

  • 클래스: Va.TextareaField  va_component.js:9418
  • short name: textareaField
  • 상속: Va.Field (다른 Field 형제들과 같음)
  • 내부 컴포넌트: Va.Textarea 인스턴스 (fieldComponent)
  • isContainer: true
  • 베이스 CSS: va-field

1. 기본 사용

{
    tagName: 'textareaField',
    label: '리뷰',
    value: '',
    placeholder: '리뷰를 작성하세요',
    maxLength: 500,
    visibleTextLength: true,
    required: true,
    onChange: 'onReviewChange'
}

라벨 + 검증 + 여러 줄 입력 + 글자 수 카운터가 한 번에 세팅.


2. Textarea / InputField와의 차이

항목Va.TextareaVa.TextareaFieldVa.InputField

폼 라벨
검증 메시지
필수 표시(별표)
입력 형태 여러 줄 여러 줄 한 줄
글자 수 카운터 ✓ (내부 위임)
높이 자동 조절 ✓ (내부 위임)
리사이즈 방향
height-stretch 클래스 자동

한 줄 요약: "폼 안 라벨 붙은 여러 줄 텍스트 필드 — 게시글·리뷰·메모에 최적."


3. 주요 속성

Textarea 전용 (계승)

속성기본값설명

maxLength 최대 글자 수
byteMaxLength 바이트 단위로 계산 (한글 3바이트 등)
visibleTextLength 우하단 글자 수 카운터 표시
autoHeight 내용에 따라 높이 자동 조절
resizeField 'none' / 'vertical' / 'horizontal' / 'both'

⚠️ byteMaxLength → byteMaxlength 오타 — 소스 va_component.js:9446에서 optionField로 넘길 때 byteMaxlength(소문자 l)로 잘못 표기됨. 다만 va_component.js:9470에서 update()가 직접 this.fieldComponent.byteMaxlength = this.byteMaxLength로 재세팅하므로 실제 동작은 문제없음. 소스 검색 시 두 표기 모두 확인 필요.

라벨 관련 (Field 상속)

속성설명

label 폼 라벨
labelPosition top / bottom / left / right
labelWidth 라벨 폭
noLabel 폼 라벨 숨김
infoButton info 아이콘
required 필수 표시

필드 관련 (Textarea로 위임)

속성설명

value 텍스트 값
placeholder 플레이스홀더
readonly / disabled 상태
size / appearance / shape 시각 스타일
textAlign 정렬
stopPropagation 이벤트 버블링
masking / valueType Field 표준 전달 (Textarea에서 실효 제한)

검증

속성설명

validation {state, size, message}
validationState success / warning / error
validationMessage 메시지

세부 커스터마이즈 (textarea 옵션 키)

{
    tagName: 'textareaField',
    label: '메모',
    textarea: {                       // ← 내부 Textarea에 직접 전달
        // Textarea 세부 옵션
    }
}

각 Field 계열 옵션 키:

  • InputField → input
  • TextareaField → textarea
  • ComboboxField → combobox

4. height-stretch 자동 부여

TextareaField의 update()가 fieldDivElement에 height-stretch 클래스를 자동 부여합니다 (va_component.js:9469).

의미: 부모 컨테이너의 높이를 채우도록 CSS 조정. 폼 안에서 Textarea가 남는 세로 공간을 자연스럽게 차지하게 됩니다.

이건 InputField 등 한 줄 필드에는 없는 특유 로직으로, 여러 줄 필드는 높이가 유동적이라는 걸 CSS 레벨에서 반영한 것.


5. 이벤트

Field 표준 이벤트 세트. setFieldComponentEvent() 호출로 focus/blur/change/keydown 등이 표준 방식으로 dispatch됨:

이벤트시그니처발생 시점

change (component, element, evt) 값 변경 확정 시 (blur 후). 검증 자동 리셋
focus / blur (component, element, evt) 포커스 진입/이탈 (blur는 200ms debounce)
keydown (component, element, keyCode, evt) 키다운. 검증 자동 리셋
keyup (component, element, evt) 키업 (실시간 감지에 유용)
click (component, element, evt) 필드 클릭
mousedown / contextmenu 표준  

change 콜백 예시

onReviewChange(comp, el, evt) {
    const value = comp.getValue();
    console.log('작성된 리뷰:', value);
}

6. 메서드

값 관리 (Field 상속)

메서드설명

getValue() 내부 Textarea의 getValue() 위임
setValue(value) 값 세팅. autoHeight면 자동 재계산 트리거

상태 (Field 상속)

메서드설명

setDisabled(bool) / getDisabled() 비활성화
setReadOnly(bool) / setReadonly(bool) 읽기 전용
setLabel(label) 폼 라벨 변경
setPlaceholder(text) 플레이스홀더 변경
setSize(size) 크기

검증

메서드설명

setValidation(state, message) 검증 표시
clearValidation() 검증 해제

포커스

메서드설명

focus() / blur() 내부 Textarea에 위임

유틸

메서드설명

calcHeight() 높이 재계산 (오토높이 상황). fieldDivElement에 padding: 2px도 함께 조정

7. 내부 구조

<div elname="element" class="va-field [vertical|horizontal]" field="true">
  <div elname="inner" class="field-inner">
    <div elname="labelDiv" class="label-div">
      <label cpname="label">리뷰 <span class="required">*</span></label>
    </div>
    <div elname="comment" class="field-comment"></div>
    <div elname="fieldDiv" class="field-div height-stretch">     ← 자동 부여
      <div cpname="field" class="va-textarea">                   ← 내부 Va.Textarea
        <div class="field-wrapper">
          <textarea style="border:0px"></textarea>
          <div class="focus-line"></div>
        </div>
        <div class="text-length-inner"                            ← visibleTextLength일 때
             style="display:flex">
          <span class="text-length">123</span>
          <span>/</span>
          <span class="text-max-length">500</span>
        </div>
      </div>
    </div>
  </div>
  <div elname="validationDiv" style="display:none">
    <div class="va-validation">...</div>
  </div>
</div>

8. mounted()가 내부 Textarea에 위임

mounted(){
    this.fieldComponent.mounted();
}

Field 계열 중 특이하게 mounted()를 명시적으로 오버라이드해 내부 Textarea의 mounted()를 호출합니다. 이유는 Textarea가 autoHeight 초기 계산을 mounted()에서 300ms 지연으로 하기 때문 — Field가 이를 촉진.


9. 언제 쓰나

TextareaField가 맞을 때

  • 폼 안 게시글·리뷰·메모 본문
  • 긴 설명·비고·문의 입력
  • 주소 상세 (여러 줄)
  • 글자 수 제한이 있는 텍스트 필드
  • 자동 높이 조절이 자연스러운 상황

다른 걸 쓸 때

  • 라벨 없이 인라인 → Va.Textarea
  • 한 줄 입력 → Va.InputField
  • 코드 편집 → 별도 Monaco 등
  • 리치 텍스트 → 별도 에디터

10. 흔한 조합 예시

// 표준
{
    tagName: 'textareaField',
    label: '내용',
    placeholder: '내용을 입력하세요',
    style: { width: '100%', height: '200px' }
}

// 글자 수 제한 + 카운터
{
    tagName: 'textareaField',
    label: '리뷰',
    maxLength: 500,
    visibleTextLength: true,
    placeholder: '500자 이내로 작성',
    required: true
}

// 자동 높이
{
    tagName: 'textareaField',
    label: '메모',
    autoHeight: true,
    resizeField: 'none'
}

// 바이트 제한 (한글 폼)
{
    tagName: 'textareaField',
    label: '한글 게시글',
    maxLength: 900,
    byteMaxLength: true,
    visibleTextLength: true
}

// 좌측 라벨
{
    tagName: 'textareaField',
    label: '비고',
    labelPosition: 'left',
    labelWidth: 100,
    style: { width: '100%', height: '150px' }
}

// info 툴팁
{
    tagName: 'textareaField',
    label: '추가 요청사항',
    infoButton: {
        tooltip: '배송 시 주의사항이 있으면 적어주세요'
    },
    placeholder: '예: 부재 시 경비실'
}

// 읽기 전용 (긴 텍스트 표시)
{
    tagName: 'textareaField',
    label: '이용약관',
    value: '전문 내용...',
    readonly: true,
    style: { width: '100%', height: '300px' }
}

11. 실전 예 — 문의 폼

class Inquiry extends Va.View {
    onSubmit(btn, el, evt) {
        const title    = this.getRef('title').getValue();
        const category = this.getRef('category').getValue();
        const content  = this.getRef('content').getValue();

        if (!title) {
            this.getRef('title').setValidation('error', '제목을 입력하세요');
            return;
        }
        if (!content || content.length < 20) {
            this.getRef('content').setValidation('error', '20자 이상 입력하세요');
            return;
        }

        InquiryService.save(this, { title, category, content }, (view, ok) => {
            if (ok) new Va.Alert({ title: '완료', message: '문의가 접수되었습니다' }).show(view);
        });
    }

    config() {
        return {
            tagName: 'page',
            tags: [{
                tagName: 'panel',
                tags: [
                    { tagName: 'h2', innerHTML: '문의하기' },
                    {
                        tagName: 'inputField',
                        ref: 'title',
                        label: '제목',
                        required: true,
                        maxLength: 100
                    },
                    {
                        tagName: 'comboboxField',
                        ref: 'category',
                        label: '분류',
                        data: [
                            { key: 'GENERAL', display: '일반' },
                            { key: 'BILLING', display: '결제' },
                            { key: 'TECHNICAL', display: '기술' }
                        ]
                    },
                    {
                        tagName: 'textareaField',
                        ref: 'content',
                        label: '내용',
                        required: true,
                        maxLength: 2000,
                        visibleTextLength: true,
                        placeholder: '문의 내용을 자세히 작성해주세요 (20자 이상)',
                        style: { width: '100%', height: '250px' }
                    },
                    {
                        tagName: 'button',
                        text: '접수',
                        appearance: 'primary',
                        onClick: 'onSubmit'
                    }
                ]
            }]
        };
    }
}

12. 알아두면 좋을 주의사항

  1. byteMaxLength 오타 잔재 — 옵션 전달 시 byteMaxlength(소문자 l)로 되어 있지만 update()가 재세팅하므로 실제 동작은 문제없음. 소스 검색 시 두 표기 확인.
  2. height-stretch 클래스 자동 — fieldDiv가 남은 높이를 채움. 부모 컨테이너 flex 설정에 따라 유연하게 늘어남.
  3. mounted() 위임 — 내부 Textarea의 mounted() 호출. autoHeight 초기 계산이 정확히 되게 함.
  4. 옵션 키 textarea — 세부 커스터마이즈용.
  5. change/keydown 시 검증 자동 리셋 — 사용자 편집 시 이전 에러 사라짐.
  6. Enter는 줄바꿈 — 폼 제출 트리거 아님.
  7. maxLength 강제 — HTML 표준 방식. 붙여넣기도 자동 제한.
  8. byteMaxLength: true는 JS 계산 — HTML 표준 아님.
  9. autoHeight와 resizeField 상충 — 자동 조절 시 'none' 권장.
  10. autoHeight 초기 300ms 지연 — 렌더링 튐 가능.
  11. getValue()는 문자열 — 그대로 사용.
  12. focus()는 내부 textarea에 포커스 — 표준.
  13. change는 blur 후 발생 — 실시간엔 keyup.
  14. required: true는 별표만 — 실제 검증은 개발자가.

13. textareaField vs inputField 선택

상황추천

한 줄 텍스트 inputField
여러 줄 일반 텍스트 textareaField
긴 글 + 글자 수 제한 textareaField + visibleTextLength
자연스러운 높이 조절 textareaField + autoHeight
코드 편집 monaco (별도)
인라인 (라벨 없이) Va.Textarea

참고