テーブル編集UIの全面刷新とコードのリファクタリング
Lexxyエディタのテーブル編集機能が、UIと内部実装の両面で大幅に改善されました。#599では、ユーザビリティの向上と保守性の改善を目指した包括的な変更が行われています。
背景
これまでのテーブル操作UIは、すべての機能が単一の「…」メニューに集約されており、行と列の操作が混在していました。また、内部実装ではTableHandlerクラスが複数の責務を抱えており、機能追加や保守が困難な状態でした。このPRでは、UIの改善と並行して、コードベースの大規模なリファクタリングが実施されています。
UI/UXの改善
専用ドロップダウンの導入
行と列の操作メニューが分離され、それぞれ独立したドロップダウンとして提供されるようになりました。これにより、各操作ボタンの近くに対応するメニューが配置され、直感的な操作が可能になっています。
ビジュアルフィードバック
メニューオプションにホバーすると、その操作が及ぼす影響がビジュアルに表示されます。例えば、行を挿入する場合、挿入位置がハイライトされて確認できます。
これは、新しく追加されたCSS変数とクラスによって実現されています:
--lexxy-color-table-cell-add: var(--lexxy-color-selected-50);
--lexxy-color-table-cell-toggle: var(--lexxy-color-accent-lightest);
--lexxy-color-table-cell-remove: oklch(60% 0.15 27 / 0.1);
--lexxy-table-cell-add-size: 4px;
&.lexxy-content__table-cell--highlight {
&[data-action="insert"] {
&[data-child-type="row"] {
&[data-direction="after"] {
&:after {
box-shadow:
0 var(--lexxy-table-cell-add-size) 0 0 var(--lexxy-color-table-cell-add),
0 calc(-1 * var(--lexxy-table-cell-add-size) - 1px) 0 0 var(--lexxy-color-table-cell-add) inset;
}
}
}
}
}
テーブルのリサイズ制約
ブラウザネイティブのテーブルリサイズは調整が困難なため、セルに最小幅と最大幅を設定することで、より予測可能なレイアウトを実現しています:
th,
td {
border: 1px solid var(--lexxy-color-ink-lighter);
min-width: 5ch;
max-width: 50ch;
padding: 1ch;
}
セル選択の視覚的改善
テーブル全体ではなく、選択されたセルのみがハイライトされるようになりました。フォーカス状態には専用のクラスlexxy-content__table-cell--focusが追加され、2pxのインセットボーダーで表示されます。
アーキテクチャの刷新
責務の分離
従来のTableHandlerクラスは削除され、明確な責務を持つ2つのクラスに分割されました:
TableController: テーブルの状態管理とロジック
export class TableController {
constructor(editorElement) {
this.editor = editorElement.editor
this.contents = editorElement.contents
this.selection = editorElement.selection
this.currentTableNodeKey = null
this.currentCellKey = null
this.#registerKeyHandlers()
}
// セル選択、ヘッダー切り替え、行・列の挿入/削除、
// セルナビゲーション、キーボード処理などを担当
}
TableTools: UIコンポーネント
export class TableTools extends HTMLElement {
connectedCallback() {
this.tableController = new TableController(this.#editorElement)
this.#setUpButtons()
this.#monitorForTableSelection()
this.#registerKeyboardShortcuts()
}
// ツールバーの表示制御とユーザーインタラクションを担当
}
Lexical Extensionへの移行
テーブル関連のLexical設定が、新しいTablesLexicalExtensionに統合されました:
export const TablesLexicalExtension = defineExtension({
name: "lexxy/tables",
nodes: [
WrappedTableNode,
{
replace: TableNode,
with: () => new WrappedTableNode()
},
TableCellNode,
TableRowNode
],
register(editor) {
registerTablePlugin(editor)
registerTableSelectionObserver(editor, true)
setScrollableTablesActive(editor, true)
// バグ修正: 背景色のハードコード問題を解決
editor.registerNodeTransform(TableCellNode, (node) => {
if (node.getBackgroundColor() === null) {
node.setBackgroundColor("")
}
})
}
})
この変更により、エディタ初期化時の設定が簡潔になり、テーブル関連の機能が一箇所にまとまりました。
コマンドディスパッチャーの簡素化
テーブル操作用の個別コマンド(insertTableRowAbove、deleteTableColumnなど)が削除され、TableController内で直接処理されるようになりました:
const COMMANDS = [
"bold",
"italic",
// ...
"insertTable",
// テーブル操作コマンドは削除
"undo",
"redo"
]
これにより、コマンドの階層が浅くなり、コードの見通しが向上しています。
設計判断
Web Componentsの活用
UI部分はHTMLElementを継承したTableToolsクラスとして実装されており、ライフサイクル管理が明確になっています。connectedCallbackとdisconnectedCallbackでリソースの初期化と解放が行われ、メモリリークのリスクが軽減されています。
CSS変数による柔軟なテーマ設定
ビジュアルフィードバックの色は、すべてCSS変数として定義されています。これにより、テーマのカスタマイズが容易になり、JavaScriptコードに色の値がハードコードされることを避けています。
Lexicalバグの修正を含む
このPRでは、Lexical本体のバグ(#8089、#8090)に対するワークアラウンドも実装されています。TableCellNodeの背景色とヘッダースタイルの問題が、registerNodeTransformを使用して修正されています。