`wa-textarea`の`resize="auto"`が非表示状態から復元後も高さが折りたたまれたままになるバグを修正

shoelace-style/webawesome

<wa-textarea resize="auto">が初期状態で非表示のコンテナ内に配置されていた場合、表示後も高さが正しく計算されないバグが修正されました。ResizeObserverの監視対象と監視タイミングの見直しにより、幅変化および非表示→表示遷移の両方で自動リサイズが正しく機能するようになります。

背景

リグレッションにより、display: noneを持つ親要素内の<wa-textarea resize="auto">が、表示後も高さ0のままになる問題が報告されました(#2347)。

問題の根本原因は、非表示状態では内部の<textarea>scrollHeightが0になるため、その時点で計算された高さがそのまま固定されてしまう点にあります。コンポーネントが最初のupdated()でResizeObserverを一度だけ設定し、その後resize属性が変更されてもオブザーバーを再生成しない実装になっていたため、自動リサイズのトリガーが失われていました。

また、同じ実装上の問題として、resize="vertical"などのマニュアルモードからresize="auto"に切り替えた場合も、古いオブザーバーが内部の<textarea>を監視し続けるため、幅変化による高さの再計算が機能しないリグレッションも存在していました。

技術的な変更

updateResizeObserver()メソッドが大幅に改修され、autoモードと手動リサイズモードで監視対象と処理内容を分岐するようになりました。

変更前:

// The resize observer is only needed for manual resize modes (vertical, horizontal, both)
// to sync the base wrapper dimensions with the textarea.
const needsObserver = this.resize !== 'none' && this.resize !== 'auto';

if (needsObserver && !this.resizeObserver && this.input) {
  this.resizeObserver = new ResizeObserver(() => this.setTextareaDimensions());
  this.resizeObserver.observe(this.input);
} else if (!needsObserver && this.resizeObserver) {
  this.resizeObserver.disconnect();
  this.resizeObserver = undefined;
}

変更後:

private lastObservedWidth = 0;

// ...

const needsObserver = this.resize !== 'none';

// Always tear down first.
if (this.resizeObserver) {
  this.resizeObserver.disconnect();
  this.resizeObserver = undefined;
}

if (needsObserver && this.input) {
  if (this.resize === 'auto') {
    this.resizeObserver = new ResizeObserver(entries => {
      const width = entries[0]?.contentRect.width ?? 0;
      if (width !== this.lastObservedWidth) {
        this.lastObservedWidth = width;
        requestAnimationFrame(() => this.setTextareaDimensions());
      }
    });
    this.resizeObserver.observe(this);
  } else {
    this.resizeObserver = new ResizeObserver(() => this.setTextareaDimensions());
    this.resizeObserver.observe(this.input);
  }
}

変更は3点に整理できます。

  • 監視対象の変更: autoモードでは内部の<textarea>ではなくホスト要素(this)を監視するようにした。非表示状態から表示状態への遷移(display: noneの解除)でコンテナの幅変化が発生するため、ホスト要素を監視することでこのイベントを捕捉できる。
  • 幅変化のみへの絞り込み: autoモードのコールバックでは lastObservedWidth と比較して幅が変化した場合のみsetTextareaDimensions()を呼び出す。高さの変化は無視することで、自身の高さ変更がオブザーバーを再帰的にトリガーするループを防いでいる。
  • requestAnimationFrameによる遅延実行: setTextareaDimensions()の呼び出しを次のフレームに遅延させることで、ResizeObserverのコールバック内で同期的にレイアウトを変更することによる「ResizeObserver loop completed」警告を回避している。

さらに、updateResizeObserver()の冒頭で既存のオブザーバーを無条件に破棄してから再生成する構造に変わりました。これにより、resize属性の変更時に古いオブザーバーが残存する問題が解消されます。

設計判断

autoモードと手動モードで監視対象を分離する設計が採用されました。

手動リサイズモード(verticalhorizontalboth)では、ユーザーが内部の<textarea>をドラッグしてサイズを変更した際にホストのラッパー要素へ寸法を同期する必要があります。このため内部の<textarea>を監視することが適切です。一方、autoモードではユーザーによるドラッグは発生せず、幅の変化に応じた高さの再計算が目的です。監視対象を異なる要素にすることで、それぞれのモードで必要なイベントだけを正確に捕捉できます。

また、常にオブザーバーを破棄してから再生成する構造は、モード切り替え時に古いオブザーバーが誤った要素を監視し続けるリグレッションを構造的に排除します。「まず壊してから正しく作り直す」パターンにより、条件分岐の複雑さを減らしながら正確性を確保しています。

まとめ

本修正は、autoモードにおけるResizeObserverの監視対象を内部要素からホスト要素へ変更し、非表示→表示遷移を幅変化として捕捉できるようにした変更です。高さ変化の無視とrequestAnimationFrameによる遅延実行を組み合わせることで、再帰ループや「ResizeObserver loop」警告を回避しつつ、初期非表示のコンテナ内でも自動リサイズが正しく動作するようになります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
1f212666

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→各論(背景、技術詳細、設計判断)→まとめ(結論)という構成が明確であり、ガイドラインに完全に準拠しています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライト、PR番号・Issue番号のリンク記法がすべて正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

ResizeObserverやリグレッションといった専門用語を前提としており、専門知識を持つエンジニアという対象読者に適合した内容です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成で、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事で引用されている変更前後のコードは、提供されたDiff情報と完全に一致しており、正確に内容を反映しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

ResizeObserver、scrollHeight、requestAnimationFrameなどの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

監視対象の変更、再帰ループの防止策、モード切替時のリグレッション解消など、すべての技術的説明がDiffのコードとコメントによって裏付けられており、正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、PRのDescription、Diff内のコード、コメント、テストコードの記述によって裏付けられており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#2356)、Issue番号(#2347)、ファイルパスなどの数値・固有名詞はすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRの主題をより具体的に説明したものであり、内容と完全に一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事の内容は提供されたPR情報に限定されており、バージョン情報やリリース予定といった外部知識の追記(捏造)は見られません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「修正されました」「存在していました」といった時間表現は、PRの文脈と一致しており、正確です。