@14ch/svelte-ui 0.0.13 → 0.0.14

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.
Files changed (83) hide show
  1. package/dist/assets/styles/variables.scss +2 -2
  2. package/dist/components/Button.svelte +31 -5
  3. package/dist/components/Button.svelte.d.ts +5 -3
  4. package/dist/components/Checkbox.svelte +2 -3
  5. package/dist/components/Checkbox.svelte.d.ts +1 -1
  6. package/dist/components/CheckboxGroup.svelte +6 -3
  7. package/dist/components/CheckboxGroup.svelte.d.ts +1 -1
  8. package/dist/components/ColorPicker.svelte +4 -5
  9. package/dist/components/ColorPicker.svelte.d.ts +3 -3
  10. package/dist/components/Combobox.svelte +26 -7
  11. package/dist/components/Combobox.svelte.d.ts +1 -1
  12. package/dist/components/ConfirmDialog.svelte +3 -7
  13. package/dist/components/ConfirmDialog.svelte.d.ts +1 -1
  14. package/dist/components/Datepicker.svelte +88 -24
  15. package/dist/components/Datepicker.svelte.d.ts +4 -3
  16. package/dist/components/DatepickerCalendar.svelte +1 -1
  17. package/dist/components/DatepickerCalendar.svelte.d.ts +1 -1
  18. package/dist/components/Drawer.svelte +1 -1
  19. package/dist/components/Fab.svelte +28 -11
  20. package/dist/components/Fab.svelte.d.ts +8 -4
  21. package/dist/components/FileUploader.svelte +1 -1
  22. package/dist/components/FileUploader.svelte.d.ts +1 -1
  23. package/dist/components/IconButton.svelte +4 -3
  24. package/dist/components/IconButton.svelte.d.ts +4 -3
  25. package/dist/components/ImageUploader.svelte +1 -1
  26. package/dist/components/ImageUploader.svelte.d.ts +1 -1
  27. package/dist/components/Input.svelte +5 -3
  28. package/dist/components/Input.svelte.d.ts +4 -3
  29. package/dist/components/Popup.svelte +69 -82
  30. package/dist/components/Popup.svelte.d.ts +3 -3
  31. package/dist/components/PopupMenu.svelte +40 -56
  32. package/dist/components/PopupMenu.svelte.d.ts +3 -3
  33. package/dist/components/PopupMenuButton.svelte +10 -23
  34. package/dist/components/PopupMenuButton.svelte.d.ts +5 -4
  35. package/dist/components/Radio.svelte +1 -1
  36. package/dist/components/Radio.svelte.d.ts +1 -1
  37. package/dist/components/RadioGroup.svelte +1 -1
  38. package/dist/components/RadioGroup.svelte.d.ts +1 -1
  39. package/dist/components/SegmentedControl.svelte +1 -2
  40. package/dist/components/SegmentedControl.svelte.d.ts +1 -1
  41. package/dist/components/Select.svelte +1 -1
  42. package/dist/components/Select.svelte.d.ts +1 -1
  43. package/dist/components/Slider.svelte +2 -3
  44. package/dist/components/Slider.svelte.d.ts +1 -1
  45. package/dist/components/Snackbar.svelte +3 -2
  46. package/dist/components/Snackbar.svelte.d.ts +3 -2
  47. package/dist/components/SnackbarItem.svelte +4 -3
  48. package/dist/components/SnackbarItem.svelte.d.ts +4 -3
  49. package/dist/components/Switch.svelte +2 -4
  50. package/dist/components/Switch.svelte.d.ts +1 -1
  51. package/dist/components/TabItem.svelte +2 -2
  52. package/dist/components/Textarea.svelte +14 -12
  53. package/dist/components/Textarea.svelte.d.ts +4 -3
  54. package/dist/components/skeleton/SkeletonAvatar.svelte +22 -32
  55. package/dist/components/skeleton/SkeletonAvatar.svelte.d.ts +6 -2
  56. package/dist/components/skeleton/SkeletonButton.svelte +18 -16
  57. package/dist/components/skeleton/SkeletonButton.svelte.d.ts +5 -2
  58. package/dist/components/skeleton/SkeletonHeading.svelte +15 -18
  59. package/dist/components/skeleton/SkeletonHeading.svelte.d.ts +3 -2
  60. package/dist/components/skeleton/SkeletonMedia.svelte +29 -41
  61. package/dist/components/skeleton/SkeletonMedia.svelte.d.ts +8 -2
  62. package/dist/components/skeleton/SkeletonText.svelte +12 -14
  63. package/dist/components/skeleton/SkeletonText.svelte.d.ts +4 -2
  64. package/dist/i18n/index.d.ts +143 -5
  65. package/dist/i18n/index.js +20 -32
  66. package/dist/i18n/locales/de.d.ts +35 -0
  67. package/dist/i18n/locales/de.js +35 -0
  68. package/dist/i18n/locales/es.d.ts +35 -0
  69. package/dist/i18n/locales/es.js +35 -0
  70. package/dist/i18n/locales/fr.d.ts +35 -0
  71. package/dist/i18n/locales/fr.js +35 -0
  72. package/dist/i18n/locales/zh-cn.d.ts +35 -0
  73. package/dist/i18n/locales/zh-cn.js +35 -0
  74. package/dist/index.d.ts +5 -2
  75. package/dist/index.js +1 -0
  76. package/dist/types/menuItem.d.ts +1 -1
  77. package/dist/types/propOptions.d.ts +54 -0
  78. package/dist/types/propOptions.js +5 -0
  79. package/dist/utils/popupManager.d.ts +26 -0
  80. package/dist/utils/popupManager.js +34 -0
  81. package/package.json +1 -1
  82. /package/dist/types/{eventHandlers.d.ts → callbackHandlers.d.ts} +0 -0
  83. /package/dist/types/{eventHandlers.js → callbackHandlers.js} +0 -0
