@14ch/svelte-ui 0.0.1
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 +359 -0
- package/dist/assets/styles/README.md +144 -0
- package/dist/assets/styles/core.scss +61 -0
- package/dist/assets/styles/import.scss +11 -0
- package/dist/assets/styles/optional/fonts.scss +23 -0
- package/dist/assets/styles/optional/reset.scss +230 -0
- package/dist/assets/styles/variables.scss +805 -0
- package/dist/components/Button.svelte +574 -0
- package/dist/components/Button.svelte.d.ts +56 -0
- package/dist/components/COMPONENT_DESIGN_GUIDELINES.md +127 -0
- package/dist/components/Checkbox.svelte +523 -0
- package/dist/components/Checkbox.svelte.d.ts +42 -0
- package/dist/components/CheckboxGroup.svelte +82 -0
- package/dist/components/CheckboxGroup.svelte.d.ts +13 -0
- package/dist/components/ColorPicker.svelte +496 -0
- package/dist/components/ColorPicker.svelte.d.ts +45 -0
- package/dist/components/Combobox.svelte +576 -0
- package/dist/components/Combobox.svelte.d.ts +52 -0
- package/dist/components/ConfirmDialog.svelte +116 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +20 -0
- package/dist/components/Datepicker.svelte +578 -0
- package/dist/components/Datepicker.svelte.d.ts +72 -0
- package/dist/components/DatepickerCalendar.svelte +925 -0
- package/dist/components/DatepickerCalendar.svelte.d.ts +31 -0
- package/dist/components/Dialog.svelte +245 -0
- package/dist/components/Dialog.svelte.d.ts +38 -0
- package/dist/components/Drawer.svelte +383 -0
- package/dist/components/Drawer.svelte.d.ts +39 -0
- package/dist/components/Fab.svelte +486 -0
- package/dist/components/Fab.svelte.d.ts +51 -0
- package/dist/components/FileUploader.svelte +456 -0
- package/dist/components/FileUploader.svelte.d.ts +36 -0
- package/dist/components/Icon.svelte +167 -0
- package/dist/components/Icon.svelte.d.ts +21 -0
- package/dist/components/IconButton.svelte +557 -0
- package/dist/components/IconButton.svelte.d.ts +60 -0
- package/dist/components/ImageUploader.svelte +516 -0
- package/dist/components/ImageUploader.svelte.d.ts +37 -0
- package/dist/components/ImageUploaderPreview.svelte +157 -0
- package/dist/components/ImageUploaderPreview.svelte.d.ts +13 -0
- package/dist/components/Input.svelte +885 -0
- package/dist/components/Input.svelte.d.ts +75 -0
- package/dist/components/LoadingSpinner.svelte +116 -0
- package/dist/components/LoadingSpinner.svelte.d.ts +10 -0
- package/dist/components/Modal.svelte +313 -0
- package/dist/components/Modal.svelte.d.ts +34 -0
- package/dist/components/Pagination.svelte +276 -0
- package/dist/components/Pagination.svelte.d.ts +14 -0
- package/dist/components/Popup.svelte +676 -0
- package/dist/components/Popup.svelte.d.ts +40 -0
- package/dist/components/PopupMenu.svelte +421 -0
- package/dist/components/PopupMenu.svelte.d.ts +24 -0
- package/dist/components/PopupMenuButton.svelte +365 -0
- package/dist/components/PopupMenuButton.svelte.d.ts +42 -0
- package/dist/components/Radio.svelte +548 -0
- package/dist/components/Radio.svelte.d.ts +42 -0
- package/dist/components/RadioGroup.svelte +74 -0
- package/dist/components/RadioGroup.svelte.d.ts +14 -0
- package/dist/components/Select.svelte +479 -0
- package/dist/components/Select.svelte.d.ts +47 -0
- package/dist/components/Slider.svelte +473 -0
- package/dist/components/Slider.svelte.d.ts +46 -0
- package/dist/components/Snackbar.svelte +124 -0
- package/dist/components/Snackbar.svelte.d.ts +9 -0
- package/dist/components/SnackbarItem.svelte +423 -0
- package/dist/components/SnackbarItem.svelte.d.ts +21 -0
- package/dist/components/Switch.svelte +454 -0
- package/dist/components/Switch.svelte.d.ts +40 -0
- package/dist/components/Tab.svelte +193 -0
- package/dist/components/Tab.svelte.d.ts +14 -0
- package/dist/components/TabItem.svelte +140 -0
- package/dist/components/TabItem.svelte.d.ts +17 -0
- package/dist/components/Textarea.svelte +702 -0
- package/dist/components/Textarea.svelte.d.ts +64 -0
- package/dist/components/skeleton/Skeleton.svelte +235 -0
- package/dist/components/skeleton/Skeleton.svelte.d.ts +13 -0
- package/dist/components/skeleton/SkeletonAvatar.svelte +97 -0
- package/dist/components/skeleton/SkeletonAvatar.svelte.d.ts +8 -0
- package/dist/components/skeleton/SkeletonBox.svelte +105 -0
- package/dist/components/skeleton/SkeletonBox.svelte.d.ts +12 -0
- package/dist/components/skeleton/SkeletonButton.svelte +71 -0
- package/dist/components/skeleton/SkeletonButton.svelte.d.ts +8 -0
- package/dist/components/skeleton/SkeletonHeading.svelte +49 -0
- package/dist/components/skeleton/SkeletonHeading.svelte.d.ts +8 -0
- package/dist/components/skeleton/SkeletonMedia.svelte +115 -0
- package/dist/components/skeleton/SkeletonMedia.svelte.d.ts +9 -0
- package/dist/components/skeleton/SkeletonText.svelte +75 -0
- package/dist/components/skeleton/SkeletonText.svelte.d.ts +8 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +43 -0
- package/dist/types/icon.d.ts +4 -0
- package/dist/types/icon.js +2 -0
- package/dist/types/menuItem.d.ts +8 -0
- package/dist/types/menuItem.js +1 -0
- package/dist/types/options.d.ts +6 -0
- package/dist/types/options.js +4 -0
- package/dist/types/skeleton.d.ts +77 -0
- package/dist/types/skeleton.js +19 -0
- package/dist/utils/accessibility.d.ts +48 -0
- package/dist/utils/accessibility.js +87 -0
- package/dist/utils/formatText.d.ts +4 -0
- package/dist/utils/formatText.js +44 -0
- package/dist/utils/mobile.d.ts +9 -0
- package/dist/utils/mobile.js +47 -0
- package/dist/utils/snackbar.svelte.d.ts +51 -0
- package/dist/utils/snackbar.svelte.js +107 -0
- package/dist/utils/style.d.ts +17 -0
- package/dist/utils/style.js +22 -0
- package/package.json +102 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
<!-- Combobox.svelte -->
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
5
|
+
import Input from './Input.svelte';
|
|
6
|
+
import Popup from './Popup.svelte';
|
|
7
|
+
import { announceSelection } from '../utils/accessibility';
|
|
8
|
+
import { t } from '../i18n';
|
|
9
|
+
|
|
10
|
+
// =========================================================================
|
|
11
|
+
// Props, States & Constants
|
|
12
|
+
// =========================================================================
|
|
13
|
+
let {
|
|
14
|
+
// 基本プロパティ
|
|
15
|
+
name,
|
|
16
|
+
value = $bindable(),
|
|
17
|
+
options = [] as string[],
|
|
18
|
+
|
|
19
|
+
// HTML属性系
|
|
20
|
+
inputAttributes,
|
|
21
|
+
id = `combobox-${Math.random().toString(36).substring(2, 15)}`,
|
|
22
|
+
tabindex = null,
|
|
23
|
+
maxlength = null,
|
|
24
|
+
|
|
25
|
+
// スタイル/レイアウト
|
|
26
|
+
customStyle = '',
|
|
27
|
+
variant = 'default',
|
|
28
|
+
focusStyle = 'outline',
|
|
29
|
+
placeholder = '選択してください',
|
|
30
|
+
fullWidth = false,
|
|
31
|
+
minWidth = variant === 'inline' ? null : 120,
|
|
32
|
+
maxWidth = null,
|
|
33
|
+
rounded = false,
|
|
34
|
+
|
|
35
|
+
// 状態/動作
|
|
36
|
+
disabled = false,
|
|
37
|
+
readonly = false,
|
|
38
|
+
required = false,
|
|
39
|
+
filterable = true,
|
|
40
|
+
clearable = false,
|
|
41
|
+
|
|
42
|
+
// 入力イベント
|
|
43
|
+
onchange = () => {}, // No params for type inference
|
|
44
|
+
oninput = () => {}, // No params for type inference
|
|
45
|
+
|
|
46
|
+
// フォーカスイベント
|
|
47
|
+
onfocus = () => {}, // No params for type inference
|
|
48
|
+
onblur = () => {}, // No params for type inference
|
|
49
|
+
|
|
50
|
+
// キーボードイベント
|
|
51
|
+
onkeydown = () => {}, // No params for type inference
|
|
52
|
+
onkeyup = () => {}, // No params for type inference
|
|
53
|
+
|
|
54
|
+
// マウスイベント
|
|
55
|
+
onclick = () => {}, // No params for type inference
|
|
56
|
+
onmousedown = () => {}, // No params for type inference
|
|
57
|
+
onmouseup = () => {}, // No params for type inference
|
|
58
|
+
onmouseenter = () => {}, // No params for type inference
|
|
59
|
+
onmouseleave = () => {}, // No params for type inference
|
|
60
|
+
onmouseover = () => {}, // No params for type inference
|
|
61
|
+
onmouseout = () => {}, // No params for type inference
|
|
62
|
+
oncontextmenu = () => {}, // No params for type inference
|
|
63
|
+
onauxclick = () => {}, // No params for type inference
|
|
64
|
+
|
|
65
|
+
// タッチイベント
|
|
66
|
+
ontouchstart = () => {}, // No params for type inference
|
|
67
|
+
ontouchend = () => {}, // No params for type inference
|
|
68
|
+
ontouchmove = () => {}, // No params for type inference
|
|
69
|
+
ontouchcancel = () => {}, // No params for type inference
|
|
70
|
+
|
|
71
|
+
// ポインターイベント
|
|
72
|
+
onpointerdown = () => {}, // No params for type inference
|
|
73
|
+
onpointerup = () => {}, // No params for type inference
|
|
74
|
+
onpointerenter = () => {}, // No params for type inference
|
|
75
|
+
onpointerleave = () => {}, // No params for type inference
|
|
76
|
+
onpointermove = () => {}, // No params for type inference
|
|
77
|
+
onpointercancel = () => {}, // No params for type inference
|
|
78
|
+
|
|
79
|
+
// その他
|
|
80
|
+
...restProps
|
|
81
|
+
}: {
|
|
82
|
+
// 基本プロパティ
|
|
83
|
+
name?: string;
|
|
84
|
+
value: string | number | null | undefined;
|
|
85
|
+
options: string[];
|
|
86
|
+
|
|
87
|
+
// HTML属性系
|
|
88
|
+
inputAttributes?: HTMLInputAttributes | undefined;
|
|
89
|
+
id?: string | null;
|
|
90
|
+
tabindex?: number | null;
|
|
91
|
+
maxlength?: number | null;
|
|
92
|
+
|
|
93
|
+
// スタイル/レイアウト
|
|
94
|
+
customStyle?: string;
|
|
95
|
+
variant?: 'default' | 'inline';
|
|
96
|
+
focusStyle?: 'background' | 'outline' | 'none';
|
|
97
|
+
placeholder?: string;
|
|
98
|
+
fullWidth?: boolean;
|
|
99
|
+
minWidth?: number | null;
|
|
100
|
+
maxWidth?: number | null;
|
|
101
|
+
rounded?: boolean;
|
|
102
|
+
|
|
103
|
+
// 状態/動作
|
|
104
|
+
disabled?: boolean;
|
|
105
|
+
readonly?: boolean;
|
|
106
|
+
required?: boolean;
|
|
107
|
+
filterable?: boolean;
|
|
108
|
+
clearable?: boolean;
|
|
109
|
+
|
|
110
|
+
// 入力イベント
|
|
111
|
+
onchange?: (value: any) => void;
|
|
112
|
+
oninput?: (value: any) => void;
|
|
113
|
+
|
|
114
|
+
// フォーカスイベント
|
|
115
|
+
onfocus?: Function; // No params for type inference
|
|
116
|
+
onblur?: Function; // No params for type inference
|
|
117
|
+
|
|
118
|
+
// キーボードイベント
|
|
119
|
+
onkeydown?: Function; // No params for type inference
|
|
120
|
+
onkeyup?: Function; // No params for type inference
|
|
121
|
+
|
|
122
|
+
// マウスイベント
|
|
123
|
+
onclick?: Function; // No params for type inference
|
|
124
|
+
onmousedown?: Function; // No params for type inference
|
|
125
|
+
onmouseup?: Function; // No params for type inference
|
|
126
|
+
onmouseenter?: Function; // No params for type inference
|
|
127
|
+
onmouseleave?: Function; // No params for type inference
|
|
128
|
+
onmouseover?: Function; // No params for type inference
|
|
129
|
+
onmouseout?: Function; // No params for type inference
|
|
130
|
+
oncontextmenu?: Function; // No params for type inference
|
|
131
|
+
onauxclick?: Function; // No params for type inference
|
|
132
|
+
|
|
133
|
+
// タッチイベント
|
|
134
|
+
ontouchstart?: Function; // No params for type inference
|
|
135
|
+
ontouchend?: Function; // No params for type inference
|
|
136
|
+
ontouchmove?: Function; // No params for type inference
|
|
137
|
+
ontouchcancel?: Function; // No params for type inference
|
|
138
|
+
|
|
139
|
+
// ポインターイベント
|
|
140
|
+
onpointerdown?: Function; // No params for type inference
|
|
141
|
+
onpointerup?: Function; // No params for type inference
|
|
142
|
+
onpointerenter?: Function; // No params for type inference
|
|
143
|
+
onpointerleave?: Function; // No params for type inference
|
|
144
|
+
onpointermove?: Function; // No params for type inference
|
|
145
|
+
onpointercancel?: Function; // No params for type inference
|
|
146
|
+
|
|
147
|
+
// その他
|
|
148
|
+
[key: string]: any;
|
|
149
|
+
} = $props();
|
|
150
|
+
|
|
151
|
+
let inputValue = $state('');
|
|
152
|
+
let inputRef = $state<any>();
|
|
153
|
+
let listElement = $state<HTMLDivElement>();
|
|
154
|
+
let comboboxElement = $state<HTMLDivElement>();
|
|
155
|
+
let popupRef = $state<any>();
|
|
156
|
+
let highlightedIndex = $state(-1);
|
|
157
|
+
let isFocused = $state(false);
|
|
158
|
+
let comboboxRef = $state<HTMLDivElement>();
|
|
159
|
+
|
|
160
|
+
// 各要素のIDを生成
|
|
161
|
+
const inputId = `${id}-input`;
|
|
162
|
+
const listboxId = `${id}-listbox`;
|
|
163
|
+
|
|
164
|
+
// =========================================================================
|
|
165
|
+
|
|
166
|
+
// =========================================================================
|
|
167
|
+
// $effect
|
|
168
|
+
// =========================================================================
|
|
169
|
+
|
|
170
|
+
// =========================================================================
|
|
171
|
+
// Methods
|
|
172
|
+
// =========================================================================
|
|
173
|
+
// オプションを選択
|
|
174
|
+
const selectOption = (option: string) => {
|
|
175
|
+
value = option;
|
|
176
|
+
inputValue = option;
|
|
177
|
+
popupRef?.close();
|
|
178
|
+
highlightedIndex = -1;
|
|
179
|
+
isFocused = false;
|
|
180
|
+
|
|
181
|
+
// スクリーンリーダーアナウンス
|
|
182
|
+
if (option !== null && option !== undefined) {
|
|
183
|
+
announceSelection(option);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
onchange?.(option);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// input要素のフォーカス/クリック時
|
|
190
|
+
const handleInputFocus = (event: FocusEvent) => {
|
|
191
|
+
if (disabled) return;
|
|
192
|
+
isFocused = true;
|
|
193
|
+
popupRef?.open();
|
|
194
|
+
highlightedIndex = -1;
|
|
195
|
+
onfocus(event);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// 入力変更ハンドラー
|
|
199
|
+
const handleInput = (currentValue: string | number | undefined) => {
|
|
200
|
+
if (disabled || readonly) return;
|
|
201
|
+
const currentInputValue = String(currentValue || '');
|
|
202
|
+
|
|
203
|
+
if (filterable) {
|
|
204
|
+
inputValue = currentInputValue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 入力中はvalueを更新しない(入力値をそのまま保持)
|
|
208
|
+
highlightedIndex = -1;
|
|
209
|
+
popupRef?.open();
|
|
210
|
+
oninput?.(currentInputValue);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// 値確定ハンドラー
|
|
214
|
+
const handleChange = (currentValue: string | number | undefined) => {
|
|
215
|
+
if (disabled || readonly) return;
|
|
216
|
+
|
|
217
|
+
value = currentValue;
|
|
218
|
+
onchange?.(value);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const handleClick = (event: MouseEvent) => {
|
|
222
|
+
if (disabled) return;
|
|
223
|
+
// クリック時にもポップアップを開く
|
|
224
|
+
if (!isFocused) {
|
|
225
|
+
isFocused = true;
|
|
226
|
+
popupRef?.open();
|
|
227
|
+
highlightedIndex = -1;
|
|
228
|
+
}
|
|
229
|
+
onclick?.(event);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
233
|
+
if (disabled) return;
|
|
234
|
+
onkeydown(event);
|
|
235
|
+
|
|
236
|
+
switch (event.key) {
|
|
237
|
+
case 'ArrowDown':
|
|
238
|
+
event?.preventDefault?.();
|
|
239
|
+
if (!isFocused) {
|
|
240
|
+
isFocused = true;
|
|
241
|
+
popupRef?.open();
|
|
242
|
+
}
|
|
243
|
+
highlightedIndex = Math.min(highlightedIndex + 1, filteredOptions.length - 1);
|
|
244
|
+
break;
|
|
245
|
+
case 'ArrowUp':
|
|
246
|
+
event?.preventDefault?.();
|
|
247
|
+
if (!isFocused) {
|
|
248
|
+
isFocused = true;
|
|
249
|
+
popupRef?.open();
|
|
250
|
+
}
|
|
251
|
+
highlightedIndex = Math.max(highlightedIndex - 1, -1);
|
|
252
|
+
break;
|
|
253
|
+
case 'Enter':
|
|
254
|
+
event?.preventDefault?.();
|
|
255
|
+
if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
|
|
256
|
+
selectOption(filteredOptions[highlightedIndex]);
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
case 'Escape':
|
|
260
|
+
event?.preventDefault?.();
|
|
261
|
+
inputValue = '';
|
|
262
|
+
highlightedIndex = -1;
|
|
263
|
+
popupRef?.close();
|
|
264
|
+
isFocused = false;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const handleKeyup = (event: KeyboardEvent) => {
|
|
270
|
+
if (disabled) return;
|
|
271
|
+
onkeyup(event);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// マウスイベント
|
|
275
|
+
const handleMouseDown = (event: MouseEvent) => {
|
|
276
|
+
if (disabled) return;
|
|
277
|
+
onmousedown?.(event);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const handleMouseUp = (event: MouseEvent) => {
|
|
281
|
+
if (disabled) return;
|
|
282
|
+
onmouseup?.(event);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const handleMouseEnter = (event: MouseEvent) => {
|
|
286
|
+
if (disabled) return;
|
|
287
|
+
onmouseenter?.(event);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const handleMouseLeave = (event: MouseEvent) => {
|
|
291
|
+
if (disabled) return;
|
|
292
|
+
onmouseleave?.(event);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const handleMouseOver = (event: MouseEvent) => {
|
|
296
|
+
if (disabled) return;
|
|
297
|
+
onmouseover?.(event);
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const handleMouseOut = (event: MouseEvent) => {
|
|
301
|
+
if (disabled) return;
|
|
302
|
+
onmouseout?.(event);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const handleContextMenu = (event: MouseEvent) => {
|
|
306
|
+
if (disabled) return;
|
|
307
|
+
oncontextmenu?.(event);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const handleAuxClick = (event: MouseEvent) => {
|
|
311
|
+
if (disabled) return;
|
|
312
|
+
onauxclick?.(event);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// タッチイベント
|
|
316
|
+
const handleTouchStart = (event: TouchEvent) => {
|
|
317
|
+
if (disabled) return;
|
|
318
|
+
ontouchstart?.(event);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const handleTouchEnd = (event: TouchEvent) => {
|
|
322
|
+
if (disabled) return;
|
|
323
|
+
ontouchend?.(event);
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
const handleTouchMove = (event: TouchEvent) => {
|
|
327
|
+
if (disabled) return;
|
|
328
|
+
ontouchmove?.(event);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const handleTouchCancel = (event: TouchEvent) => {
|
|
332
|
+
if (disabled) return;
|
|
333
|
+
ontouchcancel?.(event);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// ポインターイベント
|
|
337
|
+
const handlePointerDown = (event: PointerEvent) => {
|
|
338
|
+
if (disabled) return;
|
|
339
|
+
onpointerdown?.(event);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const handlePointerUp = (event: PointerEvent) => {
|
|
343
|
+
if (disabled) return;
|
|
344
|
+
onpointerup?.(event);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const handlePointerEnter = (event: PointerEvent) => {
|
|
348
|
+
if (disabled) return;
|
|
349
|
+
onpointerenter?.(event);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const handlePointerLeave = (event: PointerEvent) => {
|
|
353
|
+
if (disabled) return;
|
|
354
|
+
onpointerleave?.(event);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const handlePointerMove = (event: PointerEvent) => {
|
|
358
|
+
if (disabled) return;
|
|
359
|
+
onpointermove?.(event);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const handlePointerCancel = (event: PointerEvent) => {
|
|
363
|
+
if (disabled) return;
|
|
364
|
+
onpointercancel?.(event);
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Popup が閉じられたときの処理
|
|
368
|
+
const handlePopupClose = () => {
|
|
369
|
+
isFocused = false;
|
|
370
|
+
highlightedIndex = -1;
|
|
371
|
+
};
|
|
372
|
+
const handleBlur = (event: FocusEvent) => {
|
|
373
|
+
if (disabled) return;
|
|
374
|
+
// フォーカスが外れたらisFocusedをfalseにする
|
|
375
|
+
isFocused = false;
|
|
376
|
+
|
|
377
|
+
// 少し遅延させてからポップアップを閉じる(オプション選択時の処理のため)
|
|
378
|
+
setTimeout(() => {
|
|
379
|
+
popupRef?.close();
|
|
380
|
+
}, 100);
|
|
381
|
+
onblur(event);
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// =========================================================================
|
|
385
|
+
// $derived
|
|
386
|
+
// =========================================================================
|
|
387
|
+
|
|
388
|
+
// フィルタリングされたオプション
|
|
389
|
+
const filteredOptions = $derived.by(() => {
|
|
390
|
+
if (!filterable || !inputValue) return options;
|
|
391
|
+
return options.filter((option) => option.toLowerCase().includes(inputValue.toLowerCase()));
|
|
392
|
+
});
|
|
393
|
+
</script>
|
|
394
|
+
|
|
395
|
+
<div
|
|
396
|
+
bind:this={comboboxElement}
|
|
397
|
+
{id}
|
|
398
|
+
class="combobox"
|
|
399
|
+
class:combobox--full-width={fullWidth}
|
|
400
|
+
style="max-width: {maxWidth}px; min-width: {minWidth}px"
|
|
401
|
+
role="combobox"
|
|
402
|
+
aria-expanded={!!popupRef}
|
|
403
|
+
aria-controls={listboxId}
|
|
404
|
+
aria-haspopup="listbox"
|
|
405
|
+
aria-owns={listboxId}
|
|
406
|
+
data-testid="combobox"
|
|
407
|
+
>
|
|
408
|
+
<!-- Inputコンポーネントを使用 -->
|
|
409
|
+
<Input
|
|
410
|
+
bind:this={inputRef}
|
|
411
|
+
bind:value={inputValue}
|
|
412
|
+
{name}
|
|
413
|
+
id={inputId}
|
|
414
|
+
{customStyle}
|
|
415
|
+
{variant}
|
|
416
|
+
{focusStyle}
|
|
417
|
+
{placeholder}
|
|
418
|
+
{fullWidth}
|
|
419
|
+
{disabled}
|
|
420
|
+
{readonly}
|
|
421
|
+
{required}
|
|
422
|
+
{clearable}
|
|
423
|
+
rightIcon={variant !== 'inline' ? 'arrow_drop_down' : undefined}
|
|
424
|
+
{tabindex}
|
|
425
|
+
{maxlength}
|
|
426
|
+
{rounded}
|
|
427
|
+
onfocus={handleInputFocus}
|
|
428
|
+
onblur={handleBlur}
|
|
429
|
+
onclick={handleClick}
|
|
430
|
+
oninput={handleInput}
|
|
431
|
+
onchange={handleChange}
|
|
432
|
+
onkeydown={handleKeydown}
|
|
433
|
+
onkeyup={handleKeyup}
|
|
434
|
+
onmousedown={handleMouseDown}
|
|
435
|
+
onmouseup={handleMouseUp}
|
|
436
|
+
onmouseenter={handleMouseEnter}
|
|
437
|
+
onmouseleave={handleMouseLeave}
|
|
438
|
+
onmouseover={handleMouseOver}
|
|
439
|
+
onmouseout={handleMouseOut}
|
|
440
|
+
oncontextmenu={handleContextMenu}
|
|
441
|
+
onauxclick={handleAuxClick}
|
|
442
|
+
ontouchstart={handleTouchStart}
|
|
443
|
+
ontouchend={handleTouchEnd}
|
|
444
|
+
ontouchmove={handleTouchMove}
|
|
445
|
+
ontouchcancel={handleTouchCancel}
|
|
446
|
+
onpointerdown={handlePointerDown}
|
|
447
|
+
onpointerup={handlePointerUp}
|
|
448
|
+
onpointerenter={handlePointerEnter}
|
|
449
|
+
onpointerleave={handlePointerLeave}
|
|
450
|
+
onpointermove={handlePointerMove}
|
|
451
|
+
onpointercancel={handlePointerCancel}
|
|
452
|
+
{inputAttributes}
|
|
453
|
+
{...restProps}
|
|
454
|
+
role="textbox"
|
|
455
|
+
aria-autocomplete="list"
|
|
456
|
+
aria-controls={listboxId}
|
|
457
|
+
/>
|
|
458
|
+
<!-- オプションリスト -->
|
|
459
|
+
<Popup
|
|
460
|
+
bind:this={popupRef}
|
|
461
|
+
anchorElement={comboboxElement}
|
|
462
|
+
position="bottom-left"
|
|
463
|
+
focusTrap={false}
|
|
464
|
+
onClose={handlePopupClose}
|
|
465
|
+
margin={4}
|
|
466
|
+
allowRepositioning={false}
|
|
467
|
+
>
|
|
468
|
+
<div
|
|
469
|
+
bind:this={listElement}
|
|
470
|
+
id={listboxId}
|
|
471
|
+
class="combobox__options"
|
|
472
|
+
role="listbox"
|
|
473
|
+
aria-label={t('combobox.optionsList')}
|
|
474
|
+
aria-labelledby={inputId}
|
|
475
|
+
>
|
|
476
|
+
{#each filteredOptions as option, index}
|
|
477
|
+
<div role="presentation">
|
|
478
|
+
<button
|
|
479
|
+
type="button"
|
|
480
|
+
class="combobox__option"
|
|
481
|
+
class:combobox__option--highlighted={index === highlightedIndex}
|
|
482
|
+
class:combobox__option--selected={option === value}
|
|
483
|
+
role="option"
|
|
484
|
+
aria-selected={option === value}
|
|
485
|
+
onmousedown={(event) => {
|
|
486
|
+
event?.preventDefault?.();
|
|
487
|
+
event?.stopPropagation?.();
|
|
488
|
+
selectOption(option);
|
|
489
|
+
}}
|
|
490
|
+
onmouseenter={() => (highlightedIndex = index)}
|
|
491
|
+
>
|
|
492
|
+
{option}
|
|
493
|
+
</button>
|
|
494
|
+
</div>
|
|
495
|
+
{:else}
|
|
496
|
+
<div class="combobox__no-options">該当するオプションがありません</div>
|
|
497
|
+
{/each}
|
|
498
|
+
</div>
|
|
499
|
+
</Popup>
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<style>
|
|
503
|
+
/* =============================================
|
|
504
|
+
* 基本構造・レイアウト
|
|
505
|
+
* ============================================= */
|
|
506
|
+
.combobox {
|
|
507
|
+
display: inline-block;
|
|
508
|
+
position: relative;
|
|
509
|
+
width: auto;
|
|
510
|
+
max-width: 100%;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.combobox--full-width {
|
|
514
|
+
width: 100%;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/* =============================================
|
|
518
|
+
* オプションリスト
|
|
519
|
+
* ============================================= */
|
|
520
|
+
.combobox__options {
|
|
521
|
+
min-width: var(--svelte-ui-combobox-min-width);
|
|
522
|
+
width: max-content;
|
|
523
|
+
max-width: var(--svelte-ui-combobox-max-width);
|
|
524
|
+
background: var(--svelte-ui-combobox-bg);
|
|
525
|
+
border-radius: var(--svelte-ui-combobox-border-radius);
|
|
526
|
+
max-height: var(--svelte-ui-combobox-options-max-height);
|
|
527
|
+
overflow-y: auto;
|
|
528
|
+
margin: 0;
|
|
529
|
+
padding: 0;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.combobox__option {
|
|
533
|
+
width: 100%;
|
|
534
|
+
padding: var(--svelte-ui-combobox-option-padding);
|
|
535
|
+
border: none;
|
|
536
|
+
background-color: transparent;
|
|
537
|
+
text-align: left;
|
|
538
|
+
cursor: pointer;
|
|
539
|
+
font-size: inherit;
|
|
540
|
+
color: var(--svelte-ui-combobox-text-color);
|
|
541
|
+
transition: background-color var(--svelte-ui-transition-duration) ease;
|
|
542
|
+
|
|
543
|
+
@media (hover: hover) {
|
|
544
|
+
&:hover,
|
|
545
|
+
&.combobox__option--highlighted {
|
|
546
|
+
background-color: var(--svelte-ui-combobox-option-hover-bg);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
&.combobox__option--highlighted {
|
|
551
|
+
background-color: var(--svelte-ui-combobox-option-hover-bg);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
&.combobox__option--selected {
|
|
555
|
+
background-color: var(--svelte-ui-combobox-option-selected-bg);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
&.combobox__option--selected.combobox__option--highlighted {
|
|
559
|
+
background-color: var(--svelte-ui-combobox-option-selected-bg);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
&:focus,
|
|
563
|
+
&:focus-visible {
|
|
564
|
+
outline: var(--svelte-ui-focus-outline-inner);
|
|
565
|
+
outline-offset: var(--svelte-ui-focus-outline-offset-inner);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.combobox__no-options {
|
|
570
|
+
padding: var(--svelte-ui-combobox-option-padding);
|
|
571
|
+
font-size: inherit;
|
|
572
|
+
color: var(--svelte-ui-combobox-no-options-color);
|
|
573
|
+
font-style: italic;
|
|
574
|
+
list-style: none;
|
|
575
|
+
}
|
|
576
|
+
</style>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
name?: string;
|
|
4
|
+
value: string | number | null | undefined;
|
|
5
|
+
options: string[];
|
|
6
|
+
inputAttributes?: HTMLInputAttributes | undefined;
|
|
7
|
+
id?: string | null;
|
|
8
|
+
tabindex?: number | null;
|
|
9
|
+
maxlength?: number | null;
|
|
10
|
+
customStyle?: string;
|
|
11
|
+
variant?: 'default' | 'inline';
|
|
12
|
+
focusStyle?: 'background' | 'outline' | 'none';
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
fullWidth?: boolean;
|
|
15
|
+
minWidth?: number | null;
|
|
16
|
+
maxWidth?: number | null;
|
|
17
|
+
rounded?: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
readonly?: boolean;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
filterable?: boolean;
|
|
22
|
+
clearable?: boolean;
|
|
23
|
+
onchange?: (value: any) => void;
|
|
24
|
+
oninput?: (value: any) => void;
|
|
25
|
+
onfocus?: Function;
|
|
26
|
+
onblur?: Function;
|
|
27
|
+
onkeydown?: Function;
|
|
28
|
+
onkeyup?: Function;
|
|
29
|
+
onclick?: Function;
|
|
30
|
+
onmousedown?: Function;
|
|
31
|
+
onmouseup?: Function;
|
|
32
|
+
onmouseenter?: Function;
|
|
33
|
+
onmouseleave?: Function;
|
|
34
|
+
onmouseover?: Function;
|
|
35
|
+
onmouseout?: Function;
|
|
36
|
+
oncontextmenu?: Function;
|
|
37
|
+
onauxclick?: Function;
|
|
38
|
+
ontouchstart?: Function;
|
|
39
|
+
ontouchend?: Function;
|
|
40
|
+
ontouchmove?: Function;
|
|
41
|
+
ontouchcancel?: Function;
|
|
42
|
+
onpointerdown?: Function;
|
|
43
|
+
onpointerup?: Function;
|
|
44
|
+
onpointerenter?: Function;
|
|
45
|
+
onpointerleave?: Function;
|
|
46
|
+
onpointermove?: Function;
|
|
47
|
+
onpointercancel?: Function;
|
|
48
|
+
[key: string]: any;
|
|
49
|
+
};
|
|
50
|
+
declare const Combobox: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
51
|
+
type Combobox = ReturnType<typeof Combobox>;
|
|
52
|
+
export default Combobox;
|