@ecatchup/basercms-ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/index.d.ts +44 -5
- package/dist/index.js +207 -199
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,6 +93,36 @@ type SelectOption = {
|
|
|
93
93
|
/>
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
## 入力欄のクラス・属性(既定で baserCMS の input スタイルが当たります)
|
|
97
|
+
|
|
98
|
+
`SearchSelect` の `triggerProps` / `searchInputProps`、`MultiSelectPicker` の
|
|
99
|
+
`searchInputProps` は、既定で以下の値になっています(baserCMS の
|
|
100
|
+
`.bca-textbox__input` を前提にしています)。
|
|
101
|
+
|
|
102
|
+
| prop | 既定値 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `triggerProps`(SearchSelect) | `{ className: 'bca-textbox__input' }` |
|
|
105
|
+
| `searchInputProps`(SearchSelect) | `{ className: 'bca-textbox__input' }` |
|
|
106
|
+
| `searchInputProps`(MultiSelectPicker) | `{ className: 'bca-textbox__input' }` |
|
|
107
|
+
|
|
108
|
+
**既定値は「置き換え」です。マージではありません。** 何か1つでも渡すと、既定値は使われず渡した内容がそのまま使われます。既定のクラス・属性が不要な場合は空オブジェクト(`{}`)を渡してください。ただしこれで置き換わるのは追加のクラス・属性だけで、部品自身の BEM クラス(`bca-search-select__trigger` 等)は常に付くため、`{}` を渡しても部品同梱の CSS は効き続けます(baserCMS の input スタイルだけ外したい場合に使えます)。`MultiSelectDialog` / `MultiSelectField` に渡した `searchInputProps` は、内部の `MultiSelectPicker` の検索欄へそのまま転送されます。`type` / `value` / `onChange` / `placeholder` / `disabled`(`triggerProps` では加えて `role` / `tabIndex` / `onClick` / `onKeyDown` / ARIA 属性)は部品側が制御するため渡せません。
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// baserCMS 以外の見た目にしたい場合
|
|
112
|
+
<SearchSelect
|
|
113
|
+
triggerProps={{ className: 'my-trigger' }}
|
|
114
|
+
searchInputProps={{ className: 'my-search-input' }}
|
|
115
|
+
...
|
|
116
|
+
/>
|
|
117
|
+
|
|
118
|
+
// 何も付けたくない場合
|
|
119
|
+
<SearchSelect
|
|
120
|
+
triggerProps={{}}
|
|
121
|
+
searchInputProps={{}}
|
|
122
|
+
...
|
|
123
|
+
/>
|
|
124
|
+
```
|
|
125
|
+
|
|
96
126
|
## フォーム連携
|
|
97
127
|
|
|
98
128
|
`name` を渡すと hidden input を描画します。
|
|
@@ -102,6 +132,43 @@ type SelectOption = {
|
|
|
102
132
|
<MultiSelectField name="data[User][group_ids][]" ... /> // 選択件数分の hidden
|
|
103
133
|
```
|
|
104
134
|
|
|
135
|
+
## 落とし穴: 複数選択で「全部外す」と何も送信されない問題
|
|
136
|
+
|
|
137
|
+
HTML フォームには、選択が 0 件だと**そのキー自体が送信されない**という性質があります。
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
2件選択 → data[User][group_ids][] = 5
|
|
141
|
+
data[User][group_ids][] = 8
|
|
142
|
+
全部外す → (何も送信されない)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
サーバー側はこれを「全部外した」のか「そもそもこのフォームにこの項目が無かった」のか区別できません。結果として、**全解除の保存ができない**という不具合になります(`SearchSelect` の単一選択は未選択でも `value=""` の hidden が常に1つ出るため、この問題は起きません)。
|
|
146
|
+
|
|
147
|
+
`MultiSelectField` は `name` を渡すと、既定でこの問題を解決する「センチネル」の hidden を追加で出力します。
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<input type="hidden" name="data[User][group_ids]" value=""> ← センチネル([] なし・常に出力)
|
|
151
|
+
<input type="hidden" name="data[User][group_ids][]" value="5"> ← 選択分([] あり)
|
|
152
|
+
<input type="hidden" name="data[User][group_ids][]" value="8">
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**なぜセンチネルの name から `[]` を外すのか。** `name` をそのまま(`[]` 付きの配列記法)にして空の hidden を送ると、サーバー側では `['']`(要素が1つだけの配列)として届きます。PHP の `empty($value)` はこれに対して `false`(=値がある)を返してしまうため、必須チェックが素通りしてしまいます。`[]` を外すと、全解除時はセンチネルの `''`(空文字列)だけが届き、`empty()` は正しく `true` を返します。これは CakePHP の `FormHelper::select()`(`multiple` 指定時)が採用しているのと同じ方式です。
|
|
156
|
+
|
|
157
|
+
このパッケージは、`name` の末尾に `[]` があればそれを取り除いた name をセンチネルに自動採用します。挙動を変えたい場合は `emptyName` で上書き・無効化できます。なお、空文字(`""`)を渡した場合も `false` と同じくセンチネルは出力されません(動的に組み立てた name が意図せず空文字になる場合はご注意ください)。
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
<MultiSelectField name="data[User][group_ids][]" ... />
|
|
161
|
+
// → センチネルは自動で "data[User][group_ids]"([] なし)
|
|
162
|
+
|
|
163
|
+
<MultiSelectField name="data[User][group_ids][]" emptyName="custom_name" ... />
|
|
164
|
+
// → センチネルの name を "custom_name" にする
|
|
165
|
+
|
|
166
|
+
<MultiSelectField name="data[User][group_ids][]" emptyName={false} ... />
|
|
167
|
+
// → センチネルを出力しない(自前でハンドリングしたい場合)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
センチネルは選択件数に関わらず常に出力され、選択分の hidden より DOM 上で前に出ます。これも CakePHP と同じ順序で、PHP のフォーム解析では同名キーが複数あると「後に来た方が勝つ」ため、センチネルを先に出すことで、選択がある場合はその値で正しく上書きされます。
|
|
171
|
+
|
|
105
172
|
## テーマ
|
|
106
173
|
|
|
107
174
|
CSS 変数を上書きしてください。
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ButtonHTMLAttributes } from 'react';
|
|
2
|
+
import { HTMLAttributes } from 'react';
|
|
3
|
+
import { InputHTMLAttributes } from 'react';
|
|
2
4
|
import { JSX } from 'react';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -13,9 +15,20 @@ export declare type ButtonPassthroughProps = Omit<ButtonHTMLAttributes<HTMLButto
|
|
|
13
15
|
[key: `data-${string}`]: string | number | boolean | undefined;
|
|
14
16
|
};
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* 部品内の検索欄(input)へ、利用側から任意のクラス・data 属性等を渡すための型。
|
|
20
|
+
*
|
|
21
|
+
* `type` / `value` / `onChange` / `placeholder` / `disabled` は部品側が制御する
|
|
22
|
+
* ため除外している。baserCMS 等、既存のデザインシステムの input スタイル
|
|
23
|
+
* (クラス+ data 属性)を当てたい場合に使う。
|
|
24
|
+
*/
|
|
25
|
+
export declare type InputPassthroughProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'value' | 'onChange' | 'placeholder' | 'disabled'> & {
|
|
26
|
+
[key: `data-${string}`]: string | number | boolean | undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
16
29
|
export declare const MultiSelectDialog: ({ open, title, options, initialValue, onSubmit, onCancel, submitLabel, cancelLabel, requireSelection, submitButtonProps, cancelButtonProps, className, ...pickerProps }: MultiSelectDialogProps) => JSX.Element | null;
|
|
17
30
|
|
|
18
|
-
export declare type MultiSelectDialogProps = Pick<MultiSelectPickerProps, 'options' | 'searchPlaceholder' | 'noResultsText' | 'listHeight' | 'maxSelected' | 'className'> & {
|
|
31
|
+
export declare type MultiSelectDialogProps = Pick<MultiSelectPickerProps, 'options' | 'searchPlaceholder' | 'noResultsText' | 'listHeight' | 'maxSelected' | 'className' | 'searchInputProps'> & {
|
|
19
32
|
open: boolean;
|
|
20
33
|
title?: string;
|
|
21
34
|
initialValue?: SelectOption[];
|
|
@@ -35,13 +48,19 @@ export declare type MultiSelectDialogProps = Pick<MultiSelectPickerProps, 'optio
|
|
|
35
48
|
* 「追加ボタン → モーダルで選択 → 決定 → タグとして並ぶ → × で削除」
|
|
36
49
|
* をひとまとめにしたフォーム部品
|
|
37
50
|
*/
|
|
38
|
-
export declare const MultiSelectField: ({ options, value, onChange, name, addButtonLabel, dialogTitle, emptyText, disabled, className, maxSelected, submitLabel, cancelLabel, requireSelection, addButtonProps, submitButtonProps, cancelButtonProps, ...pickerProps }: MultiSelectFieldProps) => JSX.Element;
|
|
51
|
+
export declare const MultiSelectField: ({ options, value, onChange, name, emptyName, addButtonLabel, dialogTitle, emptyText, disabled, className, maxSelected, submitLabel, cancelLabel, requireSelection, addButtonProps, submitButtonProps, cancelButtonProps, ...pickerProps }: MultiSelectFieldProps) => JSX.Element;
|
|
39
52
|
|
|
40
|
-
export declare type MultiSelectFieldProps = Pick<MultiSelectPickerProps, 'options' | 'searchPlaceholder' | 'noResultsText' | 'listHeight' | 'maxSelected' | 'className'> & {
|
|
53
|
+
export declare type MultiSelectFieldProps = Pick<MultiSelectPickerProps, 'options' | 'searchPlaceholder' | 'noResultsText' | 'listHeight' | 'maxSelected' | 'className' | 'searchInputProps'> & {
|
|
41
54
|
value: SelectOption[];
|
|
42
55
|
onChange: (selected: SelectOption[]) => void;
|
|
43
56
|
/** 指定時、選択件数分の hidden input を描画する */
|
|
44
57
|
name?: string;
|
|
58
|
+
/**
|
|
59
|
+
* 選択が 0 件でもキーを送るための hidden の name。
|
|
60
|
+
* 既定は name の末尾の `[]` を除いたもの(PHP / Rails 等の配列記法に対応)。
|
|
61
|
+
* false を渡すと出力しない。空文字("")を渡した場合も false と同じく出力しない。
|
|
62
|
+
*/
|
|
63
|
+
emptyName?: string | false;
|
|
45
64
|
addButtonLabel?: string;
|
|
46
65
|
dialogTitle?: string;
|
|
47
66
|
emptyText?: string;
|
|
@@ -57,7 +76,7 @@ export declare type MultiSelectFieldProps = Pick<MultiSelectPickerProps, 'option
|
|
|
57
76
|
cancelButtonProps?: ButtonPassthroughProps;
|
|
58
77
|
};
|
|
59
78
|
|
|
60
|
-
export declare const MultiSelectPicker: ({ options, value, onChange, searchPlaceholder, noResultsText, listHeight, maxSelected, className, }: MultiSelectPickerProps) => JSX.Element;
|
|
79
|
+
export declare const MultiSelectPicker: ({ options, value, onChange, searchPlaceholder, noResultsText, listHeight, maxSelected, className, searchInputProps, }: MultiSelectPickerProps) => JSX.Element;
|
|
61
80
|
|
|
62
81
|
export declare type MultiSelectPickerProps = {
|
|
63
82
|
options: SelectOption[];
|
|
@@ -68,9 +87,11 @@ export declare type MultiSelectPickerProps = {
|
|
|
68
87
|
listHeight?: number | string;
|
|
69
88
|
maxSelected?: number;
|
|
70
89
|
className?: string;
|
|
90
|
+
/** 検索欄へ渡す任意のクラス・属性(baserCMS 等の input スタイル用) */
|
|
91
|
+
searchInputProps?: InputPassthroughProps;
|
|
71
92
|
};
|
|
72
93
|
|
|
73
|
-
export declare const SearchSelect: ({ options, value, onChange, name, placeholder, emptyLabel, clearable, disabled, noResultsText, searchPlaceholder, dropdownPlacement, className, }: SearchSelectProps) => JSX.Element;
|
|
94
|
+
export declare const SearchSelect: ({ options, value, onChange, name, placeholder, emptyLabel, clearable, disabled, noResultsText, searchPlaceholder, dropdownPlacement, className, triggerProps, searchInputProps, }: SearchSelectProps) => JSX.Element;
|
|
74
95
|
|
|
75
96
|
export declare type SearchSelectProps = {
|
|
76
97
|
options: SelectOption[];
|
|
@@ -88,6 +109,10 @@ export declare type SearchSelectProps = {
|
|
|
88
109
|
searchPlaceholder?: string;
|
|
89
110
|
dropdownPlacement?: 'auto' | 'top' | 'bottom';
|
|
90
111
|
className?: string;
|
|
112
|
+
/** トリガー(閉じた状態の表示部)へ渡す任意のクラス・属性(baserCMS 等の input スタイル用) */
|
|
113
|
+
triggerProps?: TriggerPassthroughProps;
|
|
114
|
+
/** 検索欄へ渡す任意のクラス・属性(baserCMS 等の input スタイル用) */
|
|
115
|
+
searchInputProps?: InputPassthroughProps;
|
|
91
116
|
};
|
|
92
117
|
|
|
93
118
|
/**
|
|
@@ -103,4 +128,18 @@ export declare type SelectOption = {
|
|
|
103
128
|
disabled?: boolean;
|
|
104
129
|
};
|
|
105
130
|
|
|
131
|
+
/**
|
|
132
|
+
* `SearchSelect` のトリガー(閉じた状態の div)へ、利用側から任意のクラス・
|
|
133
|
+
* data 属性等を渡すための型。
|
|
134
|
+
*
|
|
135
|
+
* `role` / `tabIndex` / `onClick` / `onKeyDown` / `aria-expanded` /
|
|
136
|
+
* `aria-haspopup` / `aria-disabled` / `aria-controls` / `aria-activedescendant`
|
|
137
|
+
* は部品側が制御する(開閉のハンドラ、ARIA 属性の同期)ため除外している。
|
|
138
|
+
* baserCMS 等、既存のデザインシステムの input スタイル(クラス+ data 属性)を
|
|
139
|
+
* 当てたい場合に使う。
|
|
140
|
+
*/
|
|
141
|
+
export declare type TriggerPassthroughProps = Omit<HTMLAttributes<HTMLDivElement>, 'role' | 'tabIndex' | 'onClick' | 'onKeyDown' | 'aria-expanded' | 'aria-haspopup' | 'aria-disabled' | 'aria-controls' | 'aria-activedescendant'> & {
|
|
142
|
+
[key: `data-${string}`]: string | number | boolean | undefined;
|
|
143
|
+
};
|
|
144
|
+
|
|
106
145
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,149 +1,153 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useMemo as
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
return
|
|
6
|
-
(i) => i.label.toLowerCase().includes(
|
|
1
|
+
import { jsxs as y, jsx as s } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo as q, useEffect as C, useState as D, useLayoutEffect as Q, useRef as L, useId as H } from "react";
|
|
3
|
+
const Y = (t, e) => {
|
|
4
|
+
const c = e.trim().toLowerCase();
|
|
5
|
+
return c ? t.filter(
|
|
6
|
+
(i) => i.label.toLowerCase().includes(c) || (i.sublabel ?? "").toLowerCase().includes(c)
|
|
7
7
|
) : t;
|
|
8
|
-
},
|
|
8
|
+
}, T = (t, e) => q(() => Y(t, e), [t, e]), z = (t, e, c) => {
|
|
9
9
|
C(() => {
|
|
10
10
|
if (!e) return;
|
|
11
11
|
const i = (n) => {
|
|
12
|
-
t.current && !t.current.contains(n.target) &&
|
|
12
|
+
t.current && !t.current.contains(n.target) && c();
|
|
13
13
|
};
|
|
14
14
|
return document.addEventListener("mousedown", i), () => document.removeEventListener("mousedown", i);
|
|
15
|
-
}, [t, e,
|
|
16
|
-
},
|
|
17
|
-
const [n,
|
|
18
|
-
return
|
|
15
|
+
}, [t, e, c]);
|
|
16
|
+
}, B = (t, e, c, i = 240) => {
|
|
17
|
+
const [n, o] = D(c === "top" ? "top" : "bottom");
|
|
18
|
+
return Q(() => {
|
|
19
19
|
if (!e) return;
|
|
20
|
-
if (
|
|
21
|
-
|
|
20
|
+
if (c !== "auto") {
|
|
21
|
+
o(c);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
}, [t, e,
|
|
29
|
-
},
|
|
24
|
+
const u = t.current;
|
|
25
|
+
if (!u) return;
|
|
26
|
+
const d = u.getBoundingClientRect(), h = window.innerHeight - d.bottom;
|
|
27
|
+
o(h < i && d.top > h ? "top" : "bottom");
|
|
28
|
+
}, [t, e, c, i]), n;
|
|
29
|
+
}, A = "", te = ({
|
|
30
30
|
options: t,
|
|
31
31
|
value: e,
|
|
32
|
-
onChange:
|
|
32
|
+
onChange: c,
|
|
33
33
|
name: i,
|
|
34
34
|
placeholder: n = "選択してください",
|
|
35
|
-
emptyLabel:
|
|
36
|
-
clearable:
|
|
37
|
-
disabled:
|
|
38
|
-
noResultsText:
|
|
39
|
-
searchPlaceholder:
|
|
40
|
-
dropdownPlacement:
|
|
41
|
-
className:
|
|
35
|
+
emptyLabel: o,
|
|
36
|
+
clearable: u = !0,
|
|
37
|
+
disabled: d = !1,
|
|
38
|
+
noResultsText: h = "一致する項目がありません",
|
|
39
|
+
searchPlaceholder: v = "検索...",
|
|
40
|
+
dropdownPlacement: f = "auto",
|
|
41
|
+
className: x = "",
|
|
42
|
+
triggerProps: w = { className: "bca-textbox__input" },
|
|
43
|
+
searchInputProps: g = { className: "bca-textbox__input" }
|
|
42
44
|
}) => {
|
|
43
|
-
const [
|
|
44
|
-
|
|
45
|
-
const
|
|
45
|
+
const [r, _] = D(!1), [l, p] = D(""), [b, N] = D(0), $ = L(null), R = L(null), S = L(null), M = H(), I = (a) => `${M}-option-${a || "__empty__"}`, O = T(t, l), E = o ? [{ id: A, label: o }, ...O] : O, m = t.find((a) => a.id === e) ?? null;
|
|
46
|
+
z($, r, () => _(!1));
|
|
47
|
+
const F = B($, r, f);
|
|
46
48
|
C(() => {
|
|
47
|
-
|
|
48
|
-
}, [
|
|
49
|
-
|
|
50
|
-
}, [
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
if (!
|
|
49
|
+
r || (p(""), N(0));
|
|
50
|
+
}, [r]), C(() => {
|
|
51
|
+
r && R.current?.focus();
|
|
52
|
+
}, [r]);
|
|
53
|
+
const K = (a) => {
|
|
54
|
+
c(a === A ? null : a), _(!1), S.current?.focus();
|
|
55
|
+
}, j = (a) => {
|
|
56
|
+
if (!d && !a.nativeEvent.isComposing && !(a.key !== "Escape" && a.target.closest(".bca-search-select__clear"))) {
|
|
55
57
|
if (a.key === "Escape") {
|
|
56
|
-
|
|
58
|
+
_(!1), S.current?.focus();
|
|
57
59
|
return;
|
|
58
60
|
}
|
|
59
|
-
if (!
|
|
60
|
-
(a.key === "ArrowDown" || a.key === "Enter") && (a.preventDefault(),
|
|
61
|
+
if (!r) {
|
|
62
|
+
(a.key === "ArrowDown" || a.key === "Enter") && (a.preventDefault(), _(!0));
|
|
61
63
|
return;
|
|
62
64
|
}
|
|
63
65
|
if (a.key === "ArrowDown")
|
|
64
|
-
a.preventDefault(),
|
|
66
|
+
a.preventDefault(), N((k) => Math.min(k + 1, E.length - 1));
|
|
65
67
|
else if (a.key === "ArrowUp")
|
|
66
|
-
a.preventDefault(),
|
|
68
|
+
a.preventDefault(), N((k) => Math.max(k - 1, 0));
|
|
67
69
|
else if (a.key === "Enter") {
|
|
68
70
|
a.preventDefault();
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
+
const k = E[b];
|
|
72
|
+
k && !k.disabled && K(k.id);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
};
|
|
74
|
-
return /* @__PURE__ */
|
|
75
|
-
i && /* @__PURE__ */
|
|
76
|
-
/* @__PURE__ */
|
|
76
|
+
return /* @__PURE__ */ y("div", { ref: $, className: `bca-search-select ${x}`.trim(), onKeyDown: j, children: [
|
|
77
|
+
i && /* @__PURE__ */ s("input", { type: "hidden", name: i, value: e ?? "" }),
|
|
78
|
+
/* @__PURE__ */ y(
|
|
77
79
|
"div",
|
|
78
80
|
{
|
|
81
|
+
...w,
|
|
79
82
|
ref: S,
|
|
80
83
|
role: "combobox",
|
|
81
|
-
"aria-expanded":
|
|
84
|
+
"aria-expanded": r,
|
|
82
85
|
"aria-haspopup": "listbox",
|
|
83
|
-
"aria-disabled":
|
|
84
|
-
"aria-controls":
|
|
85
|
-
"aria-activedescendant":
|
|
86
|
-
tabIndex:
|
|
87
|
-
className:
|
|
88
|
-
"data-disabled":
|
|
89
|
-
onClick: () => !
|
|
86
|
+
"aria-disabled": d,
|
|
87
|
+
"aria-controls": r ? M : void 0,
|
|
88
|
+
"aria-activedescendant": r && E[b] ? I(E[b].id) : void 0,
|
|
89
|
+
tabIndex: d ? -1 : 0,
|
|
90
|
+
className: `bca-search-select__trigger ${w?.className ?? ""}`.trim(),
|
|
91
|
+
"data-disabled": d || void 0,
|
|
92
|
+
onClick: () => !d && _(!r),
|
|
90
93
|
children: [
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
-
!
|
|
94
|
+
/* @__PURE__ */ s("span", { className: m ? "bca-search-select__value" : "bca-search-select__placeholder", children: m ? m.label : n }),
|
|
95
|
+
!d && u && e ? /* @__PURE__ */ s(
|
|
93
96
|
"button",
|
|
94
97
|
{
|
|
95
98
|
type: "button",
|
|
96
99
|
className: "bca-search-select__clear",
|
|
97
100
|
"aria-label": "選択を解除",
|
|
98
101
|
onClick: (a) => {
|
|
99
|
-
a.stopPropagation(),
|
|
102
|
+
a.stopPropagation(), c(null);
|
|
100
103
|
},
|
|
101
104
|
children: "×"
|
|
102
105
|
}
|
|
103
|
-
) : /* @__PURE__ */
|
|
106
|
+
) : /* @__PURE__ */ s("span", { className: "bca-search-select__arrow", "aria-hidden": "true", children: "▼" })
|
|
104
107
|
]
|
|
105
108
|
}
|
|
106
109
|
),
|
|
107
|
-
|
|
108
|
-
/* @__PURE__ */
|
|
110
|
+
r && /* @__PURE__ */ y("div", { className: "bca-search-select__dropdown", "data-placement": F, children: [
|
|
111
|
+
/* @__PURE__ */ s("div", { className: "bca-search-select__search", children: /* @__PURE__ */ s(
|
|
109
112
|
"input",
|
|
110
113
|
{
|
|
111
|
-
|
|
114
|
+
...g,
|
|
115
|
+
ref: R,
|
|
112
116
|
type: "text",
|
|
113
|
-
className:
|
|
114
|
-
placeholder:
|
|
115
|
-
value:
|
|
117
|
+
className: `bca-search-select__search-input ${g?.className ?? ""}`.trim(),
|
|
118
|
+
placeholder: v,
|
|
119
|
+
value: l,
|
|
116
120
|
onChange: (a) => {
|
|
117
|
-
|
|
121
|
+
p(a.target.value), N(0);
|
|
118
122
|
},
|
|
119
123
|
onClick: (a) => a.stopPropagation()
|
|
120
124
|
}
|
|
121
125
|
) }),
|
|
122
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ s("ul", { role: "listbox", id: M, className: "bca-search-select__list", children: E.length > 0 ? E.map((a, k) => /* @__PURE__ */ y(
|
|
123
127
|
"li",
|
|
124
128
|
{
|
|
125
129
|
id: I(a.id),
|
|
126
130
|
role: "option",
|
|
127
|
-
"aria-selected": a.id === (e ??
|
|
131
|
+
"aria-selected": a.id === (e ?? A),
|
|
128
132
|
"aria-disabled": a.disabled || void 0,
|
|
129
133
|
className: "bca-search-select__option",
|
|
130
|
-
"data-active":
|
|
131
|
-
"data-selected": a.id === (e ??
|
|
134
|
+
"data-active": k === b || void 0,
|
|
135
|
+
"data-selected": a.id === (e ?? A) || void 0,
|
|
132
136
|
"data-disabled": a.disabled || void 0,
|
|
133
|
-
onMouseEnter: () => !a.disabled &&
|
|
134
|
-
onClick: () => !a.disabled &&
|
|
137
|
+
onMouseEnter: () => !a.disabled && N(k),
|
|
138
|
+
onClick: () => !a.disabled && K(a.id),
|
|
135
139
|
children: [
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
-
a.sublabel && /* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ s("span", { className: "bca-search-select__label", children: a.label }),
|
|
141
|
+
a.sublabel && /* @__PURE__ */ s("span", { className: "bca-search-select__sublabel", children: `(${a.sublabel})` })
|
|
138
142
|
]
|
|
139
143
|
},
|
|
140
144
|
a.id || "__empty__"
|
|
141
|
-
)) : /* @__PURE__ */
|
|
145
|
+
)) : /* @__PURE__ */ s("li", { className: "bca-search-select__no-results", children: h }) })
|
|
142
146
|
] })
|
|
143
147
|
] });
|
|
144
|
-
},
|
|
145
|
-
/* @__PURE__ */
|
|
146
|
-
/* @__PURE__ */
|
|
148
|
+
}, U = ({ items: t, onRemove: e, emptyText: c = "選択されていません", disabled: i = !1 }) => t.length === 0 ? /* @__PURE__ */ s("span", { className: "bca-selected-tags__empty", children: c }) : /* @__PURE__ */ s("ul", { className: "bca-selected-tags", children: t.map((n) => /* @__PURE__ */ y("li", { className: "bca-selected-tags__item", children: [
|
|
149
|
+
/* @__PURE__ */ s("span", { className: "bca-selected-tags__label", children: n.label }),
|
|
150
|
+
/* @__PURE__ */ s(
|
|
147
151
|
"button",
|
|
148
152
|
{
|
|
149
153
|
type: "button",
|
|
@@ -154,212 +158,216 @@ const q = (t, e) => {
|
|
|
154
158
|
children: "×"
|
|
155
159
|
}
|
|
156
160
|
)
|
|
157
|
-
] }, n.id)) }),
|
|
161
|
+
] }, n.id)) }), G = ({
|
|
158
162
|
options: t,
|
|
159
163
|
value: e,
|
|
160
|
-
onChange:
|
|
164
|
+
onChange: c,
|
|
161
165
|
searchPlaceholder: i = "検索...",
|
|
162
166
|
noResultsText: n = "選択可能な項目はありません",
|
|
163
|
-
listHeight:
|
|
164
|
-
maxSelected:
|
|
165
|
-
className:
|
|
167
|
+
listHeight: o = 350,
|
|
168
|
+
maxSelected: u,
|
|
169
|
+
className: d = "",
|
|
170
|
+
searchInputProps: h = { className: "bca-textbox__input" }
|
|
166
171
|
}) => {
|
|
167
|
-
const [
|
|
168
|
-
|
|
169
|
-
},
|
|
170
|
-
|
|
172
|
+
const [v, f] = D(""), x = new Set(e.map((l) => l.id)), w = T(t, v).filter((l) => !x.has(l.id)), g = u !== void 0 && e.length >= u, r = (l) => {
|
|
173
|
+
g || l.disabled || c([...e, l]);
|
|
174
|
+
}, _ = (l) => {
|
|
175
|
+
c(e.filter((p) => p.id !== l));
|
|
171
176
|
};
|
|
172
|
-
return /* @__PURE__ */
|
|
173
|
-
/* @__PURE__ */
|
|
174
|
-
/* @__PURE__ */
|
|
177
|
+
return /* @__PURE__ */ y("div", { className: `bca-multi-select-picker ${d}`.trim(), children: [
|
|
178
|
+
/* @__PURE__ */ y("div", { className: "bca-multi-select-picker__search", children: [
|
|
179
|
+
/* @__PURE__ */ s(
|
|
175
180
|
"input",
|
|
176
181
|
{
|
|
182
|
+
...h,
|
|
177
183
|
type: "text",
|
|
178
|
-
className:
|
|
184
|
+
className: `bca-multi-select-picker__search-input ${h?.className ?? ""}`.trim(),
|
|
179
185
|
placeholder: i,
|
|
180
|
-
value:
|
|
181
|
-
onChange: (l) =>
|
|
186
|
+
value: v,
|
|
187
|
+
onChange: (l) => f(l.target.value)
|
|
182
188
|
}
|
|
183
189
|
),
|
|
184
|
-
|
|
190
|
+
v && /* @__PURE__ */ s(
|
|
185
191
|
"button",
|
|
186
192
|
{
|
|
187
193
|
type: "button",
|
|
188
194
|
className: "bca-multi-select-picker__search-clear",
|
|
189
195
|
"aria-label": "検索語をクリア",
|
|
190
|
-
onClick: () =>
|
|
196
|
+
onClick: () => f(""),
|
|
191
197
|
children: "×"
|
|
192
198
|
}
|
|
193
199
|
)
|
|
194
200
|
] }),
|
|
195
|
-
/* @__PURE__ */
|
|
196
|
-
const
|
|
197
|
-
return /* @__PURE__ */
|
|
201
|
+
/* @__PURE__ */ s("div", { className: "bca-multi-select-picker__list-wrapper", style: { maxHeight: o }, children: /* @__PURE__ */ s("ul", { className: "bca-multi-select-picker__list", role: "listbox", "aria-multiselectable": "true", "aria-label": "選択可能な項目", children: w.length > 0 ? w.map((l) => {
|
|
202
|
+
const p = l.disabled || g;
|
|
203
|
+
return /* @__PURE__ */ y(
|
|
198
204
|
"li",
|
|
199
205
|
{
|
|
200
206
|
className: "bca-multi-select-picker__option",
|
|
201
207
|
role: "option",
|
|
202
208
|
"aria-selected": !1,
|
|
203
|
-
"aria-disabled":
|
|
204
|
-
"data-disabled":
|
|
205
|
-
tabIndex:
|
|
206
|
-
onClick: () =>
|
|
207
|
-
onKeyDown: (
|
|
208
|
-
|
|
209
|
+
"aria-disabled": p || void 0,
|
|
210
|
+
"data-disabled": p || void 0,
|
|
211
|
+
tabIndex: p ? -1 : 0,
|
|
212
|
+
onClick: () => r(l),
|
|
213
|
+
onKeyDown: (b) => {
|
|
214
|
+
b.nativeEvent.isComposing || (b.key === "Enter" || b.key === " ") && (b.preventDefault(), r(l));
|
|
209
215
|
},
|
|
210
216
|
children: [
|
|
211
|
-
/* @__PURE__ */
|
|
212
|
-
l.sublabel && /* @__PURE__ */
|
|
217
|
+
/* @__PURE__ */ s("span", { className: "bca-multi-select-picker__label", children: l.label }),
|
|
218
|
+
l.sublabel && /* @__PURE__ */ s("span", { className: "bca-multi-select-picker__sublabel", children: `(${l.sublabel})` })
|
|
213
219
|
]
|
|
214
220
|
},
|
|
215
221
|
l.id
|
|
216
222
|
);
|
|
217
|
-
}) : /* @__PURE__ */
|
|
218
|
-
/* @__PURE__ */
|
|
223
|
+
}) : /* @__PURE__ */ s("li", { className: "bca-multi-select-picker__no-results", children: n }) }) }),
|
|
224
|
+
/* @__PURE__ */ s("div", { className: "bca-multi-select-picker__selected", children: /* @__PURE__ */ s(U, { items: e, onRemove: _ }) })
|
|
219
225
|
] });
|
|
220
|
-
},
|
|
226
|
+
}, J = 'a[href], button, input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])', P = (t) => "disabled" in t && t.disabled, W = (t) => Array.from(t.querySelectorAll(J)).filter((e) => !P(e)), X = (t, e) => {
|
|
221
227
|
C(() => {
|
|
222
228
|
if (!e) return;
|
|
223
|
-
const
|
|
224
|
-
if (!
|
|
229
|
+
const c = t.current;
|
|
230
|
+
if (!c) return;
|
|
225
231
|
const i = (n) => {
|
|
226
232
|
if (n.key !== "Tab") return;
|
|
227
|
-
const
|
|
228
|
-
if (
|
|
229
|
-
const
|
|
230
|
-
n.shiftKey ? (document.activeElement ===
|
|
233
|
+
const o = W(c);
|
|
234
|
+
if (o.length === 0) return;
|
|
235
|
+
const u = o[0], d = o[o.length - 1];
|
|
236
|
+
n.shiftKey ? (document.activeElement === u || !c.contains(document.activeElement)) && (n.preventDefault(), d.focus()) : (document.activeElement === d || !c.contains(document.activeElement)) && (n.preventDefault(), u.focus());
|
|
231
237
|
};
|
|
232
|
-
return
|
|
238
|
+
return c.addEventListener("keydown", i), () => c.removeEventListener("keydown", i);
|
|
233
239
|
}, [t, e]);
|
|
234
|
-
},
|
|
240
|
+
}, Z = ({
|
|
235
241
|
open: t,
|
|
236
242
|
title: e = "選択",
|
|
237
|
-
options:
|
|
243
|
+
options: c,
|
|
238
244
|
initialValue: i,
|
|
239
245
|
onSubmit: n,
|
|
240
|
-
onCancel:
|
|
241
|
-
submitLabel:
|
|
242
|
-
cancelLabel:
|
|
243
|
-
requireSelection:
|
|
244
|
-
submitButtonProps:
|
|
245
|
-
cancelButtonProps:
|
|
246
|
-
className:
|
|
247
|
-
...
|
|
246
|
+
onCancel: o,
|
|
247
|
+
submitLabel: u = "決定",
|
|
248
|
+
cancelLabel: d = "キャンセル",
|
|
249
|
+
requireSelection: h = !0,
|
|
250
|
+
submitButtonProps: v = { className: "bca-btn", "data-bca-btn-type": "save" },
|
|
251
|
+
cancelButtonProps: f = { className: "bca-btn" },
|
|
252
|
+
className: x = "",
|
|
253
|
+
...w
|
|
248
254
|
}) => {
|
|
249
|
-
const [
|
|
255
|
+
const [g, r] = D(i ?? []), _ = L(null), l = L(null);
|
|
250
256
|
if (C(() => {
|
|
251
|
-
t &&
|
|
257
|
+
t && r(i ?? []);
|
|
252
258
|
}, [t]), C(() => {
|
|
253
259
|
if (!t) return;
|
|
254
|
-
const
|
|
255
|
-
|
|
260
|
+
const b = (N) => {
|
|
261
|
+
N.isComposing || N.key === "Escape" && o();
|
|
256
262
|
};
|
|
257
|
-
return document.addEventListener("keydown",
|
|
258
|
-
}, [t,
|
|
259
|
-
t ? (
|
|
260
|
-
}, [t]),
|
|
261
|
-
const
|
|
262
|
-
return /* @__PURE__ */
|
|
263
|
+
return document.addEventListener("keydown", b), () => document.removeEventListener("keydown", b);
|
|
264
|
+
}, [t, o]), C(() => {
|
|
265
|
+
t ? (l.current = document.activeElement, _.current?.focus()) : (l.current?.focus(), l.current = null);
|
|
266
|
+
}, [t]), X(_, t), !t) return null;
|
|
267
|
+
const p = !h || g.length > 0;
|
|
268
|
+
return /* @__PURE__ */ s("div", { className: "bca-multi-select-dialog__overlay", children: /* @__PURE__ */ y(
|
|
263
269
|
"div",
|
|
264
270
|
{
|
|
265
|
-
ref:
|
|
271
|
+
ref: _,
|
|
266
272
|
role: "dialog",
|
|
267
273
|
"aria-modal": "true",
|
|
268
274
|
"aria-label": e,
|
|
269
275
|
tabIndex: -1,
|
|
270
|
-
className: `bca-multi-select-dialog ${
|
|
276
|
+
className: `bca-multi-select-dialog ${x}`.trim(),
|
|
271
277
|
children: [
|
|
272
|
-
/* @__PURE__ */
|
|
273
|
-
/* @__PURE__ */
|
|
274
|
-
/* @__PURE__ */
|
|
278
|
+
/* @__PURE__ */ y("div", { className: "bca-multi-select-dialog__header", children: [
|
|
279
|
+
/* @__PURE__ */ s("span", { className: "bca-multi-select-dialog__title", children: e }),
|
|
280
|
+
/* @__PURE__ */ s("button", { type: "button", className: "bca-multi-select-dialog__close", "aria-label": "閉じる", onClick: o, children: "×" })
|
|
275
281
|
] }),
|
|
276
|
-
/* @__PURE__ */
|
|
277
|
-
/* @__PURE__ */
|
|
278
|
-
/* @__PURE__ */
|
|
282
|
+
/* @__PURE__ */ s("div", { className: "bca-multi-select-dialog__body", children: /* @__PURE__ */ s(G, { options: c, value: g, onChange: r, ...w }) }),
|
|
283
|
+
/* @__PURE__ */ y("div", { className: "bca-multi-select-dialog__footer", children: [
|
|
284
|
+
/* @__PURE__ */ s(
|
|
279
285
|
"button",
|
|
280
286
|
{
|
|
281
|
-
...
|
|
287
|
+
...f,
|
|
282
288
|
type: "button",
|
|
283
|
-
className: `bca-multi-select-dialog__button ${
|
|
284
|
-
onClick:
|
|
285
|
-
children:
|
|
289
|
+
className: `bca-multi-select-dialog__button ${f?.className ?? ""}`.trim(),
|
|
290
|
+
onClick: o,
|
|
291
|
+
children: d
|
|
286
292
|
}
|
|
287
293
|
),
|
|
288
|
-
/* @__PURE__ */
|
|
294
|
+
/* @__PURE__ */ s(
|
|
289
295
|
"button",
|
|
290
296
|
{
|
|
291
|
-
...
|
|
297
|
+
...v,
|
|
292
298
|
type: "button",
|
|
293
|
-
className: `bca-multi-select-dialog__button bca-multi-select-dialog__button--primary ${
|
|
294
|
-
disabled: !
|
|
295
|
-
onClick: () => n(
|
|
296
|
-
children:
|
|
299
|
+
className: `bca-multi-select-dialog__button bca-multi-select-dialog__button--primary ${v?.className ?? ""}`.trim(),
|
|
300
|
+
disabled: !p,
|
|
301
|
+
onClick: () => n(g),
|
|
302
|
+
children: u
|
|
297
303
|
}
|
|
298
304
|
)
|
|
299
305
|
] })
|
|
300
306
|
]
|
|
301
307
|
}
|
|
302
308
|
) });
|
|
303
|
-
},
|
|
309
|
+
}, ae = ({
|
|
304
310
|
options: t,
|
|
305
311
|
value: e,
|
|
306
|
-
onChange:
|
|
312
|
+
onChange: c,
|
|
307
313
|
name: i,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
emptyName: n,
|
|
315
|
+
addButtonLabel: o = "追加",
|
|
316
|
+
dialogTitle: u = "選択",
|
|
317
|
+
emptyText: d = "選択されていません",
|
|
318
|
+
disabled: h = !1,
|
|
319
|
+
className: v = "",
|
|
320
|
+
maxSelected: f,
|
|
321
|
+
submitLabel: x,
|
|
315
322
|
cancelLabel: w,
|
|
316
|
-
requireSelection:
|
|
317
|
-
addButtonProps:
|
|
318
|
-
submitButtonProps:
|
|
323
|
+
requireSelection: g,
|
|
324
|
+
addButtonProps: r = { className: "bca-btn", "data-bca-btn-type": "add" },
|
|
325
|
+
submitButtonProps: _,
|
|
319
326
|
cancelButtonProps: l,
|
|
320
|
-
...
|
|
327
|
+
...p
|
|
321
328
|
}) => {
|
|
322
|
-
const [
|
|
323
|
-
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
};
|
|
327
|
-
return /* @__PURE__ */
|
|
328
|
-
i &&
|
|
329
|
-
|
|
330
|
-
/* @__PURE__ */
|
|
329
|
+
const [b, N] = D(!1), $ = new Set(e.map((m) => m.id)), R = t.filter((m) => !$.has(m.id)), S = f !== void 0 ? Math.max(0, f - e.length) : void 0, M = f !== void 0 && e.length >= f, I = (m) => {
|
|
330
|
+
c(e.filter((F) => F.id !== m));
|
|
331
|
+
}, O = (m) => {
|
|
332
|
+
c([...e, ...m]), N(!1);
|
|
333
|
+
}, E = n === !1 ? void 0 : n ?? i?.replace(/\[\]$/, "");
|
|
334
|
+
return /* @__PURE__ */ y("div", { className: `bca-multi-select-field ${v}`.trim(), children: [
|
|
335
|
+
i && E && /* @__PURE__ */ s("input", { type: "hidden", name: E, value: "" }),
|
|
336
|
+
i && e.map((m) => /* @__PURE__ */ s("input", { type: "hidden", name: i, value: m.id }, m.id)),
|
|
337
|
+
/* @__PURE__ */ s("div", { className: "bca-multi-select-field__tags", children: /* @__PURE__ */ s(U, { items: e, onRemove: I, emptyText: d, disabled: h }) }),
|
|
338
|
+
/* @__PURE__ */ s("div", { className: "bca-multi-select-field__actions", children: /* @__PURE__ */ s(
|
|
331
339
|
"button",
|
|
332
340
|
{
|
|
333
|
-
...
|
|
341
|
+
...r,
|
|
334
342
|
type: "button",
|
|
335
|
-
className: `bca-multi-select-field__add ${
|
|
336
|
-
disabled:
|
|
337
|
-
onClick: () =>
|
|
338
|
-
children:
|
|
343
|
+
className: `bca-multi-select-field__add ${r?.className ?? ""}`.trim(),
|
|
344
|
+
disabled: h || M,
|
|
345
|
+
onClick: () => N(!0),
|
|
346
|
+
children: o
|
|
339
347
|
}
|
|
340
348
|
) }),
|
|
341
|
-
/* @__PURE__ */
|
|
342
|
-
|
|
349
|
+
/* @__PURE__ */ s(
|
|
350
|
+
Z,
|
|
343
351
|
{
|
|
344
|
-
open:
|
|
345
|
-
title:
|
|
346
|
-
options:
|
|
347
|
-
onSubmit:
|
|
348
|
-
onCancel: () =>
|
|
349
|
-
maxSelected:
|
|
350
|
-
submitLabel:
|
|
352
|
+
open: b,
|
|
353
|
+
title: u,
|
|
354
|
+
options: R,
|
|
355
|
+
onSubmit: O,
|
|
356
|
+
onCancel: () => N(!1),
|
|
357
|
+
maxSelected: S,
|
|
358
|
+
submitLabel: x,
|
|
351
359
|
cancelLabel: w,
|
|
352
|
-
requireSelection:
|
|
353
|
-
submitButtonProps:
|
|
360
|
+
requireSelection: g,
|
|
361
|
+
submitButtonProps: _,
|
|
354
362
|
cancelButtonProps: l,
|
|
355
|
-
...
|
|
363
|
+
...p
|
|
356
364
|
}
|
|
357
365
|
)
|
|
358
366
|
] });
|
|
359
367
|
};
|
|
360
368
|
export {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
369
|
+
Z as MultiSelectDialog,
|
|
370
|
+
ae as MultiSelectField,
|
|
371
|
+
G as MultiSelectPicker,
|
|
372
|
+
te as SearchSelect
|
|
365
373
|
};
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--bca-accent: #D4EDC9;--bca-accent-hover: #E9F7E3;--bca-border: #ccc;--bca-radius: 4px;--bca-font-size: 14px;--bca-z-index: 9999}.bca-selected-tags{display:flex;flex-wrap:wrap;gap:5px;list-style:none;margin:0;padding:0}.bca-selected-tags__empty{color:#999;font-size:var(--bca-font-size)}.bca-selected-tags__item{display:flex;align-items:center;padding:5px 10px;background-color:var(--bca-accent);border-radius:15px;font-size:var(--bca-font-size)}.bca-selected-tags__remove{margin-left:5px}:where(.bca-selected-tags__remove){font:inherit;border:none;background:transparent;font-weight:700;cursor:pointer}.bca-selected-tags__remove:disabled{cursor:not-allowed;opacity:.5}.bca-search-select{position:relative;width:100%;font-size:var(--bca-font-size)}.bca-search-select__trigger{position:relative;
|
|
1
|
+
:root{--bca-accent: #D4EDC9;--bca-accent-hover: #E9F7E3;--bca-border: #ccc;--bca-radius: 4px;--bca-font-size: 14px;--bca-z-index: 9999}.bca-selected-tags{display:flex;flex-wrap:wrap;gap:5px;list-style:none;margin:0;padding:0}.bca-selected-tags__empty{color:#999;font-size:var(--bca-font-size)}.bca-selected-tags__item{display:flex;align-items:center;padding:5px 10px;background-color:var(--bca-accent);border-radius:15px;font-size:var(--bca-font-size)}.bca-selected-tags__remove{margin-left:5px}:where(.bca-selected-tags__remove){font:inherit;border:none;background:transparent;font-weight:700;cursor:pointer}.bca-selected-tags__remove:disabled{cursor:not-allowed;opacity:.5}.bca-search-select{position:relative;width:100%;font-size:var(--bca-font-size)}.bca-search-select__trigger{position:relative;align-items:center;box-sizing:border-box;width:100%;cursor:pointer}.bca-search-select__trigger.bca-search-select__trigger{display:flex;padding-right:32px}:where(.bca-search-select__trigger){padding-top:4px;padding-left:8px;padding-bottom:4px;border:1px solid var(--bca-border);border-radius:var(--bca-radius);background-color:#fff}.bca-search-select__trigger[data-disabled]{background-color:#f4f4f4;color:#999;cursor:not-allowed}.bca-search-select__value,.bca-search-select__placeholder{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.bca-search-select__placeholder{color:#999}.bca-search-select__clear,.bca-search-select__arrow{position:absolute;right:8px;top:50%;transform:translateY(-50%);color:#999}.bca-search-select__clear{border:none;background:transparent;font-weight:700;cursor:pointer;padding:0 4px}.bca-search-select__arrow{font-size:12px;pointer-events:none}.bca-search-select__dropdown{position:absolute;left:0;width:100%;box-sizing:border-box;z-index:var(--bca-z-index);background-color:#fff;border:1px solid var(--bca-border);border-radius:var(--bca-radius);box-shadow:0 2px 6px #00000026;max-height:240px;display:flex;flex-direction:column}.bca-search-select__dropdown[data-placement=bottom]{top:100%}.bca-search-select__dropdown[data-placement=top]{bottom:100%}.bca-search-select__search{padding:8px;border-bottom:1px solid #eee}.bca-search-select__search-input{box-sizing:border-box;width:100%}.bca-search-select__search-input.bca-search-select__search-input{font:inherit;margin:0}:where(.bca-search-select__search-input){padding:4px 8px;border:1px solid var(--bca-border);border-radius:var(--bca-radius)}.bca-search-select__list{list-style:none;margin:0;padding:0;overflow-y:auto;flex:1}.bca-search-select__option{padding:6px 12px;cursor:pointer;border-bottom:1px solid #f9f9f9}.bca-search-select__option[data-active]{background-color:var(--bca-accent-hover)}.bca-search-select__option[data-selected]{background-color:var(--bca-accent);font-weight:700}.bca-search-select__option[data-disabled]{color:#999;cursor:not-allowed;background-color:transparent}.bca-search-select__sublabel{margin-left:4px;font-size:.8em;color:#666}.bca-search-select__no-results{padding:8px 12px;color:#999;text-align:center}.bca-multi-select-picker{font-size:var(--bca-font-size)}.bca-multi-select-picker__search{position:relative;margin-bottom:10px}.bca-multi-select-picker__search-input{box-sizing:border-box;width:100%}.bca-multi-select-picker__search-input.bca-multi-select-picker__search-input{font:inherit;margin:0}:where(.bca-multi-select-picker__search-input){padding:4px 30px 4px 8px;border:1px solid var(--bca-border);border-radius:var(--bca-radius)}.bca-multi-select-picker__search-clear{position:absolute;right:8px;top:50%;transform:translateY(-50%);border:none;background:transparent;font-size:16px;color:#999;cursor:pointer}.bca-multi-select-picker__list-wrapper{overflow-y:auto;border:1px solid var(--bca-border);border-radius:var(--bca-radius)}.bca-multi-select-picker__list{list-style:none;margin:0;padding:0}.bca-multi-select-picker__option{padding:8px 10px;border-bottom:1px solid #eee;cursor:pointer}.bca-multi-select-picker__option:hover{background-color:var(--bca-accent-hover)}.bca-multi-select-picker__option:focus-visible{outline:2px solid var(--bca-accent-hover);outline-offset:-2px;background-color:var(--bca-accent-hover)}.bca-multi-select-picker__option[data-disabled]{color:#999;cursor:not-allowed}.bca-multi-select-picker__option[data-disabled]:hover{background-color:transparent}.bca-multi-select-picker__sublabel{margin-left:4px;font-size:.8em;color:#666}.bca-multi-select-picker__no-results{padding:10px;color:#999;text-align:center}.bca-multi-select-picker__selected{margin-top:10px;padding:10px;border:1px solid var(--bca-border);border-radius:var(--bca-radius);min-height:40px;max-height:120px;overflow-y:auto}.bca-multi-select-dialog__overlay{position:fixed;inset:0;display:flex;justify-content:center;align-items:center;background-color:#0000004d;z-index:var(--bca-z-index)}.bca-multi-select-dialog{width:500px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);display:flex;flex-direction:column;background-color:#fff;border:1px solid var(--bca-border);border-radius:var(--bca-radius);box-shadow:0 4px 16px #0003;font-size:var(--bca-font-size)}.bca-multi-select-dialog:focus{outline:none}.bca-multi-select-dialog__header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid var(--bca-border);background-color:#f5f5f5}.bca-multi-select-dialog__title{font-weight:700}:where(.bca-multi-select-dialog__close){font:inherit;border:none;background:transparent;font-size:18px;cursor:pointer;color:#666}.bca-multi-select-dialog__body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:1em}.bca-multi-select-dialog__footer{display:flex;justify-content:flex-end;gap:8px;padding:8px 12px;border-top:1px solid var(--bca-border)}:where(.bca-multi-select-dialog__button){font:inherit;padding:4px 16px;border:1px solid var(--bca-border);border-radius:var(--bca-radius);background-color:#fff;cursor:pointer}:where(.bca-multi-select-dialog__button--primary){background-color:var(--bca-accent)}.bca-multi-select-dialog__button:disabled{opacity:.5;cursor:not-allowed}.bca-multi-select-field{font-size:var(--bca-font-size)}.bca-multi-select-field__tags{padding:10px;border:1px solid var(--bca-border);border-radius:var(--bca-radius);min-height:40px;box-sizing:border-box}.bca-multi-select-field__actions{margin-top:8px}:where(.bca-multi-select-field__add){font:inherit;padding:4px 16px;border:1px solid var(--bca-border);border-radius:var(--bca-radius);background-color:#fff;cursor:pointer}.bca-multi-select-field__add:disabled{opacity:.5;cursor:not-allowed}
|