@@ -610,8 +610,8 @@
610
610
  /* Drawer */
611
611
  --svelte-ui-drawer-gap: 16px;
612
612
  --svelte-ui-drawer-gap-sm: 8px;
613
- --svelte-ui-drawer-padding: 16px 24px;
614
- --svelte-ui-drawer-body-padding: 24px;
613
+ --svelte-ui-drawer-header-padding: 16px 24px;
614
+ --svelte-ui-drawer-body-padding: 0;
615
615
  --svelte-ui-drawer-header-height: 56px;
616
616
  --svelte-ui-drawer-title-font-size: 1.4rem;
617
617
  --svelte-ui-drawer-description-font-size: 0.875rem;
@@ -10,7 +10,8 @@
10
10
  MouseHandler,
11
11
  TouchHandler,
12
12
  PointerHandler
13
- } from '../types/eventHandlers';
13
+ } from '../types/callbackHandlers';
14
+ import type { ButtonVariant, ButtonSize } from '../types/propOptions';
14
15
  import Icon from './Icon.svelte';
15
16
  import LoadingSpinner from './LoadingSpinner.svelte';
16
17
  import { getStyleFromNumber } from '../utils/style';
@@ -29,10 +30,11 @@
29
30
 
30
31
  // スタイル/レイアウト
31
32
  customStyle?: HTMLButtonAttributes['style'];
32
- variant?: 'ghost' | 'filled' | 'outlined' | 'glass';
33
- size?: 'small' | 'medium' | 'large';
33
+ variant?: ButtonVariant;
34
+ size?: ButtonSize;
34
35
  color?: string;
35
36
  fullWidth?: boolean;
37
+ align?: 'left' | 'center' | 'right';
36
38
  minWidth?: string | number;
37
39
  rounded?: boolean;
38
40
  popup?: boolean;
@@ -107,6 +109,7 @@
107
109
  size = 'medium',
108
110
  color,
109
111
  fullWidth = false,
112
+ align = 'center',
110
113
  minWidth = 0,
111
114
  rounded = false,
112
115
  popup = false,
@@ -310,6 +313,7 @@
310
313
  popup && 'button--popup',
311
314
  rounded && 'button--rounded',
312
315
  fullWidth && 'button--full-width',
316
+ `button--align-${align}`,
313
317
  loading && 'button--loading',
314
318
  reducedMotion && 'button--no-motion'
315
319
  ]
@@ -453,6 +457,14 @@
453
457
  width: 100%;
454
458
  }
455
459
 
