4 ポイント 投稿者 GN⁺ 2025-03-08 | 1件のコメント | WhatsAppで共有
  • ボタンは動的なWebアプリケーションを作るうえで不可欠。メニューを開いたり、操作を切り替えたり、フォームを送信したりするために使われる
  • Chrome 135では、新しい command および commandfor 属性によって、従来の popovertargetaction および popovertarget 属性を改善し、置き換える
  • 既存のボタン動作を実装する際に発生する問題点:
    • 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> の再生命令
    • 要素内テキストのコピー機能

1件のコメント

 
GN⁺ 2025-03-08
Hacker Newsの意見
  • プログラミング言語理論家たちは80年代から、"goto"のより強力なバージョンである"comefrom"について推測してきた。これはintercalでのみ実装されていた。intercalはCのような言語よりも安全性、性能、人間工学の面で優れているが、商業市場への参入には苦労している。javascriptがintercalのこの機能を統合するのを見るのは興味深い。これが、javascriptのクロージャベースのオブジェクトが関数型プログラミングを主流へ導いたように、礼儀正しいプログラミングの急増につながることを願う

  • InvokersはChromeだけのものではない。Firefox nightlyでもすでに利用可能

  • JSなしで宣言的なUI動作を実装するというアイデアは魅力的

    • ポップオーバー/モーダルのボイラープレートを削減する(aria-expandedの操作が不要)
    • show-modalのような組み込みコマンドはアクセシビリティをマークアップに統合する
    • カスタムコマンド(例: --rotate-landscape)により、コンポーネントはHTMLを通じてAPIを公開できる
  • 疑問点:

    • 抽象化 vs. マジック: これは単に複雑さをJSからHTMLへ移しているだけなのか? フレームワークはすでに状態を抽象化している。これはどう共存するのか?
    • Shadow DOMとの摩擦: shadow roots間で.commandForElementを設定するにはJSが必要。これは半分しか解決されていない問題のように見える
    • 将来性: OpenUIが20個以上のコマンド(例: show-picker, toggle-details)を追加した場合、プラットフォームはニッチな構文で膨れ上がるのか?
  • 仕様:

    • button element, commandfor属性
    • button element, command属性
  • これはNext、Be、Appleなどが約30年前に使っていたアクション/メッセージングパターンなのか、それとも私が何か見落としているのか

    • これは有用だったが、基本的なデザインパターンを維持しようとする複雑さのため、インターフェースベースのコントローラーパターンへと進化した。したがって、この箱が開くなら多くの改善要望が出ると予想する
  • Netscapeの初期のJava UIツールキット(IFC)はアクション要素を接続できた

  • 新しいcommandおよびcommandfor属性はpopovertargetactionおよびpopovertarget属性を改善し、置き換える

    • これらは基本的に利用可能になったのか? 置き換えるとはどういう意味か? いずれこれを削除するのか? ウェブ開発者は、もはや不要になったものをアップデートで削除することはできない
  • 文字列でプログラミングすることに完全にアレルギー反応がある。アクセシビリティ上の利点は理解するが、別のWebアプリ動作レイヤーとして要素IDを使うことには特に興味をひかれない

  • 完全なAPIなしにこれを実装すべきではなかった。5個ほどのコマンドではなく、HTMLを通じてあらゆるJavaScript機能を実装できるように見える。そうなると数千のコマンドになり得る

  • HTMLでcommand and conquerに期待していた

  • HTMLを改善し拡張するのは良いことだが、まだ道のりは長い。HTMXチームはいくつか良いアイデアを持っている