薬剤師のためのautocompleteを、結局初めから作ることになった話
作りたいもの
薬歴を書く際に、薬品名を簡単に入力するための補助アプリを作りたいと考えました。以下の機能を目指します。
- インプットに薬品名の数文字を入力すると、自動的に候補が補完される。
- キー操作でクリップボードに文字列をコピーできる。
- オフライン環境でも動作する。
基本戦略
- 技術スタック: Electron + Vite + React
- 言語: TypeScript
問題
-
ネットで調べた結果、<input> と <datalist> を組み合わせたシンプルな実装が便利そうだったので、試してみました。しかし、以下の問題が発生しました。
-
候補数が多すぎてレイアウトが崩れる。
例:
- <datalist> では柔軟な部分一致検索が難しい。
例えば「ドネぺ3」と入力しても、期待した候補が表示されない。
開発方針
- 入力したキーワード(例:「ドネぺ3」)に対応するため、検索ロジックを自作する。
- 候補が長くなってもレイアウトが崩れないよう、スクロールバーを付ける。
- <datalist> に似た使用感を維持する。
実装
検索
-
ライブラリ:fuse.js
-
oprion: 3文字以上で検索を開始
-
手順:検索文字を一文字ずつ分解して、順次マッチする文字列をピックアップしていく
-
// 検索の開始地点 public matchingbyEachCharactor(query: string) { // queryの正規化 const normalized = this.normalizeQuery(query); const chars = normalized.split(""); for (const c of chars) { this.matchingCore(this.selectedDatas, c); this.selectedDatas = this.extractSuffixByKeyword( c, this.selectedDatas, ); } } protected matchingCore(_data: DrugData[], query: string) { const normalized = this.normalizeQuery(query); const fuse = new Fuse(_data, this.options); this.selectedDatas = fuse.search(normalized).map((r) => r.item); } protected extractSuffixByKeyword( keyword: string, _datas: DrugData[], ): DrugData[] { const normalized = this.normalizeQuery(keyword); // ここで、正規表現を使って、アルファベットのみかどうかを判定 const isAlphabet = /^[a-zA-Z]+$/.test(normalized); return _datas.map((d) => { let newData= ""; let index = -1; if (isAlphabet) { const normalizedLowerCase = normalized.toLowerCase(); index = d.val.toLowerCase().indexOf(normalizedLowerCase); } else { index = d.val.indexOf(normalized); } if (index !== -1) { newData = d.val.substring(index + 1); } return { ...d, val: newData, }; }); }
-
表示部分
- <select> を使用して候補リストを表示
- スクロールバーで候補を快適に閲覧可能にする。
キーバインド実装
-
onKeyDown イベントを使用してキー入力に対応
-
変換中の入力と、リストハイライト用のキー操作を分離して処理する
<input ref={inputRef} id="drugName" type="text" value={searchTerm} onChange={handleInput} onKeyDown={handleKeyDown} onCompositionEnd={compositionEndHandler} onCompositionStart={() => setDidEndComposition(false)} className="w-full px-3 py-2 border border-gray-300 rounded focus:outline focus:outline-1 focus:outline-white" placeholder="入力してください" />
-
つまづきポイント
- 入力中の変換キーイベントと、候補リスト移動用のキーイベントを区別する必要があった。
- onCompositionStart と onCompositionEnd を活用して状態を適切に管理。
- 入力中の変換キーイベントと、候補リスト移動用のキーイベントを区別する必要があった。
成果物
- 全くリファクタリングしていないので、読みにくいが、成果物として、githubに挙げておく
- Github > autocompleteForDrugName