460
+ .button--align-left {
461
+ justify-content: flex-start;
462
+ }
463
+
464
+ .button--align-right {
465
+ justify-content: flex-end;
466
+ }
467
+
456
468
  .button--rounded {
457
469
  border-radius: var(--svelte-ui-button-border-radius-rounded);
458
470
  }
@@ -510,9 +522,8 @@
510
522
  }
511
523
 
512
524
  .button__label {
513
- width: 100%;
514
525
  display: block;
515
- text-align: center;
526
+ width: auto;
516
527
  line-height: var(--svelte-ui-button-line-height);
517
528
  text-box-trim: trim-both;
518
529
  text-box-edge: cap alphabetic;
@@ -531,6 +542,11 @@
531
542
  justify-content: center;
532
543
  }
533
544
 
545
+ .button--align-left .button__popup-icon {
546
+ margin-left: auto;
547
+ margin-right: -4px;
548
+ }
549
+
534
550
  .button__loading {
535
551
  position: absolute;
536
552
  top: 50%;
@@ -550,6 +566,11 @@
550
566
  margin-right: -2px;
551
567
  }
552
568
 
569
+ .button--align-left.button--small .button__popup-icon {
570
+ margin-left: auto;
571
+ margin-right: -2px;
572
+ }
573
+
553
574
  .button--large .button__icon {
554
575
  margin-left: -6px;
555
576
  }
@@ -558,6 +579,11 @@
558
579
  margin-right: -6px;
559
580
  }
560
581
 
582
+ .button--align-left.button--large .button__popup-icon {
583
+ margin-left: auto;
584
+ margin-right: -6px;
585
+ }
586
+
561
587
  /* Reduced motion */
562
588
  .button--no-motion,
563
589
  .button--no-motion:before,
@@ -1,17 +1,19 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
3
  import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
4
- import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler } from '../types/eventHandlers';
4
+ import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler } from '../types/callbackHandlers';
5
+ import type { ButtonVariant, ButtonSize } from '../types/propOptions';
5
6
  export type ButtonProps = {
6
7
  children: Snippet;
7
8
  buttonAttributes?: HTMLButtonAttributes | undefined;
8
9
  type?: HTMLButtonAttributes['type'];
9
10
  tabindex?: number | null;
10
11
  customStyle?: HTMLButtonAttributes['style'];
11
- variant?: 'ghost' | 'filled' | 'outlined' | 'glass';
12
- size?: 'small' | 'medium' | 'large';
12
+ variant?: ButtonVariant;
13
+ size?: ButtonSize;
13
14
  color?: string;
14
15
  fullWidth?: boolean;
16
+ align?: 'left' | 'center' | 'right';
15
17
  minWidth?: string | number;
16
18
  rounded?: boolean;
17
19
  popup?: boolean;
@@ -8,9 +8,8 @@
8
8
  KeyboardHandler,
9
9
  MouseHandler,
10
10
  TouchHandler,
11
- PointerHandler,
12
- BivariantValueHandler
13
- } from '../types/eventHandlers';
11
+ PointerHandler
12
+ } from '../types/callbackHandlers';
14
13
 
15
14
  // =========================================================================
16
15
  // Props, States & Constants
@@ -1,6 +1,6 @@
1
1
  import { type Snippet } from 'svelte';
2
2
  import type { HTMLInputAttributes } from 'svelte/elements';
3
- import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler } from '../types/eventHandlers';
3
+ import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler } from '../types/callbackHandlers';
4
4
  export type CheckboxProps = {
5
5
  children?: Snippet;
6
6
  name?: string;
@@ -5,7 +5,7 @@
5
5
  import Checkbox from './Checkbox.svelte';
6
6
  import { onMount } from 'svelte';
7
7
  import { getStyleFromNumber } from '../utils/style';
8
- import type { BivariantValueHandler } from '../types/eventHandlers';
8
+ import type { BivariantValueHandler } from '../types/callbackHandlers';
9
9
 
10
10
  // =========================================================================
11
11
  // Props, States & Constants
@@ -78,8 +78,11 @@
78
78
  onchange(value);
79
79
  };
80
80
 
