4P by GN⁺ 26일전 | ★ favorite | 댓글 1개
  • 버튼은 동적 웹 애플리케이션을 만드는 데 필수임. 메뉴를 열고, 작업을 전환하고, 폼을 제출하는데 사용
  • Chrome 135에서는 새로운 commandcommandfor 속성으로 이전의 popovertargetactionpopovertarget 속성을 개선하고 대체함
  • 기존에 버튼 동작을 구현할 때 발생하는 문제점:
    • HTML의 onclick 핸들러는 보안 정책(CSP)으로 인해 실제 코드에서 사용이 제한될 수 있음
    • 버튼과 다른 요소의 상태 동기화가 필요하며, 접근성을 유지하면서 상태를 관리하는 코드는 복잡함
    • React, AlpineJS, Svelte 등에서도 상태 및 이벤트 핸들링이 복잡함

commandcommandfor 패턴

  • commandcommandfor 속성을 사용하면 버튼이 다른 요소에 대해 선언적으로 동작할 수 있음. 이는 프레임워크의 편리함을 제공하면서도 유연성을 유지
  • commandfor 버튼은 ID를 사용(for속성과 비슷) 하고, command는 내장 값을 받아 더 직관적인 접근 방식을 제공
  • 예제: 메뉴 열기 버튼 구현
    • aria-expanded나 추가적인 JavaScript가 필요하지 않음
    <button commandfor="my-menu" command="show-popover">  
      Open Menu  
    </button>  
    <div popover id="my-menu">  
      <!-- ... -->  
    </div>  
    

commandcommandfor vs popovertargetactionpopovertarget

  • popover를 사용한 적이 있다면 popovertargetpopovertargetaction 속성에 익숙할 수 있음
  • 이들은 commandforcommand와 유사하게 작동하지만, 팝오버에 특화
  • 새로운 속성은 이전 속성을 완전히 대체하며, 추가 기능을 제공함

내장 명령

  • 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> 재생 명령
    • 요소에서 텍스트 복사 기능
Hacker News 의견
  • 프로그래밍 언어 이론가들은 80년대부터 "goto"의 더 강력한 버전인 "comefrom"에 대해 추측해 왔음. 이는 intercal에서만 구현되었음. intercal은 C와 같은 언어보다 안전성, 성능, 인체공학적으로 우수하지만 상업 시장에 진입하는 데 어려움을 겪고 있음. javascript가 intercal의 이 기능을 통합하는 것을 보는 것은 흥미로움. 이는 javascript의 클로저 기반 객체가 함수형 프로그래밍을 주류로 이끈 것처럼 정중한 프로그래밍의 급증으로 이어질 수 있기를 바람

  • Invokers는 Chrome만의 것이 아님. Firefox nightly에서도 이미 사용 가능함

  • JS 없이 선언적 UI 동작을 구현하는 아이디어는 매력적임

    • 팝오버/모달의 보일러플레이트를 제거함 (aria-expanded 조작 불필요)
    • show-modal과 같은 내장 명령은 접근성을 마크업에 통합함
    • 사용자 정의 명령(e.g., --rotate-landscape)은 HTML을 통해 컴포넌트가 API를 노출할 수 있게 함
  • 의문점:

    • 추상화 vs. 마법: 이는 단순히 복잡성을 JS에서 HTML로 이동시키는 것인가? 프레임워크는 이미 상태를 추상화함. 이는 어떻게 공존할 것인가?
    • Shadow DOM 마찰: shadow roots 간에 .commandForElement를 설정하기 위해 JS가 필요함. 이는 절반만 해결된 문제처럼 보임
    • 미래 대비: OpenUI가 20개 이상의 명령(e.g., show-picker, toggle-details)을 추가하면, 플랫폼이 틈새 구문으로 부풀어 오를 것인가?
  • 사양:

    • button element, commandfor 속성
    • button element, command 속성
  • 이것이 Next, Be, Apple 등이 약 30년 전에 사용한 액션/메시징 패턴인가, 아니면 내가 뭔가 놓친 것인가

    • 이는 유용했지만 기본적인 디자인 패턴을 유지하려는 복잡성 때문에 인터페이스 기반 컨트롤러 패턴으로 진화했음. 따라서 이 상자가 열리면 많은 개선 요청이 있을 것으로 예상함
  • Netscape의 초기 Java UI 툴킷(IFC)이 액션 요소를 연결할 수 있게 했음

  • 새로운 command 및 commandfor 속성은 popovertargetaction 및 popovertarget 속성을 개선하고 대체함

    • 이것들이 기본적으로 사용 가능해진 것인가? 대체한다는 것은 무슨 의미인가? 언젠가 이를 제거할 것인가? 웹 개발자들이 더 이상 필요하지 않은 것을 업데이트로 제거할 수는 없음
  • 문자열로 프로그래밍하는 것에 완전히 알레르기 반응을 보임. 접근성 이점은 이해하지만, 또 다른 웹 앱 동작 레이어로 요소 ID를 사용하는 것에 대해 특별히 흥미롭지 않음

  • 전체 API 없이 이를 구현하지 말았어야 함. 5개 정도의 명령 대신, HTML을 통해 모든 JavaScript 기능을 구현할 수 있는 것처럼 보임. 이는 수천 개의 명령이 될 수 있음

  • HTML에서 command and conquer에 대한 기대감이 있었음

  • HTML을 개선하고 확장하는 것은 좋지만, 아직 갈 길이 멀음. HTMX 팀이 몇 가지 좋은 아이디어를 가지고 있음