@ecatchup/basercms-ui 0.1.1 → 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 +37 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +155 -153
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,6 +132,43 @@ type SelectOption = {
|
|
|
132
132
|
<MultiSelectField name="data[User][group_ids][]" ... /> // 選択件数分の hidden
|
|
133
133
|
```
|
|
134
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
|
+
|
|
135
172
|
## テーマ
|
|
136
173
|
|
|
137
174
|
CSS 変数を上書きしてください。
|
package/dist/index.d.ts
CHANGED
|
@@ -48,13 +48,19 @@ export declare type MultiSelectDialogProps = Pick<MultiSelectPickerProps, 'optio
|
|
|
48
48
|
* 「追加ボタン → モーダルで選択 → 決定 → タグとして並ぶ → × で削除」
|
|
49
49
|
* をひとまとめにしたフォーム部品
|
|
50
50
|
*/
|
|
51
|
-
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;
|
|
52
52
|
|
|
53
53
|
export declare type MultiSelectFieldProps = Pick<MultiSelectPickerProps, 'options' | 'searchPlaceholder' | 'noResultsText' | 'listHeight' | 'maxSelected' | 'className' | 'searchInputProps'> & {
|
|
54
54
|
value: SelectOption[];
|
|
55
55
|
onChange: (selected: SelectOption[]) => void;
|
|
56
56
|
/** 指定時、選択件数分の hidden input を描画する */
|
|
57
57
|
name?: string;
|
|
58
|
+
/**
|
|
59
|
+
* 選択が 0 件でもキーを送るための hidden の name。
|
|
60
|
+
* 既定は name の末尾の `[]` を除いたもの(PHP / Rails 等の配列記法に対応)。
|
|
61
|
+
* false を渡すと出力しない。空文字("")を渡した場合も false と同じく出力しない。
|
|
62
|
+
*/
|
|
63
|
+
emptyName?: string | false;
|
|
58
64
|
addButtonLabel?: string;
|
|
59
65
|
dialogTitle?: string;
|
|
60
66
|
emptyText?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import { jsxs as
|
|
1
|
+
import { jsxs as y, jsx as s } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo as q, useEffect as C, useState as D, useLayoutEffect as Q, useRef as L, useId as H } from "react";
|
|
3
3
|
const Y = (t, e) => {
|
|
4
|
-
const
|
|
5
|
-
return
|
|
6
|
-
(i) => i.label.toLowerCase().includes(
|
|
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
|
-
}, B = (t, e,
|
|
17
|
-
const [n, o] = D(
|
|
15
|
+
}, [t, e, c]);
|
|
16
|
+
}, B = (t, e, c, i = 240) => {
|
|
17
|
+
const [n, o] = D(c === "top" ? "top" : "bottom");
|
|
18
18
|
return Q(() => {
|
|
19
19
|
if (!e) return;
|
|
20
|
-
if (
|
|
21
|
-
o(
|
|
20
|
+
if (c !== "auto") {
|
|
21
|
+
o(c);
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
26
|
-
const d =
|
|
27
|
-
o(
|
|
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
35
|
emptyLabel: o,
|
|
36
|
-
clearable:
|
|
36
|
+
clearable: u = !0,
|
|
37
37
|
disabled: d = !1,
|
|
38
|
-
noResultsText:
|
|
39
|
-
searchPlaceholder:
|
|
40
|
-
dropdownPlacement:
|
|
38
|
+
noResultsText: h = "一致する項目がありません",
|
|
39
|
+
searchPlaceholder: v = "検索...",
|
|
40
|
+
dropdownPlacement: f = "auto",
|
|
41
41
|
className: x = "",
|
|
42
42
|
triggerProps: w = { className: "bca-textbox__input" },
|
|
43
|
-
searchInputProps:
|
|
43
|
+
searchInputProps: g = { className: "bca-textbox__input" }
|
|
44
44
|
}) => {
|
|
45
|
-
const [r, _] = D(!1), [l, p] = D(""), [
|
|
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
46
|
z($, r, () => _(!1));
|
|
47
|
-
const
|
|
47
|
+
const F = B($, r, f);
|
|
48
48
|
C(() => {
|
|
49
|
-
r || (p(""),
|
|
49
|
+
r || (p(""), N(0));
|
|
50
50
|
}, [r]), C(() => {
|
|
51
51
|
r && R.current?.focus();
|
|
52
52
|
}, [r]);
|
|
53
|
-
const
|
|
54
|
-
|
|
53
|
+
const K = (a) => {
|
|
54
|
+
c(a === A ? null : a), _(!1), S.current?.focus();
|
|
55
55
|
}, j = (a) => {
|
|
56
56
|
if (!d && !a.nativeEvent.isComposing && !(a.key !== "Escape" && a.target.closest(".bca-search-select__clear"))) {
|
|
57
57
|
if (a.key === "Escape") {
|
|
@@ -63,19 +63,19 @@ const Y = (t, e) => {
|
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
if (a.key === "ArrowDown")
|
|
66
|
-
a.preventDefault(),
|
|
66
|
+
a.preventDefault(), N((k) => Math.min(k + 1, E.length - 1));
|
|
67
67
|
else if (a.key === "ArrowUp")
|
|
68
|
-
a.preventDefault(),
|
|
68
|
+
a.preventDefault(), N((k) => Math.max(k - 1, 0));
|
|
69
69
|
else if (a.key === "Enter") {
|
|
70
70
|
a.preventDefault();
|
|
71
|
-
const
|
|
72
|
-
|
|
71
|
+
const k = E[b];
|
|
72
|
+
k && !k.disabled && K(k.id);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
|
-
return /* @__PURE__ */
|
|
77
|
-
i && /* @__PURE__ */
|
|
78
|
-
/* @__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(
|
|
79
79
|
"div",
|
|
80
80
|
{
|
|
81
81
|
...w,
|
|
@@ -85,69 +85,69 @@ const Y = (t, e) => {
|
|
|
85
85
|
"aria-haspopup": "listbox",
|
|
86
86
|
"aria-disabled": d,
|
|
87
87
|
"aria-controls": r ? M : void 0,
|
|
88
|
-
"aria-activedescendant": r && E[
|
|
88
|
+
"aria-activedescendant": r && E[b] ? I(E[b].id) : void 0,
|
|
89
89
|
tabIndex: d ? -1 : 0,
|
|
90
90
|
className: `bca-search-select__trigger ${w?.className ?? ""}`.trim(),
|
|
91
91
|
"data-disabled": d || void 0,
|
|
92
92
|
onClick: () => !d && _(!r),
|
|
93
93
|
children: [
|
|
94
|
-
/* @__PURE__ */
|
|
95
|
-
!d &&
|
|
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(
|
|
96
96
|
"button",
|
|
97
97
|
{
|
|
98
98
|
type: "button",
|
|
99
99
|
className: "bca-search-select__clear",
|
|
100
100
|
"aria-label": "選択を解除",
|
|
101
101
|
onClick: (a) => {
|
|
102
|
-
a.stopPropagation(),
|
|
102
|
+
a.stopPropagation(), c(null);
|
|
103
103
|
},
|
|
104
104
|
children: "×"
|
|
105
105
|
}
|
|
106
|
-
) : /* @__PURE__ */
|
|
106
|
+
) : /* @__PURE__ */ s("span", { className: "bca-search-select__arrow", "aria-hidden": "true", children: "▼" })
|
|
107
107
|
]
|
|
108
108
|
}
|
|
109
109
|
),
|
|
110
|
-
r && /* @__PURE__ */
|
|
111
|
-
/* @__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(
|
|
112
112
|
"input",
|
|
113
113
|
{
|
|
114
|
-
...
|
|
114
|
+
...g,
|
|
115
115
|
ref: R,
|
|
116
116
|
type: "text",
|
|
117
|
-
className: `bca-search-select__search-input ${
|
|
118
|
-
placeholder:
|
|
117
|
+
className: `bca-search-select__search-input ${g?.className ?? ""}`.trim(),
|
|
118
|
+
placeholder: v,
|
|
119
119
|
value: l,
|
|
120
120
|
onChange: (a) => {
|
|
121
|
-
p(a.target.value),
|
|
121
|
+
p(a.target.value), N(0);
|
|
122
122
|
},
|
|
123
123
|
onClick: (a) => a.stopPropagation()
|
|
124
124
|
}
|
|
125
125
|
) }),
|
|
126
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ s("ul", { role: "listbox", id: M, className: "bca-search-select__list", children: E.length > 0 ? E.map((a, k) => /* @__PURE__ */ y(
|
|
127
127
|
"li",
|
|
128
128
|
{
|
|
129
129
|
id: I(a.id),
|
|
130
130
|
role: "option",
|
|
131
|
-
"aria-selected": a.id === (e ??
|
|
131
|
+
"aria-selected": a.id === (e ?? A),
|
|
132
132
|
"aria-disabled": a.disabled || void 0,
|
|
133
133
|
className: "bca-search-select__option",
|
|
134
|
-
"data-active":
|
|
135
|
-
"data-selected": a.id === (e ??
|
|
134
|
+
"data-active": k === b || void 0,
|
|
135
|
+
"data-selected": a.id === (e ?? A) || void 0,
|
|
136
136
|
"data-disabled": a.disabled || void 0,
|
|
137
|
-
onMouseEnter: () => !a.disabled &&
|
|
138
|
-
onClick: () => !a.disabled &&
|
|
137
|
+
onMouseEnter: () => !a.disabled && N(k),
|
|
138
|
+
onClick: () => !a.disabled && K(a.id),
|
|
139
139
|
children: [
|
|
140
|
-
/* @__PURE__ */
|
|
141
|
-
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})` })
|
|
142
142
|
]
|
|
143
143
|
},
|
|
144
144
|
a.id || "__empty__"
|
|
145
|
-
)) : /* @__PURE__ */
|
|
145
|
+
)) : /* @__PURE__ */ s("li", { className: "bca-search-select__no-results", children: h }) })
|
|
146
146
|
] })
|
|
147
147
|
] });
|
|
148
|
-
},
|
|
149
|
-
/* @__PURE__ */
|
|
150
|
-
/* @__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(
|
|
151
151
|
"button",
|
|
152
152
|
{
|
|
153
153
|
type: "button",
|
|
@@ -161,46 +161,46 @@ const Y = (t, e) => {
|
|
|
161
161
|
] }, n.id)) }), G = ({
|
|
162
162
|
options: t,
|
|
163
163
|
value: e,
|
|
164
|
-
onChange:
|
|
164
|
+
onChange: c,
|
|
165
165
|
searchPlaceholder: i = "検索...",
|
|
166
166
|
noResultsText: n = "選択可能な項目はありません",
|
|
167
167
|
listHeight: o = 350,
|
|
168
|
-
maxSelected:
|
|
168
|
+
maxSelected: u,
|
|
169
169
|
className: d = "",
|
|
170
|
-
searchInputProps:
|
|
170
|
+
searchInputProps: h = { className: "bca-textbox__input" }
|
|
171
171
|
}) => {
|
|
172
|
-
const [
|
|
173
|
-
|
|
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
174
|
}, _ = (l) => {
|
|
175
|
-
|
|
175
|
+
c(e.filter((p) => p.id !== l));
|
|
176
176
|
};
|
|
177
|
-
return /* @__PURE__ */
|
|
178
|
-
/* @__PURE__ */
|
|
179
|
-
/* @__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(
|
|
180
180
|
"input",
|
|
181
181
|
{
|
|
182
|
-
...
|
|
182
|
+
...h,
|
|
183
183
|
type: "text",
|
|
184
|
-
className: `bca-multi-select-picker__search-input ${
|
|
184
|
+
className: `bca-multi-select-picker__search-input ${h?.className ?? ""}`.trim(),
|
|
185
185
|
placeholder: i,
|
|
186
|
-
value:
|
|
187
|
-
onChange: (l) =>
|
|
186
|
+
value: v,
|
|
187
|
+
onChange: (l) => f(l.target.value)
|
|
188
188
|
}
|
|
189
189
|
),
|
|
190
|
-
|
|
190
|
+
v && /* @__PURE__ */ s(
|
|
191
191
|
"button",
|
|
192
192
|
{
|
|
193
193
|
type: "button",
|
|
194
194
|
className: "bca-multi-select-picker__search-clear",
|
|
195
195
|
"aria-label": "検索語をクリア",
|
|
196
|
-
onClick: () =>
|
|
196
|
+
onClick: () => f(""),
|
|
197
197
|
children: "×"
|
|
198
198
|
}
|
|
199
199
|
)
|
|
200
200
|
] }),
|
|
201
|
-
/* @__PURE__ */
|
|
202
|
-
const p = l.disabled ||
|
|
203
|
-
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(
|
|
204
204
|
"li",
|
|
205
205
|
{
|
|
206
206
|
className: "bca-multi-select-picker__option",
|
|
@@ -210,62 +210,62 @@ const Y = (t, e) => {
|
|
|
210
210
|
"data-disabled": p || void 0,
|
|
211
211
|
tabIndex: p ? -1 : 0,
|
|
212
212
|
onClick: () => r(l),
|
|
213
|
-
onKeyDown: (
|
|
214
|
-
|
|
213
|
+
onKeyDown: (b) => {
|
|
214
|
+
b.nativeEvent.isComposing || (b.key === "Enter" || b.key === " ") && (b.preventDefault(), r(l));
|
|
215
215
|
},
|
|
216
216
|
children: [
|
|
217
|
-
/* @__PURE__ */
|
|
218
|
-
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})` })
|
|
219
219
|
]
|
|
220
220
|
},
|
|
221
221
|
l.id
|
|
222
222
|
);
|
|
223
|
-
}) : /* @__PURE__ */
|
|
224
|
-
/* @__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: _ }) })
|
|
225
225
|
] });
|
|
226
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) => {
|
|
227
227
|
C(() => {
|
|
228
228
|
if (!e) return;
|
|
229
|
-
const
|
|
230
|
-
if (!
|
|
229
|
+
const c = t.current;
|
|
230
|
+
if (!c) return;
|
|
231
231
|
const i = (n) => {
|
|
232
232
|
if (n.key !== "Tab") return;
|
|
233
|
-
const o = W(
|
|
233
|
+
const o = W(c);
|
|
234
234
|
if (o.length === 0) return;
|
|
235
|
-
const
|
|
236
|
-
n.shiftKey ? (document.activeElement ===
|
|
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());
|
|
237
237
|
};
|
|
238
|
-
return
|
|
238
|
+
return c.addEventListener("keydown", i), () => c.removeEventListener("keydown", i);
|
|
239
239
|
}, [t, e]);
|
|
240
240
|
}, Z = ({
|
|
241
241
|
open: t,
|
|
242
242
|
title: e = "選択",
|
|
243
|
-
options:
|
|
243
|
+
options: c,
|
|
244
244
|
initialValue: i,
|
|
245
245
|
onSubmit: n,
|
|
246
246
|
onCancel: o,
|
|
247
|
-
submitLabel:
|
|
247
|
+
submitLabel: u = "決定",
|
|
248
248
|
cancelLabel: d = "キャンセル",
|
|
249
|
-
requireSelection:
|
|
250
|
-
submitButtonProps:
|
|
251
|
-
cancelButtonProps:
|
|
249
|
+
requireSelection: h = !0,
|
|
250
|
+
submitButtonProps: v = { className: "bca-btn", "data-bca-btn-type": "save" },
|
|
251
|
+
cancelButtonProps: f = { className: "bca-btn" },
|
|
252
252
|
className: x = "",
|
|
253
253
|
...w
|
|
254
254
|
}) => {
|
|
255
|
-
const [
|
|
255
|
+
const [g, r] = D(i ?? []), _ = L(null), l = L(null);
|
|
256
256
|
if (C(() => {
|
|
257
257
|
t && r(i ?? []);
|
|
258
258
|
}, [t]), C(() => {
|
|
259
259
|
if (!t) return;
|
|
260
|
-
const
|
|
261
|
-
|
|
260
|
+
const b = (N) => {
|
|
261
|
+
N.isComposing || N.key === "Escape" && o();
|
|
262
262
|
};
|
|
263
|
-
return document.addEventListener("keydown",
|
|
263
|
+
return document.addEventListener("keydown", b), () => document.removeEventListener("keydown", b);
|
|
264
264
|
}, [t, o]), C(() => {
|
|
265
265
|
t ? (l.current = document.activeElement, _.current?.focus()) : (l.current?.focus(), l.current = null);
|
|
266
266
|
}, [t]), X(_, t), !t) return null;
|
|
267
|
-
const p = !
|
|
268
|
-
return /* @__PURE__ */
|
|
267
|
+
const p = !h || g.length > 0;
|
|
268
|
+
return /* @__PURE__ */ s("div", { className: "bca-multi-select-dialog__overlay", children: /* @__PURE__ */ y(
|
|
269
269
|
"div",
|
|
270
270
|
{
|
|
271
271
|
ref: _,
|
|
@@ -275,31 +275,31 @@ const Y = (t, e) => {
|
|
|
275
275
|
tabIndex: -1,
|
|
276
276
|
className: `bca-multi-select-dialog ${x}`.trim(),
|
|
277
277
|
children: [
|
|
278
|
-
/* @__PURE__ */
|
|
279
|
-
/* @__PURE__ */
|
|
280
|
-
/* @__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: "×" })
|
|
281
281
|
] }),
|
|
282
|
-
/* @__PURE__ */
|
|
283
|
-
/* @__PURE__ */
|
|
284
|
-
/* @__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(
|
|
285
285
|
"button",
|
|
286
286
|
{
|
|
287
|
-
...
|
|
287
|
+
...f,
|
|
288
288
|
type: "button",
|
|
289
|
-
className: `bca-multi-select-dialog__button ${
|
|
289
|
+
className: `bca-multi-select-dialog__button ${f?.className ?? ""}`.trim(),
|
|
290
290
|
onClick: o,
|
|
291
291
|
children: d
|
|
292
292
|
}
|
|
293
293
|
),
|
|
294
|
-
/* @__PURE__ */
|
|
294
|
+
/* @__PURE__ */ s(
|
|
295
295
|
"button",
|
|
296
296
|
{
|
|
297
|
-
...
|
|
297
|
+
...v,
|
|
298
298
|
type: "button",
|
|
299
|
-
className: `bca-multi-select-dialog__button bca-multi-select-dialog__button--primary ${
|
|
299
|
+
className: `bca-multi-select-dialog__button bca-multi-select-dialog__button--primary ${v?.className ?? ""}`.trim(),
|
|
300
300
|
disabled: !p,
|
|
301
|
-
onClick: () => n(
|
|
302
|
-
children:
|
|
301
|
+
onClick: () => n(g),
|
|
302
|
+
children: u
|
|
303
303
|
}
|
|
304
304
|
)
|
|
305
305
|
] })
|
|
@@ -309,56 +309,58 @@ const Y = (t, e) => {
|
|
|
309
309
|
}, ae = ({
|
|
310
310
|
options: t,
|
|
311
311
|
value: e,
|
|
312
|
-
onChange:
|
|
312
|
+
onChange: c,
|
|
313
313
|
name: i,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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,
|
|
322
|
+
cancelLabel: w,
|
|
323
|
+
requireSelection: g,
|
|
324
|
+
addButtonProps: r = { className: "bca-btn", "data-bca-btn-type": "add" },
|
|
325
|
+
submitButtonProps: _,
|
|
326
|
+
cancelButtonProps: l,
|
|
327
|
+
...p
|
|
327
328
|
}) => {
|
|
328
|
-
const [
|
|
329
|
-
|
|
330
|
-
},
|
|
331
|
-
|
|
332
|
-
};
|
|
333
|
-
return /* @__PURE__ */
|
|
334
|
-
i &&
|
|
335
|
-
|
|
336
|
-
/* @__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(
|
|
337
339
|
"button",
|
|
338
340
|
{
|
|
339
|
-
...
|
|
341
|
+
...r,
|
|
340
342
|
type: "button",
|
|
341
|
-
className: `bca-multi-select-field__add ${
|
|
342
|
-
disabled:
|
|
343
|
-
onClick: () =>
|
|
344
|
-
children:
|
|
343
|
+
className: `bca-multi-select-field__add ${r?.className ?? ""}`.trim(),
|
|
344
|
+
disabled: h || M,
|
|
345
|
+
onClick: () => N(!0),
|
|
346
|
+
children: o
|
|
345
347
|
}
|
|
346
348
|
) }),
|
|
347
|
-
/* @__PURE__ */
|
|
349
|
+
/* @__PURE__ */ s(
|
|
348
350
|
Z,
|
|
349
351
|
{
|
|
350
|
-
open:
|
|
351
|
-
title:
|
|
352
|
-
options:
|
|
353
|
-
onSubmit:
|
|
354
|
-
onCancel: () =>
|
|
355
|
-
maxSelected:
|
|
356
|
-
submitLabel:
|
|
357
|
-
cancelLabel:
|
|
358
|
-
requireSelection:
|
|
359
|
-
submitButtonProps:
|
|
360
|
-
cancelButtonProps:
|
|
361
|
-
...
|
|
352
|
+
open: b,
|
|
353
|
+
title: u,
|
|
354
|
+
options: R,
|
|
355
|
+
onSubmit: O,
|
|
356
|
+
onCancel: () => N(!1),
|
|
357
|
+
maxSelected: S,
|
|
358
|
+
submitLabel: x,
|
|
359
|
+
cancelLabel: w,
|
|
360
|
+
requireSelection: g,
|
|
361
|
+
submitButtonProps: _,
|
|
362
|
+
cancelButtonProps: l,
|
|
363
|
+
...p
|
|
362
364
|
}
|
|
363
365
|
)
|
|
364
366
|
] });
|