81
- const gapStyle = gap !== undefined ? getStyleFromNumber(gap) : undefined;
82
- const minOptionWidthStyle = getStyleFromNumber(minOptionWidth);
81
+ // =========================================================================
82
+ // $defived
83
+ // =========================================================================
84
+ const gapStyle = $derived(gap !== undefined ? getStyleFromNumber(gap) : undefined);
85
+ const minOptionWidthStyle = $derived(getStyleFromNumber(minOptionWidth));
83
86
  </script>
84
87
 
85
88
  <ul
@@ -1,5 +1,5 @@
1
1
  import type { Option, OptionValue } from '../types/options';
2
- import type { BivariantValueHandler } from '../types/eventHandlers';
2
+ import type { BivariantValueHandler } from '../types/callbackHandlers';
3
3
  export type CheckboxGroupProps = {
4
4
  options: Option[];
5
5
  value: OptionValue[];
@@ -11,9 +11,8 @@
11
11
  KeyboardHandler,
12
12
  MouseHandler,
13
13
  TouchHandler,
14
- PointerHandler,
15
- BivariantValueHandler
16
- } from '../types/eventHandlers';
14
+ PointerHandler
15
+ } from '../types/callbackHandlers';
17
16
 
18
17
  // =========================================================================
19
18
  // Props, States & Constants
@@ -40,8 +39,8 @@
40
39
  iconVariant?: IconVariant;
41
40
 
42
41
  // 入力イベント
43
- onchange?: BivariantValueHandler<string>;
44
- oninput?: BivariantValueHandler<string>;
42
+ onchange?: (value: string) => void;
43
+ oninput?: (value: string) => void;
45
44
 
46
45
  // フォーカスイベント
47
46
  onfocus?: FocusHandler;
@@ -1,6 +1,6 @@
1
1
  import type { HTMLInputAttributes } from 'svelte/elements';
2
2
  import type { IconVariant } from '../types/icon';
3
- import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler, BivariantValueHandler } from '../types/eventHandlers';
3
+ import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler } from '../types/callbackHandlers';
4
4
  export type ColorPickerProps = {
5
5
  value: string;
6
6
  id?: string;
@@ -14,8 +14,8 @@ export type ColorPickerProps = {
14
14
  clearable?: boolean;
15
15
  clearButtonAriaLabel?: string;
16
16
  iconVariant?: IconVariant;
17
- onchange?: BivariantValueHandler<string>;
18
- oninput?: BivariantValueHandler<string>;
17
+ onchange?: (value: string) => void;
18
+ oninput?: (value: string) => void;
19
19
  onfocus?: FocusHandler;
20
20
  onblur?: FocusHandler;
21
21
  onkeydown?: KeyboardHandler;
@@ -13,7 +13,7 @@
13
13
  TouchHandler,
14
14
  PointerHandler,
15
15
  BivariantValueHandler
16
- } from '../types/eventHandlers';
16
+ } from '../types/callbackHandlers';
17
17
 
18
18
  // =========================================================================
19
19
  // Props, States & Constants
@@ -165,10 +165,7 @@
165
165
  let popupRef = $state<any>();
166
166
  let highlightedIndex = $state(-1);
167
167
  let isFocused = $state(false);
168
-
169
- // 各要素のIDを生成
170
- const inputId = `${id}-input`;
171
- const listboxId = `${id}-listbox`;
168
+ let isKeyboardNavigation = $state(false);
172
169
 
173
170
  // =========================================================================
174
171
  // Methods
@@ -180,6 +177,7 @@
180
177
  popupRef?.close();
181
178
  highlightedIndex = -1;
182
179
  isFocused = false;
180
+ isKeyboardNavigation = false;
183
181
 
184
182
  // スクリーンリーダーアナウンス
