import { Theme } from "../../themes"; import React from "react"; import { Middleware, Placement } from "@floating-ui/react"; export type Select2Size = "small" | "medium" | "large"; export type Select2Variant = "light" | "dark"; export type Select2SizeConfig = { fontSize: string; padding: string; iconSize: string; borderRadius: string; height: string; }; export declare const SELECT2_SIZES: Record; export type Select2OptionProps = { /** * 選択肢の値 */ value: string | number; /** * 表示ラベル */ label: string; /** * 無効状態 */ disabled?: boolean; /** * ラベルの前に表示する要素 */ prepend?: React.ReactNode; /** * ラベルの後に表示する要素 */ append?: React.ReactNode; }; /** * onSearchChange が呼ばれた理由。 * 親はこれで分岐する。searchTerm の値から意図を推測しなくてよい。 */ export type Select2SearchReason = /** 検索語が入力された。親は検索して children を差し替える。 */ "search" /** デフォルト候補を求めている。searchTerm は常に空。 */ | "default" /** 検索が打ち切られた。親は取得せず、保留中の debounce と進行中のリクエストを捨てる。 */ | "abort"; /** * サーバー検索(非同期サジェスト)の設定。 * * この機能がやっていることは、突き詰めると一つだけである。 * 「候補の絞り込みを誰が担当するか」を Select2 から親に移す。 * * これまで: 親が全件を children で渡し、Select2 が検索語で絞る * searchMode: 親が検索して絞った結果を children で渡し、Select2 は絞らずに描く * * 候補が数千件あると、全件を渡した時点で開いた瞬間の描画が重くなる。 * 絞り込みを親(サーバー)に移せば、Select2 が受け取る件数そのものが減る。 * * 実装上の切り替え点は Select2.tsx の searchedOptions にある一行だけで、 * 既存のローカル絞り込みは消していない。searchMode のときに通らなくなるだけである。 * 以下に並ぶ挙動は、担当を移したことで必要になった後始末にあたる。 * * 渡すと Select2 は次のように振る舞う。 * * - 検索語が入るまで候補を描画せず、選択済みだけを描画する。 * showDefaultOptions を渡すと、検索前も children を描画する * - 内部フィルタをかけず、親が絞り込んだ children をそのまま描画する * - isLoading のあいだはローディングを表示する。 * 検索前に候補を描画しない構成では、選択済みの表示を優先してローディングを出さない * * 検索窓がないと検索語を入力できないため、searchable の指定に関わらず検索窓を出す。 * showOptionsOnlyWhenSearching を同時に渡した場合、そちらは無視される。 * * 選択済みの option は children に含めて渡す。 * Select2 は children からラベルを引くので、検索結果だけに差し替えると * トリガーの表示ラベルと複数選択時のタグが消える。 * 複数選択では、適用前の保留中の選択も onTempChange で受け取って children に残す。 * * その裏返しで、Select2 は children のどれが検索結果でどれが選択済みかを区別できない。 * 検索が 0 件だったときは hasNoResults を渡す。渡さないと選択済みが検索結果として並ぶ。 */ export type Select2SearchMode = { /** * 検索語が変わったとき、デフォルト候補が必要になったとき、検索が打ち切られたときに呼ばれる。 * どれかは reason で渡す。searchTerm は前後の空白を落とした検索語で、search 以外では常に空。 * * - search: 検索語が入力された。同じ検索語は続けて渡らない * - default: showDefaultOptions のとき、開いた時点と検索語を消した時点で渡る * - abort: 閉じた時点とアンマウントされた時点で渡る。 * showDefaultOptions を渡していなければ、検索語を消した時点でも渡る。 * 親に何も頼んでいなければ渡らない * * 親は reason で分岐する。search と default は取得して children を差し替える。 * abort は取得せず、保留中の debounce と進行中のリクエストを捨てて isLoading を false に戻す。 * * debounce と、応答が前後して返ったときの順序の保証は親側で行う。 */ onSearchChange: (searchTerm: string, reason: Select2SearchReason) => void; /** * 検索結果を取得中かどうか。 * true の間は候補の代わりにローディングを表示する。 * ただし検索前に候補を描画しない構成では、検索語が空のあいだは * 選択済みの表示を優先し、true でもローディングを出さない。 * showDefaultOptions を渡した場合は、検索前もローディングを表示する。 * * abort を受けたら false に戻す。 * 戻さないと、閉じた時点の取得が返らないまま、次に開いてもローディングのままになる。 * @default false */ isLoading?: boolean; /** * 直近の検索が 0 件だったかどうか。 * true の間は候補の代わりに noResultsMessage を表示する。 * 検索語が空のときは見ない。isLoading が true のときはローディングが優先される。 * * children に残した選択済みは検索結果と区別がつかないため、 * 0 件の判定は親から受け取る。 * search の応答で結果の有無に合わせて更新し、default と abort では false に戻す。 * @default false */ hasNoResults?: boolean; /** * 検索語が空のときも children を候補として描画する。 * 最近使った項目やおすすめを検索前に出したいときに使う。 * これを渡したときだけ、reason が default の通知が届く。 * 開いた時点と検索語を消した時点で届くので、親はデフォルト候補を返す。 * * 候補の件数は親が絞る。 * 全件を返すと、開いた瞬間に候補がすべてマウントされる状態に戻る。 * @default false */ showDefaultOptions?: boolean; }; /** * 検索語から新しい選択肢を作る設定。 * `creatable={true}` のときは、ここにある既定値だけが使われる。 */ export type Select2Creatable = { /** * メニューの下端に固定する作成メニューのラベル。候補をスクロールしても見える。 * 押すとその行が入力欄になる。Enter でその名前を作成する。作成できる名前が入っているときは右側に、角丸の矩形で囲んだ Enter の矢印が出る。押すものではなく、Enter で作成する目印。Esc では入力を取り消してこのメニューに戻る。検索窓にフォーカスしたときも入力を取り消す。検索窓は使わない。 * @default "新規作成" */ createActionLabel?: string; /** * 作成行を選んだときに呼ばれる。引数は入力値そのもの。前後の空白(全角スペースを含む)も残す。 * 渡したときは Select2 は選択値を変えない。親が children と value を更新する。 * 渡さないときは、その入力値を値として選ぶ。 * 単一選択は置き換え、複数選択は追加する。複数選択の確定タイミングは通常の選択と同じ。 */ onCreate?: (inputValue: string) => void; /** * 追加の作成可否。false なら作成行を出さない。 * 引数は入力値そのもの。前後の空白(全角スペースを含む)も残す。 * 空文字と空白だけ(全角スペースを含む)は、この関数より先に常に拒否する。 * 同じラベルや同じ value を止めるかも、この関数で決める。 */ isValidNewOption?: (inputValue: string) => boolean; /** * 作成の通信中かどうか。 * true のあいだは作成行を出さない。 * 単一選択はメニューを閉じ、トリガーを操作できなくして矢印の位置にローディングを出す。 * 複数選択はメニューを開いたまま、トリガーの矢印とメニューにローディングを出す。 * onCreate の開始で true にし、待ちが終わったら false に戻す。失敗したときも戻す。 * @default false */ isCreating?: boolean; }; export type Select2StyleProps = { theme: Theme; $size: Select2Size; $variant: Select2Variant; $disabled?: boolean; $error?: boolean; $isOpen?: boolean; $hasValue?: boolean; }; export type Select2Props = { /** * 選択された値。multipleがtrueの場合は配列、falseの場合は単一の値 */ value?: string | number | (string | number)[]; /** * 値が変更された時のコールバック */ onChange?: ((value: string | number) => void) | ((values: (string | number)[]) => void); /** * 複数選択モード * @default false */ multiple?: boolean; /** * コンポーネントのサイズ * @default "medium" */ size?: Select2Size; /** * コンポーネントのバリアント * @default "light" */ variant?: Select2Variant; /** * タグのバリアント(複数選択時) * @default 指定がない場合、Select2のvariantに応じて自動的に決定されます。 * variant="light"の場合はdark、variant="dark"の場合はlightが適用されます。 */ tagVariant?: Select2Variant; /** * 複数選択時のタグ表示方法 * - truncate: 規定の長さを超えたらoverflow hiddenで端折る(デフォルト) * - wrap: タグを折り返して全て表示(高さが伸びる) * @default "truncate" */ tagOverflow?: "truncate" | "wrap"; /** * 無効状態 * @default false */ disabled?: boolean; /** * プレースホルダー * @default "選択してください" */ placeholder?: string; /** * 検索機能を有効にするかどうか * @default false */ searchable?: boolean; /** * 検索語から新しい選択肢を作れるようにする。 * true のときは searchable の指定に関わらず検索窓を出す。 * オブジェクトを渡すと、作成行の文言や作成時の処理を指定できる。 * @default false */ creatable?: boolean | Select2Creatable; /** * 検索入力のプレースホルダー * searchableがtrueの場合のみ使用されます * @default "検索" */ searchPlaceholder?: string; /** * 検索結果がない場合のメッセージ * searchableがtrueの場合のみ使用されます * @default "オプションがありません" */ noResultsMessage?: string; /** * 検索語が入力されるまで候補リストを描画しない * 候補が大量にある場合に、開いた瞬間の全件描画を避けるためのオプション。 * searchable=true が前提(検索窓がないと候補を表示できないため)。 * @default false * @deprecated searchMode を使う。 * このオプションは描画を遅らせるだけで、絞り込みはローカルのまま残る。 * そのため緩い検索語では、一致した候補が件数の上限なく描画される。 * searchMode は遅延描画を含んだうえで、絞り込み自体を親に任せられる。 */ showOptionsOnlyWhenSearching?: boolean; /** * サーバー検索(非同期サジェスト)を有効にする。 * 詳細は Select2SearchMode を参照。 */ searchMode?: Select2SearchMode; /** * 検索前に描画する候補がないときに表示する案内文言 * showOptionsOnlyWhenSearching と searchMode のどちらでも使われる。 * @default "キーワードを入力してください" */ searchPromptMessage?: string; /** * エラー状態 * @default false * * 注意: エラーメッセージの表示はコンポーネント側では行われません。 * エラーメッセージはプロダクト側で実装してください。 */ error?: boolean; /** * 複数選択時、選択肢の変更を適用ボタンなしで即時確定する * multiple=true のときのみ有効 * @default false */ commitOnSelect?: boolean; /** * 全選択・全クリアボタンを表示するかどうか (multiple=trueの場合のみ有効) * 全選択の対象は描画している候補である。 * searchMode では親が返した候補だけが対象になり、サーバー側で一致する全件は選ばない。 * @default false */ showBulkActions?: boolean; /** * 全選択ボタンのtooltipテキスト * @default "すべて選択" */ selectAllLabel?: string; /** * 全クリアボタンのtooltipテキスト * @default "すべてクリア" */ clearAllLabel?: string; /** * 適用ボタンのテキスト (multipleがtrueの場合のみ使用) * @default "適用" */ applyButtonText?: string; /** * キャンセルボタンのテキスト (multipleがtrueの場合のみ使用) * @default "キャンセル" */ cancelButtonText?: string; /** * 子要素(宣言的API用) * Select2Option、ContextMenu2HeadingItem、ContextMenu2SeparatorItemなどを指定できます */ children?: React.ReactNode; /** * 複数選択時の一時的な選択値が変化したときに呼ばれるコールバック * multiple=trueのときのみ有効 */ onTempChange?: (tempSelected: (string | number)[]) => void; /** * ドロップダウンメニューの幅(ピクセル) * 指定しない場合は内容にフィットします */ width?: number; /** * ドロップダウンメニューの最小幅(ピクセル) * 指定しない場合は内容にフィットします */ minWidth?: number; /** * Floating UIのplacement(メニューの表示位置) * 詳細は https://floating-ui.com/docs/tutorial#placements を参照 */ placement?: Placement; /** * Floating UIのmiddleware配列(高度なメニュー位置制御用) * 詳細は https://floating-ui.com/docs/middleware を参照 */ middleware?: Middleware[]; };