컴포넌트/버튼

SplitButton (분할버튼)

VanillaFront 2026. 9. 9. 16:43

Va.SplitButton — 액션 + 드롭다운이 나뉜 분할 버튼

버튼 하나가 두 영역으로 갈라져 있는 컴포넌트입니다. 왼쪽 영역은 즉시 실행되는 주 액션, 오른쪽 작은 화살표 영역은 드롭다운 메뉴 트리거. "저장" 버튼 옆에 "다른 이름으로 저장 / 사본으로 저장…" 같은 대안 액션들을 팝업으로 제공할 때 씁니다.

  • 클래스: Va.SplitButton  va_component.js:14959
  • short name: splitButton
  • 상속: Va.Component
  • isContainer: true — 자식으로 menuItem 담음
  • 베이스 CSS: va-split-button


1. 기본 사용

{
    tagName: 'splitButton',
    text: '저장',
    icon: 'ico_save',
    onClick: 'onSave',         // 왼쪽 본체 클릭
    onSelect: 'onAlternateSave', // 팝업 항목 선택
    tags: [
        { tagName: 'menuItem', key: 'save-as', innerHTML: '다른 이름으로 저장' },
        { tagName: 'menuItem', key: 'copy',    innerHTML: '사본으로 저장' },
        { tagName: 'menuItem', key: 'export',  innerHTML: '내보내기...' }
    ]
}
  • 본체(text 영역) 클릭  click 이벤트 (팝업 안 열림, 즉시 액션 실행)
  • 오른쪽 화살표 클릭  menuClick 이벤트 발생 + 팝업 열림
  • 팝업 항목 클릭  select 이벤트

2. Va.MenuButton과의 차이 (가장 헷갈리는 부분)

항목Va.MenuButtonVa.SplitButton

버튼 영역 1개 (전체가 팝업 트리거) 2개 (본체 + 드롭다운 트리거)
본체 클릭 시 팝업 열림 즉시 액션 실행 (팝업 안 열림)
팝업 열기 트리거 버튼 어디를 눌러도 우측 화살표 영역만
이벤트 분화 click / itemClick click / menuClick / select (3개)
키보드 네비 Esc/Tab/Enter/↓↑ 완전 지원 기본 focus만 (화살표 네비 없음)
팝업 자동 재배치 IntersectionObserver로 뷰포트 벗어나면 조정 최초 위치만 계산 (열림 시 top/left 설정)

한 줄 요약: MenuButton은 "메뉴만" 여는 버튼, SplitButton은 "기본 액션 + 옵션 팝업"이 하나로 붙은 버튼.


3. 주요 속성

버튼 표현 (Button 계열 공통)

속성설명

text / innerHTML 본체 버튼 라벨
icon, iconPosition, iconOnly, iconRotate 본체 아이콘 (드롭다운 아이콘과 별개)
appearance, shape, size 시각 스타일 (본체 + 드롭다운 트리거 양쪽에 동시 적용)
textAlign 텍스트 정렬
disabled 비활성화 (두 영역 모두)
badge 배지 부착
stopPropagation 이벤트 버블링 차단 (기본 true)

드롭다운 화살표

속성기본값설명

menuIcon 'ico_chevron_down' 우측 화살표 아이콘
menuIconExpand 'ico_chevron_down' 확장 상태 아이콘 (실제로는 update에서 chevron_down 하드코딩됨 — 커스터마이즈 유의)
menuIconVisible true 화살표 표시 여부

팝업 제어

속성설명

expanded 현재 팝업 열림 여부
popWidth 팝업 너비. 미지정 시 SplitButton 폭과 동일
expandChildCheck 자식 없으면 팝업 안 열림 (기본 true)

4. 이벤트

이벤트발생 시점

click 본체 클릭 — 주 액션 트리거
menuClick 드롭다운 화살표 클릭 — 팝업 열기 트리거
select 팝업 내 항목 클릭 (onSelect(component, element, listItem, data, folder, evt))
expand 팝업 열림
collapse 팝업 닫힘
beforePop / afterPop 팝업 표시 직전/직후
focus / blur 포커스 진입/이탈 (본체 or 화살표 어느 쪽이든)
contextmenu 본체 우클릭

핵심 3분화: click(본체) · menuClick(트리거) · select(항목). MenuButton에는 click이 곧 "팝업 열기"였지만, SplitButton에서는 click이 명확히 주 액션 실행을 의미합니다.


5. 메서드

메서드설명