185
183
  if (option !== null && option !== undefined) {
@@ -239,6 +237,7 @@
239
237
  switch (event.key) {
240
238
  case 'ArrowDown':
241
239
  event?.preventDefault?.();
240
+ isKeyboardNavigation = true;
242
241
  if (!isFocused) {
243
242
  isFocused = true;
244
243
  popupRef?.open();
@@ -247,6 +246,7 @@
247
246
  break;
248
247
  case 'ArrowUp':
249
248
  event?.preventDefault?.();
249
+ isKeyboardNavigation = true;
250
250
  if (!isFocused) {
251
251
  isFocused = true;
252
252
  popupRef?.open();
@@ -263,6 +263,7 @@
263
263
  event?.preventDefault?.();
264
264
  inputValue = '';
265
265
  highlightedIndex = -1;
266
+ isKeyboardNavigation = false;
266
267
  popupRef?.close();
267
268
  isFocused = false;
268
269
  break;
@@ -387,6 +388,9 @@
387
388
  // =========================================================================
388
389
  // $derived
389
390
  // =========================================================================
391
+ // 各要素のIDを生成
392
+ const inputId = $derived(`${id}-input`);
393
+ const listboxId = $derived(`${id}-listbox`);
390
394
 
391
395
  // フィルタリングされたオプション
392
396
  const filteredOptions = $derived.by(() => {
@@ -463,7 +467,7 @@
463
467
  bind:this={popupRef}
464
468
  anchorElement={comboboxElement}
465
469
  position="bottom-left"
466
- focusTrap={false}
470
+ mobileFullscreen={false}
467
471
  onClose={handlePopupClose}
468
472
  margin={4}
469
473
  >
@@ -481,6 +485,7 @@
481
485
  type="button"
482
486
  class="combobox__option"
483
487
  class:combobox__option--highlighted={index === highlightedIndex}
488
+ class:combobox__option--keyboard={isKeyboardNavigation && index === highlightedIndex}
484
489
  class:combobox__option--selected={option === inputValue}
485
490
  role="option"
486
491
  aria-selected={option === inputValue}
@@ -489,7 +494,10 @@
489
494
  event?.stopPropagation?.();
490
495
  selectOption(option);
491
496
  }}
492
- onmouseenter={() => (highlightedIndex = index)}
497
+ onmouseenter={() => {
498
+ highlightedIndex = index;
499
+ isKeyboardNavigation = false; // マウス操作時はキーボード操作フラグをリセット
500
+ }}
493
501
  >
494
502
  {option}
495
503
  </button>
@@ -553,6 +561,12 @@
553
561
  background-color: var(--svelte-ui-combobox-option-hover-bg);
554
562
  }
555
563
 
564
+ /* キーボード操作時のみ枠線を表示 */
565
+ &.combobox__option--keyboard {
566
+ outline: var(--svelte-ui-focus-outline-inner);
567
+ outline-offset: var(--svelte-ui-focus-outline-offset-inner);
568
+ }
569
+
556
570
  &.combobox__option--selected {
557
571
  background-color: var(--svelte-ui-combobox-option-selected-bg);
558
572
  }
@@ -561,6 +575,11 @@
561
575
  background-color: var(--svelte-ui-combobox-option-selected-bg);
562
576
  }
563
577
 
578
+ &.combobox__option--selected.combobox__option--keyboard {
579
+ outline: var(--svelte-ui-focus-outline-inner);
580
+ outline-offset: var(--svelte-ui-focus-outline-offset-inner);
581
+ }
582
+
564
583
  &:focus,
565
584
  &:focus-visible {
566
585
  outline: var(--svelte-ui-focus-outline-inner);
@@ -1,5 +1,5 @@
1
1
  import type { HTMLInputAttributes } from 'svelte/elements';
2
- import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler, BivariantValueHandler } from '../types/eventHandlers';
2
+ import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler, BivariantValueHandler } from '../types/callbackHandlers';
3
3
  export type ComboboxProps = {
4
4
  name?: string;
5
5
  value: string | number | null | undefined;
@@ -20,7 +20,7 @@
20
20
  id?: string;
21
21
 
22
22
  // スタイル/レイアウト
23
- variant?: 'info' | 'warning' | 'danger';
23
+ danger?: boolean;
24
24
  width?: string | number;
25
25
 
26
26
  // 状態/動作
@@ -43,7 +43,7 @@
43
43
  id,
44
44
 
45
45
  // スタイル/レイアウト
46
- variant = 'info',
46
+ danger = false,
47
47
  width = 400,
48
48
 
49
49
  // 状態/動作
@@ -101,11 +101,7 @@
101
101
  </Button>
102
102
  <Button
103
103
  variant="filled"
104
- color={variant === 'danger'
105
- ? 'var(--svelte-ui-danger-color)'
106
- : variant === 'warning'
107
- ? 'var(--svelte-ui-warning-color)'
108
- : undefined}
104
+ color={danger ? 'var(--svelte-ui-danger-color)' : undefined}
109
105
  onclick={handleSubmit}
110
106
  >
111
107
  {submitLabel}
@@ -4,7 +4,7 @@ export type ConfirmDialogProps = {
4
4
  submitLabel?: string;
5
5
  cancelLabel?: string;
6
6
  id?: string;
7
- variant?: 'info' | 'warning' | 'danger';
7
+ danger?: boolean;
8
8
  width?: string | number;
9
9
  isOpen?: boolean;
10
10
  closeIfClickOutside?: boolean;
@@ -14,6 +14,7 @@
14
14
  import Popup from './Popup.svelte';
15
15
  import DatepickerCalendar from './DatepickerCalendar.svelte';
16
16
  import { announceToScreenReader } from '../utils/accessibility';
17
+ import { getLocale } from '../config';
17
18
  import type { HTMLInputAttributes } from 'svelte/elements';
18
19
  import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
19
20
  import type {
@@ -23,7 +24,8 @@
23
24
  MouseHandler,
24
25
  TouchHandler,
25
26
  PointerHandler
26
- } from '../types/eventHandlers';
27
+ } from '../types/callbackHandlers';
28
+ import type { DatepickerMode, FocusStyle } from '../types/propOptions';
27
29
 
28
30
  dayjs.extend(localeData);
29
31
 
@@ -44,7 +46,7 @@
44
46
 
45
47
  // スタイル/レイアウト
46
48
  inline?: boolean;
47
- focusStyle?: 'background' | 'outline' | 'none';
49
+ focusStyle?: FocusStyle;
48
50
  fullWidth?: boolean;
49
51
  rounded?: boolean;
50
52
 
@@ -58,7 +60,7 @@
58
60
 
59
61
  // 状態/動作
60
62
  disabled?: boolean;
61
- mode?: 'single' | 'range';
63
+ mode?: DatepickerMode;
62
64
  /** テキスト入力を有効にするか */
63
65
  enableTextInput?: boolean;
64
66
  /** クリック時にポップアップを開くかどうかを有効にするか */
@@ -104,7 +106,7 @@
104
106
  value = $bindable(),
105
107
  format,
106
108
  nullString = '',
107
- locale = 'en',
109
+ locale,
108
110
  rangeSeparator = ' - ',
109
111
 
110
112
  // HTML属性系
@@ -184,8 +186,6 @@
184
186
  let openedViaKeyboard: boolean = $state(false);
185
187
  let displayValue = $state('');
186
188
 
187
- const calendarId = `${id}-calendar`;
188
-
189
189
  const localeConfig = {
190
190
  en: {
191
191
  defaultFormat: 'MM/DD/YYYY (ddd)',
@@ -234,12 +234,22 @@
234
234
  // =========================================================================
235
235
  // Effects
236
236
  // =========================================================================
237
+ const effectiveLocale = $derived(locale ?? getLocale());
238
+
237
239
  $effect(() => {
238
- dayjs.locale(locale);
240
+ // range モードのときは、value を「start <= end」の順序に正規化
241
+ if (mode === 'range' && value && 'start' in value && 'end' in value) {
242
+ const startDay = dayjs(value.start).locale(effectiveLocale);
243
+ const endDay = dayjs(value.end).locale(effectiveLocale);
244
+ if (startDay.isAfter(endDay)) {
245
+ value = { start: value.end, end: value.start };
246
+ }
247
+ }
239
248
  });
240
249
 
241
250
  $effect(() => {
242
- const formatWithLocale = (date: Date) => dayjs(date).locale(locale).format(finalFormat);
251
+ const formatWithLocale = (date: Date) =>
252
+ dayjs(date).locale(effectiveLocale).format(finalFormat);
243
253
 
244
254
  if (mode === 'range' && value && 'start' in value && 'end' in value) {
245
255
  displayValue = `${formatWithLocale(value.start)}${rangeSeparator}${formatWithLocale(value.end)}`;
@@ -257,11 +267,11 @@
257
267
  // スクリーンリーダーアナウンス
258
268
  if (value) {
259
269
  if (mode === 'range' && typeof value === 'object' && 'start' in value && 'end' in value) {
260
- const startDate = dayjs(value.start).format(finalFormat);
261
- const endDate = dayjs(value.end).format(finalFormat);
270
+ const startDate = dayjs(value.start).locale(effectiveLocale).format(finalFormat);
271
+ const endDate = dayjs(value.end).locale(effectiveLocale).format(finalFormat);
262
272
  announceToScreenReader(`Date range selected: ${startDate} to ${endDate}`);
263
273
  } else if (value instanceof Date) {
264
- const formattedDate = dayjs(value).format(finalFormat);
274
+ const formattedDate = dayjs(value).locale(effectiveLocale).format(finalFormat);
265
275
  announceToScreenReader(`Date selected: ${formattedDate}`);
266
276
  }
267
277
  }
@@ -285,8 +295,13 @@
285
295
  const handleClick = (event: MouseEvent) => {
286
296
  if (disabled) return;
287
297
  if (enableClickToOpen) {
288
- openedViaKeyboard = false;
289
- open();
298
+ const isOpen = popupRef?.getIsOpen && popupRef.getIsOpen();
299
+ if (isOpen) {
300
+ close();
301
+ } else {
302
+ openedViaKeyboard = false;
303
+ open();
304
+ }
290
305
  }
291
306
  onclick?.(event);
292
307
  };
@@ -487,24 +502,73 @@
487
502
  if (!enableTextInput) return;
488
503
 
489
504
  const inputStr = String(inputValue ?? '').trim();
505
+
506
+ // 空文字列の場合は値をクリア
490
507
  if (!inputStr) {
491
508
  value = undefined;
492
509
  onchange(value);
493
510
  return;
494
511
  }
495
512
 
496
- // 入力値のうち「日付として有効な部分」(数字と区切り記号)だけを抽出してパースする
497
- const datePartMatch = inputStr.match(/^[0-90-9./-]+/);
498
- const datePart = datePartMatch ? datePartMatch[0] : inputStr;
513
+ // 日付パース処理
514
+ if (mode === 'range') {
515
+ const parsedRange = parseRangeInput(inputStr, effectiveLocale);
516
+ if (!parsedRange) return;
499
517
 
500
- // ロケールごとの日付専用フォーマット(rangeFormat は曜日を含まない)
501
- const parseFormat = currentLocaleConfig.rangeFormat;
518
+ value = parsedRange;
519
+ onchange(value);
520
+ return;
521
+ }
522
+
523
+ // single モードでは先頭の「日付本体」のみを解釈する
524
+ const parsedSingle = parseSingleInput(inputStr, effectiveLocale);
525
+ if (!parsedSingle) return;
502
526
 
527
+ value = parsedSingle;
528
+ onchange(value);
529
+ };
530
+
531
+ const parseSingleInput = (inputStr: string, locale: string): Date | null => {
532
+ const parseFormat = currentLocaleConfig.defaultFormat;
533
+ const datePart = extractDatePart(inputStr);
503
534
  const parsedDate = dayjs(datePart, parseFormat, locale, true);
504
- if (parsedDate.isValid()) {
505
- value = parsedDate.toDate();
506
- onchange(value);
535
+ if (!parsedDate.isValid()) return null;
536
+ return parsedDate.toDate();
537
+ };
538
+
539
+ const parseRangeInput = (inputStr: string, locale: string): { start: Date; end: Date } | null => {
540
+ const parseFormat = currentLocaleConfig.rangeFormat;
541
+ const dateLikeParts = inputStr.match(/[0-90-9./-]+/g) ?? [];
542
+
543
+ if (dateLikeParts.length < 2) {
544
+ return null;
507
545
  }
546
+
547
+ const startRaw = extractDatePart(dateLikeParts[0]);
548
+ const endRaw = extractDatePart(dateLikeParts[1]);
549
+
550
+ const startParsed = dayjs(startRaw, parseFormat, locale, true);
551
+ const endParsed = dayjs(endRaw, parseFormat, locale, true);
552
+
553
+ if (!startParsed.isValid() || !endParsed.isValid()) {
554
+ return null;
555
+ }
556
+
557
+ const startDate = startParsed.toDate();
558
+ const endDate = endParsed.toDate();
559
+
560
+ // start > end の場合はここで入れ替えて正規化して返す
561
+ return startDate <= endDate
562
+ ? { start: startDate, end: endDate }
563
+ : { start: endDate, end: startDate };
564
+ };
565
+
566
+ const extractDatePart = (raw: string): string => {
567
+ // 入力値のうち「日付として有効な部分」(数字と区切り記号)だけを抽出してパースする。
568
+ // 曜日やカッコなどの装飾はここで無視される。
569
+ const trimmed = raw.trim();
570
+ const match = trimmed.match(/^[0-90-9./-]+/);
571
+ return match ? match[0] : trimmed;
508
572
  };
509
573
 
510
574
  export const open = () => {
@@ -524,8 +588,8 @@
524
588
  // =========================================================================
525
589
  // $derived
526
590
  // =========================================================================
527
-
528
- const currentLocaleConfig = $derived(localeConfig[locale]);
591
+ const calendarId = $derived(`${id}-calendar`);
592
+ const currentLocaleConfig = $derived(localeConfig[effectiveLocale]);
529
593
  const finalFormat = $derived(
530
594
  format ||
531
595
  (mode === 'range' ? currentLocaleConfig.rangeFormat : currentLocaleConfig.defaultFormat)
@@ -606,7 +670,7 @@
606
670
  onchange={handleChange}
607
671
  {minDate}
608
672
  {maxDate}
609
- {locale}
673
+ locale={effectiveLocale}
610
674
  id={calendarId}
611
675
  />
612
676
  </Popup>
@@ -6,7 +6,8 @@ import 'dayjs/locale/es';
6
6
  import 'dayjs/locale/zh-cn';
7
7
  import type { HTMLInputAttributes } from 'svelte/elements';
8
8
  import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
9
- import type { BivariantValueHandler, FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler } from '../types/eventHandlers';
9
+ import type { BivariantValueHandler, FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler } from '../types/callbackHandlers';
10
+ import type { DatepickerMode, FocusStyle } from '../types/propOptions';
10
11
  export type DatepickerProps = {
11
12
  value: Date | {
12
13
  start: Date;
@@ -19,7 +20,7 @@ export type DatepickerProps = {
19
20
  id?: string;
20
21
  inputAttributes?: HTMLInputAttributes | undefined;
21
22
  inline?: boolean;
22
- focusStyle?: 'background' | 'outline' | 'none';
23
+ focusStyle?: FocusStyle;
23
24
  fullWidth?: boolean;
24
25
  rounded?: boolean;
25
26
  hasIcon?: boolean;
@@ -29,7 +30,7 @@ export type DatepickerProps = {
29
30
  iconOpticalSize?: IconOpticalSize;
30
31
  iconVariant?: IconVariant;
31
32
  disabled?: boolean;
32
- mode?: 'single' | 'range';
33
+ mode?: DatepickerMode;
33
34
  /** テキスト入力を有効にするか */
34
35
  enableTextInput?: boolean;
35
36
  /** クリック時にポップアップを開くかどうかを有効にするか */
@@ -14,7 +14,7 @@
14
14
  import IconButton from './IconButton.svelte';
15
15
  import { onMount } from 'svelte';
16
16
  import { t } from '../i18n';
17
- import type { BivariantValueHandler } from '../types/eventHandlers';
17
+ import type { BivariantValueHandler } from '../types/callbackHandlers';
18
18
 
19
19
  dayjs.extend(localeData);
20
20
  dayjs.extend(isSameOrBefore);
@@ -4,7 +4,7 @@ import 'dayjs/locale/fr';
4
4
  import 'dayjs/locale/de';
5
5
  import 'dayjs/locale/es';
6
6
  import 'dayjs/locale/zh-cn';
7
- import type { BivariantValueHandler } from '../types/eventHandlers';
7
+ import type { BivariantValueHandler } from '../types/callbackHandlers';
8
8
  export type DatepickerCalendarProps = {
9
9
  value: Date | {
10
10
  start: Date;
@@ -310,7 +310,7 @@
310
310
  align-items: center;
311
311
  justify-content: stretch;
312
312
  min-height: var(--svelte-ui-drawer-header-height);
313
- padding: var(--svelte-ui-drawer-padding);
313
+ padding: var(--svelte-ui-drawer-header-padding);
314
314
  margin-bottom: calc(0px - var(--svelte-ui-drawer-body-padding));
315
315
  }
316
316
  .drawer__header .drawer__title {