@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 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 N, jsx as c } from "react/jsx-runtime";
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 s = e.trim().toLowerCase();
5
- return s ? t.filter(
6
- (i) => i.label.toLowerCase().includes(s) || (i.sublabel ?? "").toLowerCase().includes(s)
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
- }, K = (t, e) => q(() => Y(t, e), [t, e]), z = (t, e, s) => {
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) && s();
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, s]);
16
- }, B = (t, e, s, i = 240) => {
17
- const [n, o] = D(s === "top" ? "top" : "bottom");
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 (s !== "auto") {
21
- o(s);
20
+ if (c !== "auto") {
21
+ o(c);
22
22
  return;
23
23
  }
24
- const b = t.current;
25
- if (!b) return;
26
- const d = b.getBoundingClientRect(), y = window.innerHeight - d.bottom;
27
- o(y < i && d.top > y ? "top" : "bottom");
28
- }, [t, e, s, i]), n;
29
- }, O = "", te = ({
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: s,
32
+ onChange: c,
33
33
  name: i,
34
34
  placeholder: n = "選択してください",
35
35
  emptyLabel: o,
36
- clearable: b = !0,
36
+ clearable: u = !0,
37
37
  disabled: d = !1,
38
- noResultsText: y = "一致する項目がありません",
39
- searchPlaceholder: m = "検索...",
40
- dropdownPlacement: k = "auto",
38
+ noResultsText: h = "一致する項目がありません",
39
+ searchPlaceholder: v = "検索...",
40
+ dropdownPlacement: f = "auto",
41
41
  className: x = "",
42
42
  triggerProps: w = { className: "bca-textbox__input" },
43
- searchInputProps: f = { className: "bca-textbox__input" }
43
+ searchInputProps: g = { className: "bca-textbox__input" }
44
44
  }) => {
45
- const [r, _] = D(!1), [l, p] = D(""), [u, g] = D(0), $ = L(null), R = L(null), S = L(null), M = H(), I = (a) => `${M}-option-${a || "__empty__"}`, h = K(t, l), E = o ? [{ id: O, label: o }, ...h] : h, A = t.find((a) => a.id === e) ?? null;
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 U = B($, r, k);
47
+ const F = B($, r, f);
48
48
  C(() => {
49
- r || (p(""), g(0));
49
+ r || (p(""), N(0));
50
50
  }, [r]), C(() => {
51
51
  r && R.current?.focus();
52
52
  }, [r]);
53
- const F = (a) => {
54
- s(a === O ? null : a), _(!1), S.current?.focus();
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(), g((v) => Math.min(v + 1, E.length - 1));
66
+ a.preventDefault(), N((k) => Math.min(k + 1, E.length - 1));
67
67
  else if (a.key === "ArrowUp")
68
- a.preventDefault(), g((v) => Math.max(v - 1, 0));
68
+ a.preventDefault(), N((k) => Math.max(k - 1, 0));
69
69
  else if (a.key === "Enter") {
70
70
  a.preventDefault();
71
- const v = E[u];
72
- v && !v.disabled && F(v.id);
71
+ const k = E[b];
72
+ k && !k.disabled && K(k.id);
73
73
  }
74
74
  }
75
75
  };
76
- return /* @__PURE__ */ N("div", { ref: $, className: `bca-search-select ${x}`.trim(), onKeyDown: j, children: [
77
- i && /* @__PURE__ */ c("input", { type: "hidden", name: i, value: e ?? "" }),
78
- /* @__PURE__ */ N(
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[u] ? I(E[u].id) : void 0,
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__ */ c("span", { className: A ? "bca-search-select__value" : "bca-search-select__placeholder", children: A ? A.label : n }),
95
- !d && b && e ? /* @__PURE__ */ c(
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(), s(null);
102
+ a.stopPropagation(), c(null);
103
103
  },
104
104
  children: "×"
105
105
  }
106
- ) : /* @__PURE__ */ c("span", { className: "bca-search-select__arrow", "aria-hidden": "true", children: "▼" })
106
+ ) : /* @__PURE__ */ s("span", { className: "bca-search-select__arrow", "aria-hidden": "true", children: "▼" })
107
107
  ]
108
108
  }
109
109
  ),
110
- r && /* @__PURE__ */ N("div", { className: "bca-search-select__dropdown", "data-placement": U, children: [
111
- /* @__PURE__ */ c("div", { className: "bca-search-select__search", children: /* @__PURE__ */ c(
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
- ...f,
114
+ ...g,
115
115
  ref: R,
116
116
  type: "text",
117
- className: `bca-search-select__search-input ${f?.className ?? ""}`.trim(),
118
- placeholder: m,
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), g(0);
121
+ p(a.target.value), N(0);
122
122
  },
123
123
  onClick: (a) => a.stopPropagation()
124
124
  }
125
125
  ) }),
