@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 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 g, jsx as c } from "react/jsx-runtime";
2
- import { useMemo as P, useEffect as C, useState as D, useLayoutEffect as U, useRef as $, useId as j } from "react";
3
- const q = (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)
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
- }, F = (t, e) => P(() => q(t, e), [t, e]), Q = (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
- }, H = (t, e, s, i = 240) => {
17
- const [n, r] = D(s === "top" ? "top" : "bottom");
18
- return U(() => {
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 (s !== "auto") {
21
- r(s);
20
+ if (c !== "auto") {
21
+ o(c);
22
22
  return;
23
23
  }
24
- const m = t.current;
25
- if (!m) return;
26
- const o = m.getBoundingClientRect(), f = window.innerHeight - o.bottom;
27
- r(f < i && o.top > f ? "top" : "bottom");
28
- }, [t, e, s, i]), n;
29
- }, R = "", V = ({
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
- emptyLabel: r,
36
- clearable: m = !0,
37
- disabled: o = !1,
38
- noResultsText: f = "一致する項目がありません",
39
- searchPlaceholder: p = "検索...",
40
- dropdownPlacement: k = "auto",
41
- className: w = ""
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 [d, b] = D(!1), [v, l] = D(""), [u, h] = D(0), y = $(null), x = $(null), S = $(null), M = j(), I = (a) => `${M}-option-${a || "__empty__"}`, L = F(t, v), E = r ? [{ id: R, label: r }, ...L] : L, _ = t.find((a) => a.id === e) ?? null;
44
- Q(y, d, () => b(!1));
45
- const O = H(y, d, k);
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
- d || (l(""), h(0));
48
- }, [d]), C(() => {
49
- d && x.current?.focus();
50
- }, [d]);
51
- const A = (a) => {
52
- s(a === R ? null : a), b(!1), S.current?.focus();
53
- }, T = (a) => {
54
- if (!o && !a.nativeEvent.isComposing && !(a.key !== "Escape" && a.target.closest(".bca-search-select__clear"))) {
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
- b(!1), S.current?.focus();
58
+ _(!1), S.current?.focus();
57
59
  return;
58
60
  }
59
- if (!d) {
60
- (a.key === "ArrowDown" || a.key === "Enter") && (a.preventDefault(), b(!0));
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(), h((N) => Math.min(N + 1, E.length - 1));
66
+ a.preventDefault(), N((k) => Math.min(k + 1, E.length - 1));
65
67
  else if (a.key === "ArrowUp")
66
- a.preventDefault(), h((N) => Math.max(N - 1, 0));
68
+ a.preventDefault(), N((k) => Math.max(k - 1, 0));
67
69
  else if (a.key === "Enter") {
68
70
  a.preventDefault();
69
- const N = E[u];
70
- N && !N.disabled && A(N.id);
71
+ const k = E[b];
72
+ k && !k.disabled && K(k.id);
71
73
  }
72
74
  }
73
75
  };
74
- return /* @__PURE__ */ g("div", { ref: y, className: `bca-search-select ${w}`.trim(), onKeyDown: T, children: [
75
- i && /* @__PURE__ */ c("input", { type: "hidden", name: i, value: e ?? "" }),
76
- /* @__PURE__ */ g(
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": d,
84
+ "aria-expanded": r,
82
85
  "aria-haspopup": "listbox",
83
- "aria-disabled": o,
84
- "aria-controls": d ? M : void 0,
85
- "aria-activedescendant": d && E[u] ? I(E[u].id) : void 0,
86
- tabIndex: o ? -1 : 0,
87
- className: "bca-search-select__trigger",
88
- "data-disabled": o || void 0,
89
- onClick: () => !o && b(!d),
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__ */ c("span", { className: _ ? "bca-search-select__value" : "bca-search-select__placeholder", children: _ ? _.label : n }),
92
- !o && m && 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(
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(), s(null);
102
+ a.stopPropagation(), c(null);
100
103
  },
101
104
  children: "×"
102
105
  }
103
- ) : /* @__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: "▼" })
104
107
  ]
105
108
  }
106
109
  ),
107
- d && /* @__PURE__ */ g("div", { className: "bca-search-select__dropdown", "data-placement": O, children: [
108
- /* @__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(
109
112
  "input",
110
113
  {
111
- ref: x,
114
+ ...g,
115
+ ref: R,
112
116
  type: "text",
113
- className: "bca-search-select__search-input",
114
- placeholder: p,
115
- value: v,
117
+ className: `bca-search-select__search-input ${g?.className ?? ""}`.trim(),
118
+ placeholder: v,
119
+ value: l,
116
120
  onChange: (a) => {
117
- l(a.target.value), h(0);
121
+ p(a.target.value), N(0);
118
122
  },
119
123
  onClick: (a) => a.stopPropagation()
120
124
  }
121
125
  ) }),
122
- /* @__PURE__ */ c("ul", { role: "listbox", id: M, className: "bca-search-select__list", children: E.length > 0 ? E.map((a, N) => /* @__PURE__ */ g(
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 ?? R),
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": N === u || void 0,
131
- "data-selected": a.id === (e ?? R) || void 0,
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 && h(N),
134
- onClick: () => !a.disabled && A(a.id),
137
+ onMouseEnter: () => !a.disabled && N(k),
138
+ onClick: () => !a.disabled && K(a.id),
135
139
  children: [
136
- /* @__PURE__ */ c("span", { className: "bca-search-select__label", children: a.label }),
137
- 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})` })
138
142
  ]
139
143
  },
140
144
  a.id || "__empty__"
141
- )) : /* @__PURE__ */ c("li", { className: "bca-search-select__no-results", children: f }) })
145
+ )) : /* @__PURE__ */ s("li", { className: "bca-search-select__no-results", children: h }) })
142
146
  ] })
143
147
  ] });
144
- }, K = ({ 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__ */ g("li", { className: "bca-selected-tags__item", children: [
145
- /* @__PURE__ */ c("span", { className: "bca-selected-tags__label", children: n.label }),
146
- /* @__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(
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)) }), Y = ({
161
+ ] }, n.id)) }), G = ({
158
162
  options: t,
159
163
  value: e,
160
- onChange: s,
164
+ onChange: c,
161
165
  searchPlaceholder: i = "検索...",
162
166
  noResultsText: n = "選択可能な項目はありません",
163
- listHeight: r = 350,
164
- maxSelected: m,
165
- className: o = ""
167
+ listHeight: o = 350,
168
+ maxSelected: u,
169
+ className: d = "",
170
+ searchInputProps: h = { className: "bca-textbox__input" }
166
171
  }) => {
167
- const [f, p] = D(""), k = new Set(e.map((l) => l.id)), w = F(t, f).filter((l) => !k.has(l.id)), d = m !== void 0 && e.length >= m, b = (l) => {
168
- d || l.disabled || s([...e, l]);
169
- }, v = (l) => {
170
- s(e.filter((u) => u.id !== 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
+ }, _ = (l) => {
175
+ c(e.filter((p) => p.id !== l));
171
176
  };
172
- return /* @__PURE__ */ g("div", { className: `bca-multi-select-picker ${o}`.trim(), children: [
173
- /* @__PURE__ */ g("div", { className: "bca-multi-select-picker__search", children: [
174
- /* @__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(
175
180
  "input",
176
181
  {
182
+ ...h,
177
183
  type: "text",
178
- className: "bca-multi-select-picker__search-input",
184
+ className: `bca-multi-select-picker__search-input ${h?.className ?? ""}`.trim(),
179
185
  placeholder: i,
180
- value: f,
181
- onChange: (l) => p(l.target.value)
186
+ value: v,
187
+ onChange: (l) => f(l.target.value)
182
188
  }
183
189
  ),
184
- f && /* @__PURE__ */ c(
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: () => p(""),
196
+ onClick: () => f(""),
191
197
  children: "×"
192
198
  }
193
199
  )
194
200
  ] }),
195
- /* @__PURE__ */ c("div", { className: "bca-multi-select-picker__list-wrapper", style: { maxHeight: r }, children: /* @__PURE__ */ c("ul", { className: "bca-multi-select-picker__list", role: "listbox", "aria-multiselectable": "true", "aria-label": "選択可能な項目", children: w.length > 0 ? w.map((l) => {
196
- const u = l.disabled || d;
197
- return /* @__PURE__ */ g(
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": u || void 0,
204
- "data-disabled": u || void 0,
205
- tabIndex: u ? -1 : 0,
206
- onClick: () => b(l),
207
- onKeyDown: (h) => {
208
- h.nativeEvent.isComposing || (h.key === "Enter" || h.key === " ") && (h.preventDefault(), b(l));
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__ */ c("span", { className: "bca-multi-select-picker__label", children: l.label }),
212
- 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})` })
213
219
  ]
214
220
  },
215
221
  l.id
216
222
  );
217
- }) : /* @__PURE__ */ c("li", { className: "bca-multi-select-picker__no-results", children: n }) }) }),
218
- /* @__PURE__ */ c("div", { className: "bca-multi-select-picker__selected", children: /* @__PURE__ */ c(K, { items: e, onRemove: v }) })
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
- }, z = 'a[href], button, input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])', B = (t) => "disabled" in t && t.disabled, G = (t) => Array.from(t.querySelectorAll(z)).filter((e) => !B(e)), J = (t, e) => {
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 s = t.current;
224
- if (!s) return;
229
+ const c = t.current;
230
+ if (!c) return;
225
231
  const i = (n) => {
226
232
  if (n.key !== "Tab") return;
227
- const r = G(s);
228
- if (r.length === 0) return;
229
- const m = r[0], o = r[r.length - 1];
230
- n.shiftKey ? (document.activeElement === m || !s.contains(document.activeElement)) && (n.preventDefault(), o.focus()) : (document.activeElement === o || !s.contains(document.activeElement)) && (n.preventDefault(), m.focus());
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 s.addEventListener("keydown", i), () => s.removeEventListener("keydown", i);
238
+ return c.addEventListener("keydown", i), () => c.removeEventListener("keydown", i);
233
239
  }, [t, e]);
