@14ch/svelte-ui 0.0.5 → 0.0.7
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/dist/assets/styles/variables.scss +20 -1
- package/dist/components/Button.svelte +1 -1
- package/dist/components/ColorPicker.svelte +6 -6
- package/dist/components/ColorPicker.svelte.d.ts +1 -1
- package/dist/components/FileUploader.svelte +4 -4
- package/dist/components/FileUploader.svelte.d.ts +2 -2
- package/dist/components/ImageUploader.svelte +3 -3
- package/dist/components/ImageUploader.svelte.d.ts +2 -2
- package/dist/components/Input.svelte +29 -27
- package/dist/components/Input.svelte.d.ts +2 -4
- package/dist/components/SegmentedControl.svelte +622 -0
- package/dist/components/SegmentedControl.svelte.d.ts +49 -0
- package/dist/components/Slider.svelte +1 -1
- package/dist/components/Slider.svelte.d.ts +1 -1
- package/dist/components/Switch.svelte +1 -1
- package/dist/components/Switch.svelte.d.ts +1 -1
- package/dist/components/Textarea.svelte +5 -5
- package/dist/components/Textarea.svelte.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/types/segmentedControlItem.d.ts +13 -0
- package/dist/types/segmentedControlItem.js +1 -0
- package/package.json +1 -1
|
@@ -341,6 +341,25 @@
|
|
|
341
341
|
--svelte-ui-iconbutton-hover-overlay: var(--svelte-ui-hover-overlay);
|
|
342
342
|
--svelte-ui-iconbutton-focus-color: var(--svelte-ui-focus-color);
|
|
343
343
|
|
|
344
|
+
/* SegmentedControl */
|
|
345
|
+
--svelte-ui-segmented-control-base-padding: 4px;
|
|
346
|
+
--svelte-ui-segmented-control-base-border-radius: var(--svelte-ui-border-radius);
|
|
347
|
+
--svelte-ui-segmented-control-gap: 4px;
|
|
348
|
+
--svelte-ui-segmented-control-button-radius: calc(var(--svelte-ui-border-radius) - 1px);
|
|
349
|
+
--svelte-ui-segmented-control-button-height-sm: 24px;
|
|
350
|
+
--svelte-ui-segmented-control-button-height: 32px;
|
|
351
|
+
--svelte-ui-segmented-control-button-height-lg: 40px;
|
|
352
|
+
--svelte-ui-segmented-control-font-size-sm: 12px;
|
|
353
|
+
--svelte-ui-segmented-control-font-size: 14px;
|
|
354
|
+
--svelte-ui-segmented-control-font-size-lg: 16px;
|
|
355
|
+
--svelte-ui-segmented-control-button-padding-sm: 4px 8px;
|
|
356
|
+
--svelte-ui-segmented-control-button-padding: 6px 12px;
|
|
357
|
+
--svelte-ui-segmented-control-button-padding-lg: 8px 16px;
|
|
358
|
+
--svelte-ui-segmented-control-base-bg: var(--svelte-ui-surface-accent-color);
|
|
359
|
+
--svelte-ui-segmented-control-selected-bg: var(--svelte-ui-primary-color);
|
|
360
|
+
--svelte-ui-segmented-control-selected-text-color: var(--svelte-ui-text-on-filled-color);
|
|
361
|
+
--svelte-ui-segmented-control-hover-overlay: var(--svelte-ui-hover-overlay);
|
|
362
|
+
|
|
344
363
|
/* Input */
|
|
345
364
|
--svelte-ui-input-height: var(--svelte-ui-form-height);
|
|
346
365
|
--svelte-ui-input-padding: var(--svelte-ui-form-padding);
|
|
@@ -490,7 +509,7 @@
|
|
|
490
509
|
/* ColorPicker */
|
|
491
510
|
--svelte-ui-colorpicker-trigger-size: 28px;
|
|
492
511
|
--svelte-ui-colorpicker-text-padding-left: 40px;
|
|
493
|
-
--svelte-ui-colorpicker-trigger-border-radius: calc(var(--svelte-ui-border-radius) -
|
|
512
|
+
--svelte-ui-colorpicker-trigger-border-radius: calc(var(--svelte-ui-border-radius) - 1px);
|
|
494
513
|
--svelte-ui-colorpicker-trigger-border-style: dashed;
|
|
495
514
|
--svelte-ui-colorpicker-trigger-border-width: 1px;
|
|
496
515
|
--svelte-ui-colorpicker-trigger-offset: 4px;
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
...restProps
|
|
73
73
|
}: {
|
|
74
74
|
// 基本プロパティ
|
|
75
|
-
value
|
|
75
|
+
value: string;
|
|
76
76
|
|
|
77
77
|
// HTML属性系
|
|
78
78
|
id?: string;
|
|
@@ -167,10 +167,6 @@
|
|
|
167
167
|
// Methods
|
|
168
168
|
// =========================================================================
|
|
169
169
|
|
|
170
|
-
const handleSubmit = (event: SubmitEvent) => {
|
|
171
|
-
event?.preventDefault?.();
|
|
172
|
-
handleChange();
|
|
173
|
-
};
|
|
174
170
|
|
|
175
171
|
const handleChange = (event?: Event): void => {
|
|
176
172
|
// 空文字列の場合はそのまま処理
|
|
@@ -207,6 +203,11 @@
|
|
|
207
203
|
onclick?.(event);
|
|
208
204
|
};
|
|
209
205
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
206
|
+
// Enterキーで色の変更を確定
|
|
207
|
+
if (event.key === 'Enter' && !disabled && !readonly) {
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
handleChange();
|
|
210
|
+
}
|
|
210
211
|
if (disabled) return;
|
|
211
212
|
onkeydown(event);
|
|
212
213
|
};
|
|
@@ -361,7 +362,6 @@
|
|
|
361
362
|
onpointerleave={handlePointerLeave}
|
|
362
363
|
onpointermove={handlePointerMove}
|
|
363
364
|
onpointercancel={handlePointerCancel}
|
|
364
|
-
onsubmit={handleSubmit}
|
|
365
365
|
{...restProps}
|
|
366
366
|
/>
|
|
367
367
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
2
|
import type { IconVariant } from '../types/icon';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
|
-
value
|
|
4
|
+
value: string;
|
|
5
5
|
id?: string;
|
|
6
6
|
inputAttributes?: HTMLInputAttributes | undefined;
|
|
7
7
|
customStyle?: string;
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
onpointerleave = () => {} // No params for type inference
|
|
62
62
|
}: {
|
|
63
63
|
// 基本プロパティ
|
|
64
|
-
value: FileList | undefined;
|
|
64
|
+
value: FileList | null | undefined;
|
|
65
65
|
multiple?: boolean;
|
|
66
66
|
maxFileSize?: number;
|
|
67
67
|
placeholder?: string;
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
removeFileAriaLabel?: string;
|
|
87
87
|
|
|
88
88
|
// 入力イベント
|
|
89
|
-
onchange?: (value: FileList | null) => void;
|
|
89
|
+
onchange?: (value: FileList | null | undefined) => void;
|
|
90
90
|
|
|
91
91
|
// フォーカスイベント
|
|
92
92
|
onfocus?: Function; // No params for type inference
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
dt.items.add(value[i]);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
value = dt.files.length > 0 ? dt.files :
|
|
190
|
+
value = dt.files.length > 0 ? dt.files : null;
|
|
191
191
|
};
|
|
192
192
|
|
|
193
193
|
const validateFile = (file: File): boolean => {
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
export const reset = () => {
|
|
241
241
|
if (fileInputRef) {
|
|
242
242
|
fileInputRef.value = '';
|
|
243
|
-
value =
|
|
243
|
+
value = null;
|
|
244
244
|
errorMessage = '';
|
|
245
245
|
}
|
|
246
246
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
value: FileList | undefined;
|
|
3
|
+
value: FileList | null | undefined;
|
|
4
4
|
multiple?: boolean;
|
|
5
5
|
maxFileSize?: number;
|
|
6
6
|
placeholder?: string;
|
|
@@ -17,7 +17,7 @@ type $$ComponentProps = {
|
|
|
17
17
|
iconOpticalSize?: IconOpticalSize;
|
|
18
18
|
iconVariant?: IconVariant;
|
|
19
19
|
removeFileAriaLabel?: string;
|
|
20
|
-
onchange?: (value: FileList | null) => void;
|
|
20
|
+
onchange?: (value: FileList | null | undefined) => void;
|
|
21
21
|
onfocus?: Function;
|
|
22
22
|
onblur?: Function;
|
|
23
23
|
onkeydown?: Function;
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
onpointerleave = () => {} // No params for type inference
|
|
67
67
|
}: {
|
|
68
68
|
// 基本プロパティ
|
|
69
|
-
value: FileList | undefined;
|
|
69
|
+
value: FileList | null | undefined;
|
|
70
70
|
multiple?: boolean;
|
|
71
71
|
maxFileSize?: number;
|
|
72
72
|
placeholder?: string;
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
removeFileAriaLabel?: string;
|
|
94
94
|
|
|
95
95
|
// 入力イベント
|
|
96
|
-
onchange?: (value: FileList | null) => void;
|
|
96
|
+
onchange?: (value: FileList | null | undefined) => void;
|
|
97
97
|
|
|
98
98
|
// フォーカスイベント
|
|
99
99
|
onfocus?: Function; // No params for type inference
|
|
@@ -295,7 +295,7 @@
|
|
|
295
295
|
export const reset = () => {
|
|
296
296
|
if (fileInputRef) {
|
|
297
297
|
fileInputRef.value = '';
|
|
298
|
-
value =
|
|
298
|
+
value = null;
|
|
299
299
|
errorMessage = '';
|
|
300
300
|
cleanupObjectUrls();
|
|
301
301
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
value: FileList | undefined;
|
|
3
|
+
value: FileList | null | undefined;
|
|
4
4
|
multiple?: boolean;
|
|
5
5
|
maxFileSize?: number;
|
|
6
6
|
placeholder?: string;
|
|
@@ -19,7 +19,7 @@ type $$ComponentProps = {
|
|
|
19
19
|
iconOpticalSize?: IconOpticalSize;
|
|
20
20
|
iconVariant?: IconVariant;
|
|
21
21
|
removeFileAriaLabel?: string;
|
|
22
|
-
onchange?: (value: FileList | null) => void;
|
|
22
|
+
onchange?: (value: FileList | null | undefined) => void;
|
|
23
23
|
onfocus?: Function;
|
|
24
24
|
onblur?: Function;
|
|
25
25
|
onkeydown?: Function;
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
step = null,
|
|
29
29
|
size = null,
|
|
30
30
|
autocomplete = null,
|
|
31
|
+
spellcheck = null,
|
|
31
32
|
inputAttributes,
|
|
32
33
|
|
|
33
34
|
// スタイル/レイアウト
|
|
@@ -94,11 +95,6 @@
|
|
|
94
95
|
// 入力イベント
|
|
95
96
|
onchange = () => {}, // No params for type inference
|
|
96
97
|
oninput = () => {}, // No params for type inference
|
|
97
|
-
onsubmit = () => {}, // No params for type inference
|
|
98
|
-
|
|
99
|
-
// IMEイベント
|
|
100
|
-
oncompositionstart = () => {}, // No params for type inference
|
|
101
|
-
oncompositionend = () => {}, // No params for type inference
|
|
102
98
|
|
|
103
99
|
// アイコンイベント
|
|
104
100
|
onRightIconClick,
|
|
@@ -109,7 +105,7 @@
|
|
|
109
105
|
}: {
|
|
110
106
|
// 基本プロパティ
|
|
111
107
|
name?: string;
|
|
112
|
-
value: string | number
|
|
108
|
+
value: string | number;
|
|
113
109
|
|
|
114
110
|
// HTML属性系
|
|
115
111
|
id?: string | null;
|
|
@@ -122,6 +118,7 @@
|
|
|
122
118
|
step?: number | null;
|
|
123
119
|
size?: number | null;
|
|
124
120
|
autocomplete?: HTMLInputElement['autocomplete'] | null;
|
|
121
|
+
spellcheck?: boolean | null;
|
|
125
122
|
inputAttributes?: HTMLInputAttributes | undefined;
|
|
126
123
|
|
|
127
124
|
// スタイル/レイアウト
|
|
@@ -188,11 +185,6 @@
|
|
|
188
185
|
// 入力イベント
|
|
189
186
|
onchange?: (value: any) => void;
|
|
190
187
|
oninput?: (value: any) => void;
|
|
191
|
-
onsubmit?: (value: any) => void;
|
|
192
|
-
|
|
193
|
-
// IMEイベント
|
|
194
|
-
oncompositionstart?: Function; // No params for type inference
|
|
195
|
-
oncompositionend?: Function; // No params for type inference
|
|
196
188
|
|
|
197
189
|
// アイコンイベント
|
|
198
190
|
onRightIconClick?: Function; // No params for type inference
|
|
@@ -204,6 +196,7 @@
|
|
|
204
196
|
|
|
205
197
|
let ref: HTMLInputElement | undefined = $state();
|
|
206
198
|
let isFocused: boolean = $state(false);
|
|
199
|
+
let isComposing: boolean = $state(false);
|
|
207
200
|
|
|
208
201
|
// =========================================================================
|
|
209
202
|
// Methods
|
|
@@ -212,8 +205,8 @@
|
|
|
212
205
|
const clear = (): void => {
|
|
213
206
|
if (disabled || readonly) return;
|
|
214
207
|
ref?.focus();
|
|
215
|
-
|
|
216
|
-
|
|
208
|
+
value = '';
|
|
209
|
+
onchange?.('');
|
|
217
210
|
};
|
|
218
211
|
|
|
219
212
|
export const focus = () => {
|
|
@@ -241,6 +234,10 @@
|
|
|
241
234
|
|
|
242
235
|
// キーボードイベント
|
|
243
236
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
237
|
+
// Enterキーで入力確定(blur)する(IME変換中は除く)
|
|
238
|
+
if (event.key === 'Enter' && !disabled && !readonly && !isComposing) {
|
|
239
|
+
ref?.blur();
|
|
240
|
+
}
|
|
244
241
|
onkeydown?.(event);
|
|
245
242
|
};
|
|
246
243
|
|
|
@@ -249,12 +246,6 @@
|
|
|
249
246
|
};
|
|
250
247
|
|
|
251
248
|
// 入力イベント
|
|
252
|
-
const handleSubmit = (event: SubmitEvent) => {
|
|
253
|
-
if (disabled || readonly) return;
|
|
254
|
-
event?.preventDefault?.();
|
|
255
|
-
ref?.blur();
|
|
256
|
-
onsubmit?.(value);
|
|
257
|
-
};
|
|
258
249
|
|
|
259
250
|
const handleChange = () => {
|
|
260
251
|
if (disabled || readonly) return;
|
|
@@ -364,11 +355,19 @@
|
|
|
364
355
|
onpointercancel?.(event);
|
|
365
356
|
};
|
|
366
357
|
|
|
358
|
+
// IMEイベント
|
|
359
|
+
const handleCompositionStart = () => {
|
|
360
|
+
isComposing = true;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const handleCompositionEnd = () => {
|
|
364
|
+
isComposing = false;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
367
|
// =========================================================================
|
|
368
368
|
// $derived
|
|
369
369
|
// =========================================================================
|
|
370
370
|
const getDisplayValue = (): string => {
|
|
371
|
-
if (value === undefined) return '';
|
|
372
371
|
if (type === 'number' && typeof value === 'number') {
|
|
373
372
|
return value.toLocaleString();
|
|
374
373
|
}
|
|
@@ -404,8 +403,8 @@
|
|
|
404
403
|
{getDisplayValue()}
|
|
405
404
|
</div>
|
|
406
405
|
{/if}
|
|
407
|
-
<!--
|
|
408
|
-
<
|
|
406
|
+
<!-- 入力用要素 -->
|
|
407
|
+
<div class="input__wrapper">
|
|
409
408
|
<input
|
|
410
409
|
{id}
|
|
411
410
|
{name}
|
|
@@ -425,6 +424,7 @@
|
|
|
425
424
|
{required}
|
|
426
425
|
{tabindex}
|
|
427
426
|
{size}
|
|
427
|
+
{spellcheck}
|
|
428
428
|
onchange={handleChange}
|
|
429
429
|
oninput={handleInput}
|
|
430
430
|
onfocus={handleFocus}
|
|
@@ -450,10 +450,12 @@
|
|
|
450
450
|
onpointerleave={handlePointerLeave}
|
|
451
451
|
onpointermove={handlePointerMove}
|
|
452
452
|
onpointercancel={handlePointerCancel}
|
|
453
|
+
oncompositionstart={handleCompositionStart}
|
|
454
|
+
oncompositionend={handleCompositionEnd}
|
|
453
455
|
{...inputAttributes}
|
|
454
456
|
{...restProps}
|
|
455
457
|
/>
|
|
456
|
-
</
|
|
458
|
+
</div>
|
|
457
459
|
<!-- クリアボタン -->
|
|
458
460
|
{#if clearable && !disabled && !readonly}
|
|
459
461
|
<div class="input__clear-button">
|
|
@@ -544,7 +546,7 @@
|
|
|
544
546
|
height: inherit;
|
|
545
547
|
}
|
|
546
548
|
|
|
547
|
-
|
|
549
|
+
.input__wrapper {
|
|
548
550
|
padding: inherit;
|
|
549
551
|
border: none;
|
|
550
552
|
font-size: inherit;
|
|
@@ -686,7 +688,7 @@
|
|
|
686
688
|
* デザインバリアント:default
|
|
687
689
|
* ============================================= */
|
|
688
690
|
.input:not(.input--inline) {
|
|
689
|
-
|
|
691
|
+
.input__wrapper {
|
|
690
692
|
position: static;
|
|
691
693
|
opacity: 1;
|
|
692
694
|
}
|
|
@@ -747,7 +749,7 @@
|
|
|
747
749
|
text-align: right;
|
|
748
750
|
}
|
|
749
751
|
|
|
750
|
-
|
|
752
|
+
.input__wrapper {
|
|
751
753
|
position: absolute;
|
|
752
754
|
top: 0;
|
|
753
755
|
left: 0;
|
|
@@ -788,7 +790,7 @@
|
|
|
788
790
|
opacity: 0;
|
|
789
791
|
}
|
|
790
792
|
|
|
791
|
-
|
|
793
|
+
.input__wrapper {
|
|
792
794
|
opacity: 1;
|
|
793
795
|
}
|
|
794
796
|
}
|
|
@@ -2,7 +2,7 @@ import type { HTMLInputAttributes } from 'svelte/elements';
|
|
|
2
2
|
import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
name?: string;
|
|
5
|
-
value: string | number
|
|
5
|
+
value: string | number;
|
|
6
6
|
id?: string | null;
|
|
7
7
|
type?: 'text' | 'password' | 'email' | 'tel' | 'url' | 'number';
|
|
8
8
|
tabindex?: number | null;
|
|
@@ -13,6 +13,7 @@ type $$ComponentProps = {
|
|
|
13
13
|
step?: number | null;
|
|
14
14
|
size?: number | null;
|
|
15
15
|
autocomplete?: HTMLInputElement['autocomplete'] | null;
|
|
16
|
+
spellcheck?: boolean | null;
|
|
16
17
|
inputAttributes?: HTMLInputAttributes | undefined;
|
|
17
18
|
inline?: boolean;
|
|
18
19
|
focusStyle?: 'background' | 'outline' | 'none';
|
|
@@ -61,9 +62,6 @@ type $$ComponentProps = {
|
|
|
61
62
|
onpointercancel?: Function;
|
|
62
63
|
onchange?: (value: any) => void;
|
|
63
64
|
oninput?: (value: any) => void;
|
|
64
|
-
onsubmit?: (value: any) => void;
|
|
65
|
-
oncompositionstart?: Function;
|
|
66
|
-
oncompositionend?: Function;
|
|
67
65
|
onRightIconClick?: Function;
|
|
68
66
|
onLeftIconClick?: Function;
|
|
69
67
|
[key: string]: any;
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
<!-- SegmentedControl.svelte -->
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import Icon from './Icon.svelte';
|
|
5
|
+
import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
|
|
6
|
+
import type { SegmentedControlItem } from '../types/segmentedControlItem';
|
|
7
|
+
|
|
8
|
+
// =========================================================================
|
|
9
|
+
// Props, States & Constants
|
|
10
|
+
// =========================================================================
|
|
11
|
+
let {
|
|
12
|
+
// 基本プロパティ
|
|
13
|
+
items = [],
|
|
14
|
+
value = $bindable(''),
|
|
15
|
+
|
|
16
|
+
// HTML属性系
|
|
17
|
+
id,
|
|
18
|
+
name = `segmented-control-${Math.random().toString(36).substring(2, 15)}`,
|
|
19
|
+
|
|
20
|
+
// スタイル/レイアウト
|
|
21
|
+
size = 'medium',
|
|
22
|
+
fullWidth = false,
|
|
23
|
+
color,
|
|
24
|
+
rounded = false,
|
|
25
|
+
|
|
26
|
+
// アイコン関連
|
|
27
|
+
iconFilled = false,
|
|
28
|
+
iconWeight = 300,
|
|
29
|
+
iconGrade = 0,
|
|
30
|
+
iconOpticalSize,
|
|
31
|
+
iconVariant = 'outlined',
|
|
32
|
+
|
|
33
|
+
// 状態/動作
|
|
34
|
+
disabled = false,
|
|
35
|
+
|
|
36
|
+
// ARIA/アクセシビリティ
|
|
37
|
+
ariaLabel,
|
|
38
|
+
ariaLabelledby,
|
|
39
|
+
reducedMotion = false,
|
|
40
|
+
|
|
41
|
+
// 入力イベント
|
|
42
|
+
onchange = () => {}, // No params for type inference
|
|
43
|
+
|
|
44
|
+
// フォーカスイベント
|
|
45
|
+
onfocus = () => {}, // No params for type inference
|
|
46
|
+
onblur = () => {}, // No params for type inference
|
|
47
|
+
|
|
48
|
+
// キーボードイベント
|
|
49
|
+
onkeydown = () => {}, // No params for type inference
|
|
50
|
+
onkeyup = () => {}, // No params for type inference
|
|
51
|
+
|
|
52
|
+
// マウスイベント
|
|
53
|
+
onclick = () => {}, // No params for type inference
|
|
54
|
+
onmousedown = () => {}, // No params for type inference
|
|
55
|
+
onmouseup = () => {}, // No params for type inference
|
|
56
|
+
onmouseenter = () => {}, // No params for type inference
|
|
57
|
+
onmouseleave = () => {}, // No params for type inference
|
|
58
|
+
onmouseover = () => {}, // No params for type inference
|
|
59
|
+
onmouseout = () => {}, // No params for type inference
|
|
60
|
+
oncontextmenu = () => {}, // No params for type inference
|
|
61
|
+
onauxclick = () => {}, // No params for type inference
|
|
62
|
+
|
|
63
|
+
// タッチイベント
|
|
64
|
+
ontouchstart = () => {}, // No params for type inference
|
|
65
|
+
ontouchend = () => {}, // No params for type inference
|
|
66
|
+
ontouchmove = () => {}, // No params for type inference
|
|
67
|
+
ontouchcancel = () => {}, // No params for type inference
|
|
68
|
+
|
|
69
|
+
// ポインターイベント
|
|
70
|
+
onpointerdown = () => {}, // No params for type inference
|
|
71
|
+
onpointerup = () => {}, // No params for type inference
|
|
72
|
+
onpointerenter = () => {}, // No params for type inference
|
|
73
|
+
onpointerleave = () => {}, // No params for type inference
|
|
74
|
+
onpointermove = () => {}, // No params for type inference
|
|
75
|
+
onpointercancel = () => {}, // No params for type inference
|
|
76
|
+
|
|
77
|
+
// その他
|
|
78
|
+
...restProps
|
|
79
|
+
}: {
|
|
80
|
+
// 基本プロパティ
|
|
81
|
+
items: SegmentedControlItem[];
|
|
82
|
+
value: string;
|
|
83
|
+
|
|
84
|
+
// HTML属性系
|
|
85
|
+
id?: string;
|
|
86
|
+
name?: string;
|
|
87
|
+
|
|
88
|
+
// スタイル/レイアウト
|
|
89
|
+
size?: 'small' | 'medium' | 'large';
|
|
90
|
+
fullWidth?: boolean;
|
|
91
|
+
color?: string;
|
|
92
|
+
rounded?: boolean;
|
|
93
|
+
|
|
94
|
+
// アイコン関連
|
|
95
|
+
iconFilled?: boolean;
|
|
96
|
+
iconWeight?: IconWeight;
|
|
97
|
+
iconGrade?: IconGrade;
|
|
98
|
+
iconOpticalSize?: IconOpticalSize;
|
|
99
|
+
iconVariant?: IconVariant;
|
|
100
|
+
|
|
101
|
+
// 状態/動作
|
|
102
|
+
disabled?: boolean;
|
|
103
|
+
|
|
104
|
+
// ARIA/アクセシビリティ
|
|
105
|
+
ariaLabel?: string;
|
|
106
|
+
ariaLabelledby?: string;
|
|
107
|
+
reducedMotion?: boolean;
|
|
108
|
+
|
|
109
|
+
// 入力イベント
|
|
110
|
+
onchange?: (value: string) => void;
|
|
111
|
+
|
|
112
|
+
// フォーカスイベント
|
|
113
|
+
onfocus?: Function; // No params for type inference
|
|
114
|
+
onblur?: Function; // No params for type inference
|
|
115
|
+
|
|
116
|
+
// キーボードイベント
|
|
117
|
+
onkeydown?: Function; // No params for type inference
|
|
118
|
+
onkeyup?: Function; // No params for type inference
|
|
119
|
+
|
|
120
|
+
// マウスイベント
|
|
121
|
+
onclick?: Function; // No params for type inference
|
|
122
|
+
onmousedown?: Function; // No params for type inference
|
|
123
|
+
onmouseup?: Function; // No params for type inference
|
|
124
|
+
onmouseenter?: Function; // No params for type inference
|
|
125
|
+
onmouseleave?: Function; // No params for type inference
|
|
126
|
+
onmouseover?: Function; // No params for type inference
|
|
127
|
+
onmouseout?: Function; // No params for type inference
|
|
128
|
+
oncontextmenu?: Function; // No params for type inference
|
|
129
|
+
onauxclick?: Function; // No params for type inference
|
|
130
|
+
|
|
131
|
+
// タッチイベント
|
|
132
|
+
ontouchstart?: Function; // No params for type inference
|
|
133
|
+
ontouchend?: Function; // No params for type inference
|
|
134
|
+
ontouchmove?: Function; // No params for type inference
|
|
135
|
+
ontouchcancel?: Function; // No params for type inference
|
|
136
|
+
|
|
137
|
+
// ポインターイベント
|
|
138
|
+
onpointerdown?: Function; // No params for type inference
|
|
139
|
+
onpointerup?: Function; // No params for type inference
|
|
140
|
+
onpointerenter?: Function; // No params for type inference
|
|
141
|
+
onpointerleave?: Function; // No params for type inference
|
|
142
|
+
onpointermove?: Function; // No params for type inference
|
|
143
|
+
onpointercancel?: Function; // No params for type inference
|
|
144
|
+
|
|
145
|
+
// その他
|
|
146
|
+
[key: string]: any;
|
|
147
|
+
} = $props();
|
|
148
|
+
|
|
149
|
+
// =========================================================================
|
|
150
|
+
// Methods
|
|
151
|
+
// =========================================================================
|
|
152
|
+
const handleChange = (item: SegmentedControlItem, event: Event) => {
|
|
153
|
+
if (disabled || item.disabled) return;
|
|
154
|
+
|
|
155
|
+
const target = event.target as HTMLInputElement;
|
|
156
|
+
if (target.checked) {
|
|
157
|
+
value = item.value;
|
|
158
|
+
onchange(item.value);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const handleFocus = (event: FocusEvent) => {
|
|
163
|
+
onfocus(event);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const handleBlur = (event: FocusEvent) => {
|
|
167
|
+
onblur(event);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
171
|
+
if (disabled || items.length === 0) {
|
|
172
|
+
onkeydown(event);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Radioコンポーネントと同じキーボードナビゲーション
|
|
177
|
+
if (
|
|
178
|
+
event.key === 'ArrowUp' ||
|
|
179
|
+
event.key === 'ArrowDown' ||
|
|
180
|
+
event.key === 'ArrowLeft' ||
|
|
181
|
+
event.key === 'ArrowRight'
|
|
182
|
+
) {
|
|
183
|
+
const radioInputs = document.querySelectorAll(
|
|
184
|
+
`input[type="radio"][name="${name}"]`
|
|
185
|
+
) as NodeListOf<HTMLInputElement>;
|
|
186
|
+
const currentIndex = Array.from(radioInputs).findIndex((input) => input === event.target);
|
|
187
|
+
|
|
188
|
+
if (currentIndex !== -1) {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
let nextIndex;
|
|
191
|
+
|
|
192
|
+
if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
|
|
193
|
+
// 前の有効なアイテムを探す
|
|
194
|
+
for (let i = currentIndex - 1; i >= 0; i--) {
|
|
195
|
+
if (!items[i]?.disabled && !radioInputs[i].disabled) {
|
|
196
|
+
nextIndex = i;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// 見つからなければ最後の有効なアイテムへ
|
|
201
|
+
if (nextIndex === undefined) {
|
|
202
|
+
for (let i = items.length - 1; i > currentIndex; i--) {
|
|
203
|
+
if (!items[i]?.disabled && !radioInputs[i].disabled) {
|
|
204
|
+
nextIndex = i;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
// 次の有効なアイテムを探す
|
|
211
|
+
for (let i = currentIndex + 1; i < items.length; i++) {
|
|
212
|
+
if (!items[i]?.disabled && !radioInputs[i].disabled) {
|
|
213
|
+
nextIndex = i;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// 見つからなければ最初の有効なアイテムへ
|
|
218
|
+
if (nextIndex === undefined) {
|
|
219
|
+
for (let i = 0; i < currentIndex; i++) {
|
|
220
|
+
if (!items[i]?.disabled && !radioInputs[i].disabled) {
|
|
221
|
+
nextIndex = i;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (nextIndex !== undefined) {
|
|
229
|
+
const nextInput = radioInputs[nextIndex];
|
|
230
|
+
if (nextInput && !nextInput.disabled) {
|
|
231
|
+
nextInput.focus();
|
|
232
|
+
nextInput.click();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else if (event.key === 'Home') {
|
|
237
|
+
event.preventDefault();
|
|
238
|
+
const radioInputs = document.querySelectorAll(
|
|
239
|
+
`input[type="radio"][name="${name}"]`
|
|
240
|
+
) as NodeListOf<HTMLInputElement>;
|
|
241
|
+
for (let i = 0; i < items.length; i++) {
|
|
242
|
+
if (!items[i]?.disabled && !radioInputs[i].disabled) {
|
|
243
|
+
radioInputs[i].focus();
|
|
244
|
+
radioInputs[i].click();
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} else if (event.key === 'End') {
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
const radioInputs = document.querySelectorAll(
|
|
251
|
+
`input[type="radio"][name="${name}"]`
|
|
252
|
+
) as NodeListOf<HTMLInputElement>;
|
|
253
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
254
|
+
if (!items[i]?.disabled && !radioInputs[i].disabled) {
|
|
255
|
+
radioInputs[i].focus();
|
|
256
|
+
radioInputs[i].click();
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
onkeydown(event);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const handleKeyup = (event: KeyboardEvent) => {
|
|
266
|
+
onkeyup(event);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// マウスイベント
|
|
270
|
+
const handleClick = (event: MouseEvent) => {
|
|
271
|
+
if (disabled) return;
|
|
272
|
+
onclick(event);
|
|
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
|
+
// =========================================================================
|
|
368
|
+
// $derived
|
|
369
|
+
// =========================================================================
|
|
370
|
+
const effectiveIconSize = $derived(
|
|
371
|
+
iconOpticalSize || (size === 'small' ? 16 : size === 'large' ? 24 : 20)
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const containerClasses = $derived(
|
|
375
|
+
[
|
|
376
|
+
'segmented-control',
|
|
377
|
+
`segmented-control--${size}`,
|
|
378
|
+
fullWidth && 'segmented-control--full-width',
|
|
379
|
+
rounded && 'segmented-control--rounded',
|
|
380
|
+
disabled && 'segmented-control--disabled',
|
|
381
|
+
reducedMotion && 'segmented-control--no-motion'
|
|
382
|
+
]
|
|
383
|
+
.filter(Boolean)
|
|
384
|
+
.join(' ')
|
|
385
|
+
);
|
|
386
|
+
</script>
|
|
387
|
+
|
|
388
|
+
<div
|
|
389
|
+
class={containerClasses}
|
|
390
|
+
role="radiogroup"
|
|
391
|
+
aria-label={ariaLabelledby ? undefined : ariaLabel}
|
|
392
|
+
aria-labelledby={ariaLabelledby}
|
|
393
|
+
style="--svelte-ui-segmented-control-selected-bg: {color || 'var(--svelte-ui-primary-color)'};"
|
|
394
|
+
{id}
|
|
395
|
+
data-testid="segmented-control"
|
|
396
|
+
{...restProps}
|
|
397
|
+
>
|
|
398
|
+
{#each items as item, index}
|
|
399
|
+
{@const isSelected = value === item.value}
|
|
400
|
+
{@const isDisabled = disabled || item.disabled}
|
|
401
|
+
{@const inputId = `${name}-${index}`}
|
|
402
|
+
<div class="segmented-control__item">
|
|
403
|
+
<input
|
|
404
|
+
type="radio"
|
|
405
|
+
id={inputId}
|
|
406
|
+
{name}
|
|
407
|
+
value={item.value}
|
|
408
|
+
checked={isSelected}
|
|
409
|
+
disabled={isDisabled}
|
|
410
|
+
class="segmented-control__input"
|
|
411
|
+
onchange={(e) => handleChange(item, e)}
|
|
412
|
+
onfocus={handleFocus}
|
|
413
|
+
onblur={handleBlur}
|
|
414
|
+
onkeydown={handleKeydown}
|
|
415
|
+
onkeyup={handleKeyup}
|
|
416
|
+
onclick={handleClick}
|
|
417
|
+
onmousedown={handleMouseDown}
|
|
418
|
+
onmouseup={handleMouseUp}
|
|
419
|
+
onmouseenter={handleMouseEnter}
|
|
420
|
+
onmouseleave={handleMouseLeave}
|
|
421
|
+
onmouseover={handleMouseOver}
|
|
422
|
+
onmouseout={handleMouseOut}
|
|
423
|
+
oncontextmenu={handleContextMenu}
|
|
424
|
+
onauxclick={handleAuxClick}
|
|
425
|
+
ontouchstart={handleTouchStart}
|
|
426
|
+
ontouchend={handleTouchEnd}
|
|
427
|
+
ontouchmove={handleTouchMove}
|
|
428
|
+
ontouchcancel={handleTouchCancel}
|
|
429
|
+
onpointerdown={handlePointerDown}
|
|
430
|
+
onpointerup={handlePointerUp}
|
|
431
|
+
onpointerenter={handlePointerEnter}
|
|
432
|
+
onpointerleave={handlePointerLeave}
|
|
433
|
+
onpointermove={handlePointerMove}
|
|
434
|
+
onpointercancel={handlePointerCancel}
|
|
435
|
+
data-testid="segmented-control-input"
|
|
436
|
+
data-value={item.value}
|
|
437
|
+
/>
|
|
438
|
+
<label
|
|
439
|
+
for={inputId}
|
|
440
|
+
class="segmented-control__label"
|
|
441
|
+
class:segmented-control__label--selected={isSelected}
|
|
442
|
+
class:segmented-control__label--first={index === 0}
|
|
443
|
+
class:segmented-control__label--last={index === items.length - 1}
|
|
444
|
+
aria-label={item.ariaLabel || item.label || undefined}
|
|
445
|
+
>
|
|
446
|
+
{#if item.icon}
|
|
447
|
+
<span class="segmented-control__icon">
|
|
448
|
+
<Icon
|
|
449
|
+
filled={iconFilled || isSelected}
|
|
450
|
+
weight={iconWeight}
|
|
451
|
+
grade={iconGrade}
|
|
452
|
+
opticalSize={effectiveIconSize}
|
|
453
|
+
variant={iconVariant}
|
|
454
|
+
>
|
|
455
|
+
{item.icon}
|
|
456
|
+
</Icon>
|
|
457
|
+
</span>
|
|
458
|
+
{/if}
|
|
459
|
+
{#if item.label}
|
|
460
|
+
<span class="segmented-control__label-text">{item.label}</span>
|
|
461
|
+
{/if}
|
|
462
|
+
</label>
|
|
463
|
+
</div>
|
|
464
|
+
{/each}
|
|
465
|
+
</div>
|
|
466
|
+
|
|
467
|
+
<style>.segmented-control {
|
|
468
|
+
display: inline-flex;
|
|
469
|
+
position: relative;
|
|
470
|
+
padding: var(--svelte-ui-segmented-control-base-padding);
|
|
471
|
+
background-color: var(--svelte-ui-segmented-control-base-bg);
|
|
472
|
+
border-radius: var(--svelte-ui-segmented-control-base-border-radius);
|
|
473
|
+
gap: var(--svelte-ui-segmented-control-gap);
|
|
474
|
+
box-sizing: border-box;
|
|
475
|
+
width: fit-content;
|
|
476
|
+
max-width: 100%;
|
|
477
|
+
overflow-x: auto;
|
|
478
|
+
overflow-y: visible;
|
|
479
|
+
-ms-overflow-style: none;
|
|
480
|
+
overscroll-behavior: contain;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.segmented-control::-webkit-scrollbar {
|
|
484
|
+
display: none;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.segmented-control--full-width {
|
|
488
|
+
width: 100%;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.segmented-control--full-width .segmented-control__item {
|
|
492
|
+
flex: 1;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.segmented-control--rounded {
|
|
496
|
+
border-radius: var(--svelte-ui-border-radius-rounded, 9999px);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.segmented-control--disabled {
|
|
500
|
+
opacity: var(--svelte-ui-button-disabled-opacity);
|
|
501
|
+
pointer-events: none;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.segmented-control__item {
|
|
505
|
+
position: relative;
|
|
506
|
+
flex: 0 1 auto;
|
|
507
|
+
display: flex;
|
|
508
|
+
align-items: stretch;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.segmented-control__input {
|
|
512
|
+
position: absolute;
|
|
513
|
+
width: 0;
|
|
514
|
+
height: 0;
|
|
515
|
+
margin: 0;
|
|
516
|
+
opacity: 0;
|
|
517
|
+
pointer-events: none;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.segmented-control__label {
|
|
521
|
+
display: flex;
|
|
522
|
+
align-items: center;
|
|
523
|
+
justify-content: center;
|
|
524
|
+
gap: 6px;
|
|
525
|
+
position: relative;
|
|
526
|
+
border: none;
|
|
527
|
+
background-color: transparent;
|
|
528
|
+
color: var(--svelte-ui-text-subtle-color, var(--svelte-ui-text-color));
|
|
529
|
+
white-space: nowrap;
|
|
530
|
+
cursor: pointer;
|
|
531
|
+
transition-property: background-color, color;
|
|
532
|
+
transition-duration: var(--svelte-ui-transition-duration, 0.2s);
|
|
533
|
+
transition-timing-function: ease;
|
|
534
|
+
outline: none;
|
|
535
|
+
font-family: inherit;
|
|
536
|
+
font-weight: 500;
|
|
537
|
+
line-height: 1;
|
|
538
|
+
box-sizing: border-box;
|
|
539
|
+
width: 100%;
|
|
540
|
+
text-align: center;
|
|
541
|
+
border-radius: var(--svelte-ui-segmented-control-button-radius);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.segmented-control--small .segmented-control__label {
|
|
545
|
+
height: var(--svelte-ui-segmented-control-button-height-sm);
|
|
546
|
+
font-size: var(--svelte-ui-segmented-control-font-size-sm);
|
|
547
|
+
padding: var(--svelte-ui-segmented-control-button-padding-sm);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.segmented-control--small .segmented-control__label:not(:has(.segmented-control__label-text)) {
|
|
551
|
+
width: var(--svelte-ui-segmented-control-button-height-sm);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.segmented-control--medium .segmented-control__label {
|
|
555
|
+
height: var(--svelte-ui-segmented-control-button-height);
|
|
556
|
+
font-size: var(--svelte-ui-segmented-control-font-size);
|
|
557
|
+
padding: var(--svelte-ui-segmented-control-button-padding);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.segmented-control--medium .segmented-control__label:not(:has(.segmented-control__label-text)) {
|
|
561
|
+
width: var(--svelte-ui-segmented-control-button-height);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.segmented-control--large .segmented-control__label {
|
|
565
|
+
height: var(--svelte-ui-segmented-control-button-height-lg);
|
|
566
|
+
font-size: var(--svelte-ui-segmented-control-font-size-lg);
|
|
567
|
+
padding: var(--svelte-ui-segmented-control-button-padding-lg);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.segmented-control--large .segmented-control__label:not(:has(.segmented-control__label-text)) {
|
|
571
|
+
width: var(--svelte-ui-segmented-control-button-height-lg);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.segmented-control--rounded .segmented-control__label {
|
|
575
|
+
border-radius: calc(var(--svelte-ui-border-radius-rounded, 9999px) - 2px);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
@media (hover: hover) {
|
|
579
|
+
.segmented-control__input:not(:disabled) + .segmented-control__label:hover:not(.segmented-control__label--selected) {
|
|
580
|
+
background-color: var(--svelte-ui-segmented-control-hover-overlay);
|
|
581
|
+
color: var(--svelte-ui-text-color);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
.segmented-control__input:focus-visible + .segmented-control__label {
|
|
585
|
+
outline: var(--svelte-ui-focus-outline-inner, 2px solid currentColor);
|
|
586
|
+
outline-offset: var(--svelte-ui-focus-outline-offset-inner, 2px);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.segmented-control__input:checked + .segmented-control__label,
|
|
590
|
+
.segmented-control__label--selected {
|
|
591
|
+
background-color: var(--svelte-ui-segmented-control-selected-bg);
|
|
592
|
+
color: var(--svelte-ui-segmented-control-selected-text-color, white);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
.segmented-control:not(.segmented-control--disabled) .segmented-control__input:disabled + .segmented-control__label {
|
|
596
|
+
opacity: var(--svelte-ui-button-disabled-opacity);
|
|
597
|
+
cursor: not-allowed;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.segmented-control__icon {
|
|
601
|
+
display: flex;
|
|
602
|
+
align-items: center;
|
|
603
|
+
justify-content: center;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.segmented-control__label-text {
|
|
607
|
+
text-box-trim: trim-both;
|
|
608
|
+
text-box-edge: cap alphabetic;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/* Reduced motion */
|
|
612
|
+
.segmented-control--no-motion,
|
|
613
|
+
.segmented-control--no-motion .segmented-control__label {
|
|
614
|
+
transition-duration: 0.01s;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
@media (prefers-reduced-motion: reduce) {
|
|
618
|
+
.segmented-control,
|
|
619
|
+
.segmented-control__label {
|
|
620
|
+
transition-duration: 0.01s;
|
|
621
|
+
}
|
|
622
|
+
}</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
|
|
2
|
+
import type { SegmentedControlItem } from '../types/segmentedControlItem';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
items: SegmentedControlItem[];
|
|
5
|
+
value: string;
|
|
6
|
+
id?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
size?: 'small' | 'medium' | 'large';
|
|
9
|
+
fullWidth?: boolean;
|
|
10
|
+
color?: string;
|
|
11
|
+
rounded?: boolean;
|
|
12
|
+
iconFilled?: boolean;
|
|
13
|
+
iconWeight?: IconWeight;
|
|
14
|
+
iconGrade?: IconGrade;
|
|
15
|
+
iconOpticalSize?: IconOpticalSize;
|
|
16
|
+
iconVariant?: IconVariant;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
ariaLabel?: string;
|
|
19
|
+
ariaLabelledby?: string;
|
|
20
|
+
reducedMotion?: boolean;
|
|
21
|
+
onchange?: (value: string) => void;
|
|
22
|
+
onfocus?: Function;
|
|
23
|
+
onblur?: Function;
|
|
24
|
+
onkeydown?: Function;
|
|
25
|
+
onkeyup?: Function;
|
|
26
|
+
onclick?: Function;
|
|
27
|
+
onmousedown?: Function;
|
|
28
|
+
onmouseup?: Function;
|
|
29
|
+
onmouseenter?: Function;
|
|
30
|
+
onmouseleave?: Function;
|
|
31
|
+
onmouseover?: Function;
|
|
32
|
+
onmouseout?: Function;
|
|
33
|
+
oncontextmenu?: Function;
|
|
34
|
+
onauxclick?: Function;
|
|
35
|
+
ontouchstart?: Function;
|
|
36
|
+
ontouchend?: Function;
|
|
37
|
+
ontouchmove?: Function;
|
|
38
|
+
ontouchcancel?: Function;
|
|
39
|
+
onpointerdown?: Function;
|
|
40
|
+
onpointerup?: Function;
|
|
41
|
+
onpointerenter?: Function;
|
|
42
|
+
onpointerleave?: Function;
|
|
43
|
+
onpointermove?: Function;
|
|
44
|
+
onpointercancel?: Function;
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
};
|
|
47
|
+
declare const SegmentedControl: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
48
|
+
type SegmentedControl = ReturnType<typeof SegmentedControl>;
|
|
49
|
+
export default SegmentedControl;
|
|
@@ -2,7 +2,7 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
children?: Snippet;
|
|
5
|
-
value
|
|
5
|
+
value: boolean;
|
|
6
6
|
id?: string;
|
|
7
7
|
inputAttributes?: HTMLInputAttributes | undefined;
|
|
8
8
|
size?: 'small' | 'medium' | 'large';
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
}: {
|
|
92
92
|
// 基本プロパティ
|
|
93
93
|
name?: string;
|
|
94
|
-
value: string
|
|
94
|
+
value: string;
|
|
95
95
|
placeholder?: string;
|
|
96
96
|
|
|
97
97
|
// HTML属性系
|
|
@@ -177,9 +177,9 @@
|
|
|
177
177
|
|
|
178
178
|
const clear = (): void => {
|
|
179
179
|
if (disabled || readonly) return;
|
|
180
|
-
value =
|
|
180
|
+
value = '';
|
|
181
181
|
ref?.focus();
|
|
182
|
-
onchange?.(
|
|
182
|
+
onchange?.('');
|
|
183
183
|
};
|
|
184
184
|
|
|
185
185
|
// 外部からフォーカスを当てる(キャレットを先頭に移動)
|
|
@@ -334,7 +334,7 @@
|
|
|
334
334
|
|
|
335
335
|
// HTML表示用の値(autoResize時の高さ調整用)
|
|
336
336
|
const htmlValue = $derived.by(() => {
|
|
337
|
-
if (
|
|
337
|
+
if (value !== '') {
|
|
338
338
|
let html = value
|
|
339
339
|
.replace(/ +/g, (match) => ' '.repeat(match.length))
|
|
340
340
|
.replace(/\n/g, '<br />');
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
}
|
|
346
346
|
return html;
|
|
347
347
|
} else {
|
|
348
|
-
return
|
|
348
|
+
return '';
|
|
349
349
|
}
|
|
350
350
|
});
|
|
351
351
|
</script>
|
|
@@ -2,7 +2,7 @@ import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
|
2
2
|
import type { IconVariant } from '../types/icon';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
name?: string;
|
|
5
|
-
value: string
|
|
5
|
+
value: string;
|
|
6
6
|
placeholder?: string;
|
|
7
7
|
id?: string | null;
|
|
8
8
|
tabindex?: number | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export { default as PopupMenuButton } from './components/PopupMenuButton.svelte'
|
|
|
23
23
|
export { default as Radio } from './components/Radio.svelte';
|
|
24
24
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
25
25
|
export { default as Select } from './components/Select.svelte';
|
|
26
|
+
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
26
27
|
export { default as Slider } from './components/Slider.svelte';
|
|
27
28
|
export { default as Snackbar } from './components/Snackbar.svelte';
|
|
28
29
|
export { default as SnackbarItem } from './components/SnackbarItem.svelte';
|
|
@@ -40,3 +41,4 @@ export * from './utils/mobile';
|
|
|
40
41
|
export * from './utils/snackbar.svelte';
|
|
41
42
|
export * from './utils/style';
|
|
42
43
|
export type { MenuItem } from './types/menuItem';
|
|
44
|
+
export type { SegmentedControlItem } from './types/segmentedControlItem';
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export { default as PopupMenuButton } from './components/PopupMenuButton.svelte'
|
|
|
24
24
|
export { default as Radio } from './components/Radio.svelte';
|
|
25
25
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
26
26
|
export { default as Select } from './components/Select.svelte';
|
|
27
|
+
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
27
28
|
export { default as Slider } from './components/Slider.svelte';
|
|
28
29
|
export { default as Snackbar } from './components/Snackbar.svelte';
|
|
29
30
|
export { default as SnackbarItem } from './components/SnackbarItem.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|