ファイルアップロードのライフサイクルイベントを追加

basecamp/lexxy

<lexxy-editor> 要素でファイルアップロードの開始・進捗・終了を検知できる3つのカスタムイベントが追加されました。これにより、アップロード中のフォーム送信ブロックやカスタムプログレスUIの実装が、DOM操作のハックなしに実現できます。

背景

これまで lexxy では、アップロード中かどうかを判定する公式な手段が提供されていませんでした。#735 で報告されているように、コンシューマーはアップロード進行中の判定に <lexxy-editor> 内の progress 要素の存在をDOMクエリで確認するという回避策を取らざるを得ませんでした。

具体的には、lexxy:change イベントのたびに this.element.querySelector("lexxy-editor progress") !== null を評価してアップロード中フラグを推測し、送信ボタンの disabled を切り替えるといった実装が必要でした。

この回避策はアップロード状態の内部表現(progress 要素)に依存しているため、実装の詳細変化に脆弱でした。専用イベントの追加はこの脆弱な結合を解消します。

技術的な変更

ActionTextAttachmentUploadNode クラスに #dispatchEvent プライベートメソッドが追加され、アップロードの各フェーズで3種類のイベントをディスパッチするようになりました。

変更前:

import { createElement } from "../helpers/html_helper"

// ...

upload.create((error, blob) => {
  if (error) {
    this.#handleUploadError(error)
  } else {
    this.#showUploadedAttachment(blob)
  }
})

// ...

#handleUploadProgress(event) {
  this.#setProgress(Math.round(event.loaded / event.total * 100))
}

変更後:

import { createElement, dispatch } from "../helpers/html_helper"

// ...

this.#dispatchEvent("lexxy:upload-start", { file: this.file })

upload.create((error, blob) => {
  if (error) {
    this.#dispatchEvent("lexxy:upload-end", { file: this.file, error })
    this.#handleUploadError(error)
  } else {
    this.#dispatchEvent("lexxy:upload-end", { file: this.file, error: null })
    this.#showUploadedAttachment(blob)
  }
})

// ...

#handleUploadProgress(event) {
  const progress = Math.round(event.loaded / event.total * 100)
  this.#setProgress(progress)
  this.#dispatchEvent("lexxy:upload-progress", { file: this.file, progress })
}

// ...

#dispatchEvent(name, detail) {
  const figure = this.editor.getElementByKey(this.getKey())
  if (figure) dispatch(figure, name, detail)
}

イベントのディスパッチ元は figure 要素(アタッチメントのDOM表現)です。イベントはバブルアップするため、親の <lexxy-editor> 要素でリッスンできます。各イベントの detail には以下が含まれます:

  • lexxy:upload-start: { file } — アップロード対象のファイル
  • lexxy:upload-progress: { file, progress } — ファイルと0〜100の進捗値
  • lexxy:upload-end: { file, error } — 成功時は error: null、失敗時はエラーオブジェクト

#dispatchEventfigure 要素が存在する場合のみディスパッチするガード節を持っており、ノードがDOMに未マウントの状態での呼び出しに対して安全です。

設計判断

イベントのディスパッチ元として、エディタルート要素ではなく figure 要素 が選ばれています。

これにより、複数ファイルが同時にアップロードされる場合でも、各イベントの発生元ノードを event.target で区別できます。一方でバブルアップを利用することで、<lexxy-editor> 要素の単一リスナーで全アップロードイベントをまとめて処理することも可能です。

lexxy:upload-enddetail.error は成功時に null として明示的に含まれており、'error' in event.detail による存在確認ではなく event.detail.error !== null という統一した判定パターンで成否を識別できます。このインターフェース設計は、成功・失敗の両パスで同じ detail 構造を保証します。

まとめ

本PRは、DOM内部構造への依存という脆弱な回避策を、公式イベントAPIへ置き換える変更です。figure 要素からのバブルアップという設計により、単一ファイルの追跡と複数ファイルの一括監視の両方のユースケースを一つのイベントモデルで実現しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
b52c65e7

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

「リード文(総論)→背景・技術・設計(各論)→まとめ(結論)」という理想的な3部構成が明確に守られています。特に、任意項目である「設計判断」セクションが含まれており、変更の背景にある意図まで深く解説できていて素晴らしいです。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きのシンタックスハイライト(```言語:ファイルパス)やGitHubのIssue/PRへのリンク記法([#番号](URL))が、ガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

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

「DOMクエリ」「脆弱な結合」「イベントバブリング」といった専門用語を前提として使用しており、専門知識を持つエンジニアという対象読者に適切にフォーカスされています。

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

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

各セクションが「総論→各論」で構成され、各パラグラフがトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が忠実に守られています。これにより、非常に高い可読性が実現されています。

Diff内容との照合 ✓ PASS

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

記事内で引用されている「変更前」「変更後」のコードは、提供されたDiff情報(src/nodes/action_text_attachment_upload_node.js)と完全に一致しており、正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「ディスパッチ」「バブルアップ」「ガード節」など、技術用語が文脈に応じて正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

「figure要素からイベントがディスパッチされる」「成功時にerror: nullが渡される」といった技術的な説明は、すべてDiff内のコードで裏付けられており、正確です。

事実の突合 ✓ PASS

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

記事内の主張はすべて、PRのDescription、Diff、関連Issue番号に基づいており、根拠のない推測や憶測(ハルシネーション)は一切見られません。特に「設計判断」はPRに明記されていないものの、コードから読み取れる設計思想を的確に言語化しており、価値ある洞察となっています。

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

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

PR番号(#736)、Issue番号(#735)などの数値や固有名詞は、提供された情報と完全に一致しています。

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

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

記事タイトル「ファイルアップロードのライフサイクルイベントを追加」は、PRのタイトル「Add upload lifecycle events」を正確に反映しており、内容との整合性も完璧です。

外部知識の正確性 ✓ PASS

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

PR情報に含まれない外部の知識(バージョン情報、リリース予定など)は一切記載されておらず、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

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

「これまで」と「追加されました」といった時間表現は、問題の背景と今回の変更を正しく時系列に沿って記述しており、適切です。