234
- }, W = ({
240
+ }, Z = ({
235
241
  open: t,
236
242
  title: e = "選択",
237
- options: s,
243
+ options: c,
238
244
  initialValue: i,
239
245
  onSubmit: n,
240
- onCancel: r,
241
- submitLabel: m = "決定",
242
- cancelLabel: o = "キャンセル",
243
- requireSelection: f = !0,
244
- submitButtonProps: p = { className: "bca-btn", "data-bca-btn-type": "save" },
245
- cancelButtonProps: k = { className: "bca-btn" },
246
- className: w = "",
247
- ...d
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 [b, v] = D(i ?? []), l = $(null), u = $(null);
255
+ const [g, r] = D(i ?? []), _ = L(null), l = L(null);
250
256
  if (C(() => {
251
- t && v(i ?? []);
257
+ t && r(i ?? []);
252
258
  }, [t]), C(() => {
253
259
  if (!t) return;
254
- const y = (x) => {
255
- x.isComposing || x.key === "Escape" && r();
260
+ const b = (N) => {
261
+ N.isComposing || N.key === "Escape" && o();
256
262
  };
257
- return document.addEventListener("keydown", y), () => document.removeEventListener("keydown", y);
258
- }, [t, r]), C(() => {
259
- t ? (u.current = document.activeElement, l.current?.focus()) : (u.current?.focus(), u.current = null);
260
- }, [t]), J(l, t), !t) return null;
261
- const h = !f || b.length > 0;
262
- return /* @__PURE__ */ c("div", { className: "bca-multi-select-dialog__overlay", children: /* @__PURE__ */ g(
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: l,
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 ${w}`.trim(),
276
+ className: `bca-multi-select-dialog ${x}`.trim(),
271
277
  children: [
272
- /* @__PURE__ */ g("div", { className: "bca-multi-select-dialog__header", children: [
273
- /* @__PURE__ */ c("span", { className: "bca-multi-select-dialog__title", children: e }),
274
- /* @__PURE__ */ c("button", { type: "button", className: "bca-multi-select-dialog__close", "aria-label": "閉じる", onClick: r, 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: "×" })
275
281
  ] }),
276
- /* @__PURE__ */ c("div", { className: "bca-multi-select-dialog__body", children: /* @__PURE__ */ c(Y, { options: s, value: b, onChange: v, ...d }) }),
277
- /* @__PURE__ */ g("div", { className: "bca-multi-select-dialog__footer", children: [
278
- /* @__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(
279
285
  "button",
280
286
  {
281
- ...k,
287
+ ...f,
282
288
  type: "button",
283
- className: `bca-multi-select-dialog__button ${k?.className ?? ""}`.trim(),
284
- onClick: r,
285
- children: o
289
+ className: `bca-multi-select-dialog__button ${f?.className ?? ""}`.trim(),
290
+ onClick: o,
291
+ children: d
286
292
  }
287
293
  ),
288
- /* @__PURE__ */ c(
294
+ /* @__PURE__ */ s(
289
295
  "button",
290
296
  {
291
- ...p,
297
+ ...v,
292
298
  type: "button",
293
- className: `bca-multi-select-dialog__button bca-multi-select-dialog__button--primary ${p?.className ?? ""}`.trim(),
294
- disabled: !h,
295
- onClick: () => n(b),
296
- children: m
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
- }, ee = ({
309
+ }, ae = ({
304
310
  options: t,
305
311
  value: e,
306
- onChange: s,
312
+ onChange: c,
307
313
  name: i,
308
- addButtonLabel: n = "追加",
309
- dialogTitle: r = "選択",
310
- emptyText: m = "選択されていません",
311
- disabled: o = !1,
312
- className: f = "",
313
- maxSelected: p,
314
- submitLabel: k,
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: d,
317
- addButtonProps: b = { className: "bca-btn", "data-bca-btn-type": "add" },
318
- submitButtonProps: v,
323
+ requireSelection: g,
324
+ addButtonProps: r = { className: "bca-btn", "data-bca-btn-type": "add" },
325
+ submitButtonProps: _,
319
326
  cancelButtonProps: l,
320
- ...u
327
+ ...p
321
328
  }) => {
322
- const [h, y] = D(!1), x = new Set(e.map((_) => _.id)), S = t.filter((_) => !x.has(_.id)), M = p !== void 0 ? Math.max(0, p - e.length) : void 0, I = p !== void 0 && e.length >= p, L = (_) => {
323
- s(e.filter((O) => O.id !== _));
324
- }, E = (_) => {
325
- s([...e, ..._]), y(!1);
326
- };
327
- return /* @__PURE__ */ g("div", { className: `bca-multi-select-field ${f}`.trim(), children: [
328
- i && e.map((_) => /* @__PURE__ */ c("input", { type: "hidden", name: i, value: _.id }, _.id)),
329
- /* @__PURE__ */ c("div", { className: "bca-multi-select-field__tags", children: /* @__PURE__ */ c(K, { items: e, onRemove: L, emptyText: m, disabled: o }) }),
330
- /* @__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(
331
339
  "button",
332
340
  {
333
- ...b,
341
+ ...r,
334
342
  type: "button",
335
- className: `bca-multi-select-field__add ${b?.className ?? ""}`.trim(),
336
- disabled: o || I,
337
- onClick: () => y(!0),
338
- children: n
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__ */ c(
342
- W,
349
+ /* @__PURE__ */ s(
350
+ Z,
343
351
  {
344
- open: h,
345
- title: r,
346
- options: S,
347
- onSubmit: E,
348
- onCancel: () => y(!1),
349
- maxSelected: M,
350
- submitLabel: k,
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: d,
353
- submitButtonProps: v,
360
+ requireSelection: g,
361
+ submitButtonProps: _,
354
362
  cancelButtonProps: l,
355
- ...u
363
+ ...p
356
364
  }
357
365
  )
358
366
  ] });
359
367
  };
360
368
  export {
361
- W as MultiSelectDialog,
362
- ee as MultiSelectField,
363
- Y as MultiSelectPicker,
364
- V as SearchSelect
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;display:flex;align-items:center;box-sizing:border-box;width:100%;padding:4px 32px 4px 8px;border:1px solid var(--bca-border);border-radius:var(--bca-radius);background-color:#fff;cursor:pointer}.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{font:inherit;box-sizing:border-box;width:100%;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{font:inherit;box-sizing:border-box;width:100%;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}
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}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecatchup/basercms-ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "検索付きの選択UIコンポーネント集",
5
5
  "type": "module",
6
6
  "license": "MIT",