126
- /* @__PURE__ */ c("ul", { role: "listbox", id: M, className: "bca-search-select__list", children: E.length > 0 ? E.map((a, v) => /* @__PURE__ */ N(
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 ?? O),
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": v === u || void 0,
135
- "data-selected": a.id === (e ?? O) || void 0,
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 && g(v),
138
- onClick: () => !a.disabled && F(a.id),
137
+ onMouseEnter: () => !a.disabled && N(k),
138
+ onClick: () => !a.disabled && K(a.id),
139
139
  children: [
140
- /* @__PURE__ */ c("span", { className: "bca-search-select__label", children: a.label }),
141
- a.sublabel && /* @__PURE__ */ c("span", { className: "bca-search-select__sublabel", children: `(${a.sublabel})` })
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__ */ c("li", { className: "bca-search-select__no-results", children: y }) })
145
+ )) : /* @__PURE__ */ s("li", { className: "bca-search-select__no-results", children: h }) })
146
146
  ] })
147
147
  ] });
148
- }, T = ({ items: t, onRemove: e, emptyText: s = "選択されていません", disabled: i = !1 }) => t.length === 0 ? /* @__PURE__ */ c("span", { className: "bca-selected-tags__empty", children: s }) : /* @__PURE__ */ c("ul", { className: "bca-selected-tags", children: t.map((n) => /* @__PURE__ */ N("li", { className: "bca-selected-tags__item", children: [
149
- /* @__PURE__ */ c("span", { className: "bca-selected-tags__label", children: n.label }),
150
- /* @__PURE__ */ c(
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: s,
164
+ onChange: c,
165
165
  searchPlaceholder: i = "検索...",
166
166
  noResultsText: n = "選択可能な項目はありません",
167
167
  listHeight: o = 350,
168
- maxSelected: b,
168
+ maxSelected: u,
169
169
  className: d = "",
170
- searchInputProps: y = { className: "bca-textbox__input" }
170
+ searchInputProps: h = { className: "bca-textbox__input" }
171
171
  }) => {
172
- const [m, k] = D(""), x = new Set(e.map((l) => l.id)), w = K(t, m).filter((l) => !x.has(l.id)), f = b !== void 0 && e.length >= b, r = (l) => {
173
- f || l.disabled || s([...e, l]);
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
- s(e.filter((p) => p.id !== l));
175
+ c(e.filter((p) => p.id !== l));
176
176
  };
177
- return /* @__PURE__ */ N("div", { className: `bca-multi-select-picker ${d}`.trim(), children: [
178
- /* @__PURE__ */ N("div", { className: "bca-multi-select-picker__search", children: [
179
- /* @__PURE__ */ c(
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
- ...y,
182
+ ...h,
183
183
  type: "text",
184
- className: `bca-multi-select-picker__search-input ${y?.className ?? ""}`.trim(),
184
+ className: `bca-multi-select-picker__search-input ${h?.className ?? ""}`.trim(),
185
185
  placeholder: i,
186
- value: m,
187
- onChange: (l) => k(l.target.value)
186
+ value: v,
187
+ onChange: (l) => f(l.target.value)
188
188
  }
189
189
  ),
190
- m && /* @__PURE__ */ c(
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: () => k(""),
196
+ onClick: () => f(""),
197
197
  children: "×"
198
198
  }
199
199
  )
200
200
  ] }),
201
- /* @__PURE__ */ c("div", { className: "bca-multi-select-picker__list-wrapper", style: { maxHeight: o }, children: /* @__PURE__ */ c("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 || f;
203
- return /* @__PURE__ */ N(
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: (u) => {
214
- u.nativeEvent.isComposing || (u.key === "Enter" || u.key === " ") && (u.preventDefault(), r(l));
213
+ onKeyDown: (b) => {
214
+ b.nativeEvent.isComposing || (b.key === "Enter" || b.key === " ") && (b.preventDefault(), r(l));
215
215
  },
216
216
  children: [
217
- /* @__PURE__ */ c("span", { className: "bca-multi-select-picker__label", children: l.label }),
218
- l.sublabel && /* @__PURE__ */ c("span", { className: "bca-multi-select-picker__sublabel", children: `(${l.sublabel})` })
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__ */ c("li", { className: "bca-multi-select-picker__no-results", children: n }) }) }),
224
- /* @__PURE__ */ c("div", { className: "bca-multi-select-picker__selected", children: /* @__PURE__ */ c(T, { items: e, onRemove: _ }) })
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 s = t.current;
230
- if (!s) return;
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(s);
233
+ const o = W(c);
234
234
  if (o.length === 0) return;
235
- const b = o[0], d = o[o.length - 1];
236
- n.shiftKey ? (document.activeElement === b || !s.contains(document.activeElement)) && (n.preventDefault(), d.focus()) : (document.activeElement === d || !s.contains(document.activeElement)) && (n.preventDefault(), b.focus());
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 s.addEventListener("keydown", i), () => s.removeEventListener("keydown", i);
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: s,
243
+ options: c,
244
244
  initialValue: i,
245
245
  onSubmit: n,
246
246
  onCancel: o,
247
- submitLabel: b = "決定",
247
+ submitLabel: u = "決定",
248
248
  cancelLabel: d = "キャンセル",
249
- requireSelection: y = !0,
250
- submitButtonProps: m = { className: "bca-btn", "data-bca-btn-type": "save" },
251
- cancelButtonProps: k = { className: "bca-btn" },
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 [f, r] = D(i ?? []), _ = L(null), l = L(null);
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 u = (g) => {
261
- g.isComposing || g.key === "Escape" && o();
260
+ const b = (N) => {
261
+ N.isComposing || N.key === "Escape" && o();
262
262
  };
263
- return document.addEventListener("keydown", u), () => document.removeEventListener("keydown", u);
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 = !y || f.length > 0;
268
- return /* @__PURE__ */ c("div", { className: "bca-multi-select-dialog__overlay", children: /* @__PURE__ */ N(
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__ */ N("div", { className: "bca-multi-select-dialog__header", children: [
279
- /* @__PURE__ */ c("span", { className: "bca-multi-select-dialog__title", children: e }),
280
- /* @__PURE__ */ c("button", { type: "button", className: "bca-multi-select-dialog__close", "aria-label": "閉じる", onClick: o, children: "×" })
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__ */ c("div", { className: "bca-multi-select-dialog__body", children: /* @__PURE__ */ c(G, { options: s, value: f, onChange: r, ...w }) }),
283
- /* @__PURE__ */ N("div", { className: "bca-multi-select-dialog__footer", children: [
284
- /* @__PURE__ */ c(
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
- ...k,
287
+ ...f,
288
288
  type: "button",
289
- className: `bca-multi-select-dialog__button ${k?.className ?? ""}`.trim(),
289
+ className: `bca-multi-select-dialog__button ${f?.className ?? ""}`.trim(),
290
290
  onClick: o,
291
291
  children: d
292
292
  }
293
293
  ),
294
- /* @__PURE__ */ c(
294
+ /* @__PURE__ */ s(
295
295
  "button",
296
296
  {
297
- ...m,
297
+ ...v,
298
298
  type: "button",
299
- className: `bca-multi-select-dialog__button bca-multi-select-dialog__button--primary ${m?.className ?? ""}`.trim(),
299
+ className: `bca-multi-select-dialog__button bca-multi-select-dialog__button--primary ${v?.className ?? ""}`.trim(),
300
300
  disabled: !p,
301
- onClick: () => n(f),
302
- children: b
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: s,
312
+ onChange: c,
313
313
  name: i,
314
- addButtonLabel: n = "追加",
315
- dialogTitle: o = "選択",
316
- emptyText: b = "選択されていません",
317
- disabled: d = !1,
318
- className: y = "",
319
- maxSelected: m,
320
- submitLabel: k,
321
- cancelLabel: x,
322
- requireSelection: w,
323
- addButtonProps: f = { className: "bca-btn", "data-bca-btn-type": "add" },
324
- submitButtonProps: r,
325
- cancelButtonProps: _,
326
- ...l
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 [p, u] = D(!1), g = new Set(e.map((h) => h.id)), $ = t.filter((h) => !g.has(h.id)), R = m !== void 0 ? Math.max(0, m - e.length) : void 0, S = m !== void 0 && e.length >= m, M = (h) => {
329
- s(e.filter((E) => E.id !== h));
330
- }, I = (h) => {
331
- s([...e, ...h]), u(!1);
332
- };
333
- return /* @__PURE__ */ N("div", { className: `bca-multi-select-field ${y}`.trim(), children: [
334
- i && e.map((h) => /* @__PURE__ */ c("input", { type: "hidden", name: i, value: h.id }, h.id)),
335
- /* @__PURE__ */ c("div", { className: "bca-multi-select-field__tags", children: /* @__PURE__ */ c(T, { items: e, onRemove: M, emptyText: b, disabled: d }) }),
336
- /* @__PURE__ */ c("div", { className: "bca-multi-select-field__actions", children: /* @__PURE__ */ c(
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
- ...f,
341
+ ...r,
340
342
  type: "button",
341
- className: `bca-multi-select-field__add ${f?.className ?? ""}`.trim(),
342
- disabled: d || S,
343
- onClick: () => u(!0),
344
- children: n
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__ */ c(
349
+ /* @__PURE__ */ s(
348
350
  Z,
349
351
  {
350
- open: p,
351
- title: o,
352
- options: $,
353
- onSubmit: I,
354
- onCancel: () => u(!1),
355
- maxSelected: R,
356
- submitLabel: k,
357
- cancelLabel: x,
358
- requireSelection: w,
359
- submitButtonProps: r,
360
- cancelButtonProps: _,
361
- ...l
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
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecatchup/basercms-ui",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "検索付きの選択UIコンポーネント集",
5
5
  "type": "module",
6
6
  "license": "MIT",