setText(text) / setInnerHTML(html) 본체 라벨 변경
setIcon(icon) 본체 아이콘 변경
setAppearance(v) / setShape(v) / setSize(v) 시각 속성 변경
setDisabled(bool) 비활성화
expand() 팝업 강제 열기 (Va.hideOtherComponents(this) 호출 → 다른 팝업 닫고 200ms 지연 후 표시)
collapse() / hidePop() 팝업 닫기
showPop() 팝업 위치 재계산 후 열기
focus() / blur() 본체 버튼에 포커스

6. 내부 구조

<div elname="element" class="va-split-button">
  <inner elname="inner" class="split-button-inner">
    <button elname="button" class="va-button" tabindex="0">     ← 본체 (주 액션)
      <span elname="content" class="button-inner">
        <span elname="icon" class="icon">…</span>              ← icon 지정 시
        <span elname="text" class="text">라벨</span>
      </span>
    </button>
    <button elname="menuButton" class="menu-button" tabindex="-1"> ← 드롭다운 트리거
      <span elname="menuIcon" class="icon menu ico_chevron_down">…</span>
    </button>
    <span cpname="badge">…</span>                              ← badge 지정 시
  </inner>
</div>

<!-- 팝업은 열릴 때 hiddenArea로 이동 -->
<div elname="pop" class="va-menu-pop" style="position:absolute; display:none">
  <div elname="popInner" class="menu-pop-inner">
    <!-- menuItem들이 여기 렌더됨 -->
  </div>
</div>

**두 개의 <button>**이 나란히 있는 게 이 컴포넌트의 시각적 정체성입니다. 드롭다운 트리거는 tabindex=-1로 기본 탭 순회에서 빠져 있어, 키보드 탭은 본체만 잡습니다.


7. 팝업 위치 로직

MenuButton과 유사하지만 더 단순합니다.

  • menuClick   getHiddenAreaElement()로 popElement 이동 → top = 버튼.bottom + 2px, left = 버튼.left, width = 버튼.width (또는 popWidth)
  • 뷰포트 넘침 방지 없음 — MenuButton과 달리 위로 뒤집기나 IntersectionObserver 재조정이 없습니다. 화면 하단 근처에서 SplitButton을 열면 팝업이 잘릴 수 있음.
  • 오토하이드  Va.addAutoHide(this.popElement) 등록 → 외부 클릭 시 자동 닫힘 (va_component.js:15052)

8. 항목 선택 흐름

사용자가 팝업의 menuItem 클릭
  ↓
menuItem이 부모 SplitButton에 'itemClick' dispatch
  ↓
SplitButton이 'select' 이벤트로 재방출 (onSelect 콜백 호출)
  ↓
300ms 후 expanded=false, focused=true 처리 + 'collapse' dispatch
  ↓
본체 버튼에 focus 복귀

select 콜백 시그니처:

onSelect(component, element, listItem, data, folder, evt) {
    // listItem: 선택된 menuItem
    // data: 항목의 데이터
    // folder: 서브메뉴 폴더 여부
}

9. 언제 쓰나

SplitButton이 맞을 때

  • 주된 액션 하나 + 부수 액션 몇 개 ("저장" + 다양한 저장 옵션, "실행" + 실행 프로파일 목록)
  • 사용자가 대부분 기본 액션을 원하지만, 가끔 다른 선택지가 필요할 때
  • IDE 스타일 UI (Run/Debug, Commit/Amend, Deploy/Deploy&Rollback)
  • 툴바에서 "즉시 실행"과 "옵션"을 시각적으로 분리하고 싶을 때

다른 걸 쓸 때

  • 모든 선택지가 동등한 목록 (주 액션이 없음) → Va.MenuButton
  • 기본 액션이 없고 그냥 접기/펼치기 → Va.ExpandButton
  • 단일 액션만 필요 → Va.Button
  • 여러 액션을 나란히 보여줄 공간이 충분함 → 개별 Va.Button 여러 개

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

  1. 드롭다운 트리거는 tabindex=-1 — 키보드 사용자는 본체만 탭으로 접근 가능. 화살표 영역은 마우스 전용에 가까움.
  2. 팝업 뷰포트 벗어남 처리 없음 — 화면 아래쪽에서 열 때 잘림 가능. MenuButton과 달리 자동으로 위로 뒤집지 않음.
  3. menuIconExpand가 실효 없음 — update()에서 ico_chevron_down이 항상 add되어 열림 상태와 관계없이 동일 아이콘.
  4. expandChildCheck 기본 true — 자식 menuItem 없이 만들면 트리거를 눌러도 팝업이 안 열립니다. 데이터 지연 로드 시 유의.
  5. onClick과 onMenuClick 헷갈림 주의 — 본체 클릭은 onClick, 드롭다운 클릭은 onMenuClick. 팝업만 열고 싶은데 onClick에 로직 넣으면 본체를 눌렀을 때 원치 않는 코드가 돕니다.