- 버튼은 동적 웹 애플리케이션을 만드는 데 필수임. 메뉴를 열고, 작업을 전환하고, 폼을 제출하는데 사용
- Chrome 135에서는 새로운 command 및 commandfor 속성으로 이전의 popovertargetaction 및 popovertarget 속성을 개선하고 대체함
- 기존에 버튼 동작을 구현할 때 발생하는 문제점:
-
HTML의 onclick 핸들러는 보안 정책(CSP)으로 인해 실제 코드에서 사용이 제한될 수 있음
- 버튼과 다른 요소의 상태 동기화가 필요하며, 접근성을 유지하면서 상태를 관리하는 코드는 복잡함
- React, AlpineJS, Svelte 등에서도 상태 및 이벤트 핸들링이 복잡함
command와 commandfor 패턴
-
command와 commandfor 속성을 사용하면 버튼이 다른 요소에 대해 선언적으로 동작할 수 있음. 이는 프레임워크의 편리함을 제공하면서도 유연성을 유지
-
commandfor 버튼은 ID를 사용(for속성과 비슷) 하고, command는 내장 값을 받아 더 직관적인 접근 방식을 제공
- 예제: 메뉴 열기 버튼 구현
- aria-expanded나 추가적인 JavaScript가 필요하지 않음
<button commandfor="my-menu" command="show-popover">
Open Menu
</button>
<div popover id="my-menu">
<!-- ... -->
</div>
command와 commandfor vs popovertargetaction과 popovertarget
-
popover를 사용한 적이 있다면 popovertarget과 popovertargetaction 속성에 익숙할 수 있음
- 이들은 commandfor와 command와 유사하게 작동하지만, 팝오버에 특화
- 새로운 속성은 이전 속성을 완전히 대체하며, 추가 기능을 제공함
내장 명령
-
command 속성은 다양한 API와 매핑되는 동작들을 내장
-
show-popover: el.showPopover()와 매핑됨
-
hide-popover: el.hidePopover()와 매핑됨
-
toggle-popover: el.togglePopover()와 매핑됨
-
show-modal: dialogEl.showModal()와 매핑됨
-
close: dialogEl.close()와 매핑됨
- 예제: 삭제 확인 다이얼로그 구현
- JavaScript 없이 상태 및 접근성 관리 가능
<button commandfor="confirm-dialog" command="show-modal">
Delete Record
</button>
<dialog id="confirm-dialog">
<header>
<h1>Delete Record?</h1>
<button commandfor="confirm-dialog" command="close" aria-label="Close">
<img role="none" src="/close-icon.svg">
</button>
</header>
<p>Are you sure? This action cannot be undone</p>
<footer>
<button commandfor="confirm-dialog" command="close" value="cancel">
Cancel
</button>
<button commandfor="confirm-dialog" command="close" value="delete">
Delete
</button>
</footer>
</dialog>
- 결과 처리 코드: 다이얼로그의 close 이벤트에서 반환 값 처리 가능
dialog.addEventListener("close", (event) => {
if (event.target.returnValue === "cancel") {
console.log("Cancel was clicked");
} else if (event.target.returnValue === "delete") {
console.log("Delete was clicked");
}
});
사용자 정의 명령
- 내장 명령 외에도 -- 접두사를 사용하여 사용자 정의 명령을 정의할 수 있음
- 사용자 정의 명령은 대상 요소에서 "command" 이벤트를 발생시키지만, 추가적인 로직은 수행하지 않음
- 예제: 이미지 회전 명령 구현
<button commandfor="the-image" command="--rotate-landscape">
Landscape
</button>
<button commandfor="the-image" command="--rotate-portrait">
Portrait
</button>
<img id="the-image" src="photo.jpg">
<script type="module">
const image = document.getElementById("the-image");
image.addEventListener("command", (event) => {
if (event.command === "--rotate-landscape") {
image.style.rotate = "-90deg";
} else if (event.command === "--rotate-portrait") {
image.style.rotate = "0deg";
}
});
</script>
Shadow DOM에서 명령 처리
- Shadow DOM에서는 commandfor가 ID를 기반으로 작동하기 때문에 다음과 같은 제한 사항이 있음:
- Shadow DOM 간에 요소 참조 불가
- 이 경우 JavaScript API를 사용하여 .commandForElement 속성을 설정할 수 있음
- 예제: Shadow DOM에서 명령 연결
<my-element>
<template shadowrootmode="open">
<button command="show-popover">Show popover</button>
<slot></slot>
</template>
<div popover><!-- ... --></div>
</my-element>
<script>
customElements.define("my-element", class extends HTMLElement {
connectedCallback() {
const popover = this.querySelector('[popover]');
this.shadowRoot.querySelector('button').commandForElement = popover;
}
});
</script>
향후 계획
- Chrome에서는 추가 내장 명령 도입을 계획 중:
- <details> 요소 열기 및 닫기
- <input> 및 <select>에서 "show-picker" 명령 지원
- <video> 및 <audio> 재생 명령
- 요소에서 텍스트 복사 기능