@14ch/svelte-ui 0.0.16 → 0.0.18

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 (54) hide show
  1. package/dist/assets/styles/optional/reset.scss +4 -4
  2. package/dist/assets/styles/variables.scss +80 -15
  3. package/dist/components/Button.svelte +10 -12
  4. package/dist/components/Checkbox.svelte +8 -14
  5. package/dist/components/ColorPicker.svelte +29 -18
  6. package/dist/components/ColorPicker.svelte.d.ts +4 -0
  7. package/dist/components/Combobox.svelte +33 -15
  8. package/dist/components/Combobox.svelte.d.ts +3 -2
  9. package/dist/components/ConfirmDialog.svelte +3 -4
  10. package/dist/components/ConfirmDialog.svelte.d.ts +1 -0
  11. package/dist/components/Datepicker.svelte +47 -22
  12. package/dist/components/Datepicker.svelte.d.ts +5 -3
  13. package/dist/components/Dialog.svelte +1 -5
  14. package/dist/components/Drawer.svelte +2 -3
  15. package/dist/components/Fab.svelte +7 -8
  16. package/dist/components/FileUploader.svelte +2 -1
  17. package/dist/components/IconButton.svelte +18 -13
  18. package/dist/components/Input.svelte +46 -45
  19. package/dist/components/Input.svelte.d.ts +0 -1
  20. package/dist/components/Pagination.svelte +3 -3
  21. package/dist/components/Popup.svelte +3 -4
  22. package/dist/components/PopupMenuButton.svelte +6 -4
  23. package/dist/components/Radio.svelte +7 -6
  24. package/dist/components/SegmentedControl.svelte +5 -1
  25. package/dist/components/Select.svelte +9 -6
  26. package/dist/components/Select.svelte.d.ts +1 -0
  27. package/dist/components/Slider.svelte +5 -1
  28. package/dist/components/Slider.svelte.d.ts +1 -0
  29. package/dist/components/Snackbar.svelte +3 -4
  30. package/dist/components/SnackbarItem.svelte +9 -4
  31. package/dist/components/Switch.svelte +8 -6
  32. package/dist/components/Tab.svelte +28 -13
  33. package/dist/components/TabItem.svelte +89 -40
  34. package/dist/components/TabItem.svelte.d.ts +4 -3
  35. package/dist/components/Textarea.svelte +39 -37
  36. package/dist/components/Textarea.svelte.d.ts +1 -1
  37. package/dist/config.d.ts +16 -0
  38. package/dist/config.js +32 -0
  39. package/dist/i18n/index.d.ts +72 -0
  40. package/dist/i18n/locales/de.d.ts +12 -0
  41. package/dist/i18n/locales/de.js +12 -0
  42. package/dist/i18n/locales/en.d.ts +12 -0
  43. package/dist/i18n/locales/en.js +12 -0
  44. package/dist/i18n/locales/es.d.ts +12 -0
  45. package/dist/i18n/locales/es.js +12 -0
  46. package/dist/i18n/locales/fr.d.ts +12 -0
  47. package/dist/i18n/locales/fr.js +12 -0
  48. package/dist/i18n/locales/ja.d.ts +12 -0
  49. package/dist/i18n/locales/ja.js +12 -0
  50. package/dist/i18n/locales/zh-cn.d.ts +12 -0
  51. package/dist/i18n/locales/zh-cn.js +12 -0
  52. package/dist/types/menuItem.d.ts +1 -0
  53. package/dist/utils/snackbar.svelte.d.ts +2 -0
  54. package/package.json +5 -2
@@ -136,10 +136,10 @@ q {
136
136
  quotes: none;
137
137
  }
138
138
 
139
- blockquote:before,
140
- blockquote:after,
141
- q:before,
142
- q:after {
139
+ blockquote::before,
140
+ blockquote::after,
141
+ q::before,
142
+ q::after {
143
143
  content: '';
144
144
  content: none;
145
145
  }
@@ -358,6 +358,21 @@
358
358
  --svelte-ui-segmented-control-selected-text-color: var(--svelte-ui-text-on-filled-color);
359
359
  --svelte-ui-segmented-control-hover-overlay: var(--svelte-ui-hover-overlay);
360
360
 
361
+ /* Tab */
362
+ --svelte-ui-tab-min-height: var(--svelte-ui-form-height);
363
+ --svelte-ui-tab-item-text-color: var(--svelte-ui-text-color);
364
+ --svelte-ui-tab-item-selected-text-color: var(--svelte-ui-primary-color);
365
+ --svelte-ui-tab-item-selected-bar-color: var(--svelte-ui-primary-color);
366
+ --svelte-ui-tab-item-padding-x: 16px;
367
+ --svelte-ui-tab-item-padding-y: 8px;
368
+ --svelte-ui-tab-item-padding: var(--svelte-ui-tab-item-padding-y)
369
+ var(--svelte-ui-tab-item-padding-x);
370
+ --svelte-ui-tab-item-icon-gap: 8px;
371
+ --svelte-ui-tab-item-selected-bar-offset: 0px;
372
+ --svelte-ui-tab-item-selected-bar-height: 4px;
373
+ --svelte-ui-tab-item-selected-bar-radius: 3px 3px 0 0;
374
+ --svelte-ui-tab-item-disabled-opacity: var(--svelte-ui-disabled-opacity);
375
+
361
376
  /* Input */
362
377
  --svelte-ui-input-height: var(--svelte-ui-form-height);
363
378
  --svelte-ui-input-padding: var(--svelte-ui-form-padding);
@@ -380,7 +395,11 @@
380
395
 
381
396
  /* Textarea */
382
397
  --svelte-ui-textarea-min-height: var(--svelte-ui-form-height);
383
- --svelte-ui-textarea-padding: calc((var(--svelte-ui-textarea-min-height) - 1.8em) / 2) 12px;
398
+ --svelte-ui-textarea-line-height: 1.8em;
399
+ --svelte-ui-textarea-padding: calc(
400
+ (var(--svelte-ui-textarea-min-height) - var(--svelte-ui-textarea-line-height)) / 2
401
+ )
402
+ 12px;
384
403
  --svelte-ui-textarea-border-radius: var(--svelte-ui-border-radius);
385
404
  --svelte-ui-textarea-border-radius-rounded: calc(var(--svelte-ui-textarea-min-height) / 2);
386
405
  --svelte-ui-textarea-bg: var(--svelte-ui-form-bg);
@@ -473,10 +492,10 @@
473
492
  --svelte-ui-radio-line-height: 1.2em;
474
493
 
475
494
  /* CheckboxGroup */
476
- --svelte-ui-checkbox-group-gap: 0 24px;
495
+ --svelte-ui-checkbox-group-gap: 8px 24px;
477
496
 
478
497
  /* RadioGroup */
479
- --svelte-ui-radio-group-gap: 0 24px;
498
+ --svelte-ui-radio-group-gap: 8px 24px;
480
499
  --svelte-ui-radio-disabled-opacity: var(--svelte-ui-disabled-opacity);
481
500
  --svelte-ui-radio-border-color: var(--svelte-ui-border-color);
482
501
  --svelte-ui-radio-bg-checked: var(--svelte-ui-radio-checked-color);
@@ -605,7 +624,7 @@
605
624
  --svelte-ui-drawer-gap: 16px;
606
625
  --svelte-ui-drawer-gap-sm: 8px;
607
626
  --svelte-ui-drawer-header-padding: 16px 24px;
608
- --svelte-ui-drawer-body-padding: 0;
627
+ --svelte-ui-drawer-body-padding: 24px;
609
628
  --svelte-ui-drawer-header-height: 56px;
610
629
  --svelte-ui-drawer-title-font-size: 1.4rem;
611
630
  --svelte-ui-drawer-description-font-size: 0.875rem;
@@ -635,39 +654,80 @@
635
654
  --svelte-ui-snackbar-action-padding: 4px 8px;
636
655
  --svelte-ui-snackbar-action-border-radius: 4px;
637
656
  --svelte-ui-snackbar-content-gap: 8px;
657
+ --svelte-ui-snackbar-backdrop-filter: blur(12px);
638
658
 
639
659
  /* Snackbar default */
640
- --svelte-ui-snackbar-default-filled-bg: var(--svelte-ui-surface-inverse-color);
660
+ --svelte-ui-snackbar-default-filled-bg: color-mix(
661
+ in srgb,
662
+ var(--svelte-ui-surface-inverse-color) 85%,
663
+ transparent
664
+ );
641
665
  --svelte-ui-snackbar-default-filled-text-color: var(--svelte-ui-text-inverse-color);
642
- --svelte-ui-snackbar-default-outlined-bg: var(--svelte-ui-surface-color);
666
+ --svelte-ui-snackbar-default-outlined-bg: color-mix(
667
+ in srgb,
668
+ var(--svelte-ui-surface-color) 85%,
669
+ transparent
670
+ );
643
671
  --svelte-ui-snackbar-default-outlined-text-color: var(--svelte-ui-text-color);
644
- --svelte-ui-snackbar-default-outlined-border-color: var(--svelte-ui-border-color);
672
+ --svelte-ui-snackbar-default-outlined-border-color: var(--svelte-ui-surface-inverse-color);
645
673
 
646
674
  /* Snackbar info */
647
- --svelte-ui-snackbar-info-filled-bg: var(--svelte-ui-info-color);
675
+ --svelte-ui-snackbar-info-filled-bg: color-mix(
676
+ in srgb,
677
+ var(--svelte-ui-info-color) 85%,
678
+ transparent
679
+ );
648
680
  --svelte-ui-snackbar-info-filled-text-color: var(--svelte-ui-text-on-filled-color);
649
- --svelte-ui-snackbar-info-outlined-bg: var(--svelte-ui-surface-color);
681
+ --svelte-ui-snackbar-info-outlined-bg: color-mix(
682
+ in srgb,
683
+ var(--svelte-ui-surface-color) 85%,
684
+ transparent
685
+ );
650
686
  --svelte-ui-snackbar-info-outlined-text-color: var(--svelte-ui-info-color);
651
687
  --svelte-ui-snackbar-info-outlined-border-color: var(--svelte-ui-info-color);
652
688
 
653
689
  /* Snackbar success */
654
- --svelte-ui-snackbar-success-filled-bg: var(--svelte-ui-success-color);
690
+ --svelte-ui-snackbar-success-filled-bg: color-mix(
691
+ in srgb,
692
+ var(--svelte-ui-success-color) 85%,
693
+ transparent
694
+ );
655
695
  --svelte-ui-snackbar-success-filled-text-color: var(--svelte-ui-text-on-filled-color);
656
- --svelte-ui-snackbar-success-outlined-bg: var(--svelte-ui-surface-color);
696
+ --svelte-ui-snackbar-success-outlined-bg: color-mix(
697
+ in srgb,
698
+ var(--svelte-ui-surface-color) 85%,
699
+ transparent
700
+ );
657
701
  --svelte-ui-snackbar-success-outlined-text-color: var(--svelte-ui-success-color);
658
702
  --svelte-ui-snackbar-success-outlined-border-color: var(--svelte-ui-success-color);
659
703
 
660
704
  /* Snackbar warning */
661
- --svelte-ui-snackbar-warning-filled-bg: var(--svelte-ui-warning-color);
705
+ --svelte-ui-snackbar-warning-filled-bg: color-mix(
706
+ in srgb,
707
+ var(--svelte-ui-warning-color) 85%,
708
+ transparent
709
+ );
662
710
  --svelte-ui-snackbar-warning-filled-text-color: var(--svelte-ui-text-on-filled-color);
663
- --svelte-ui-snackbar-warning-outlined-bg: var(--svelte-ui-surface-color);
711
+ --svelte-ui-snackbar-warning-outlined-bg: color-mix(
712
+ in srgb,
713
+ var(--svelte-ui-surface-color) 85%,
714
+ transparent
715
+ );
664
716
  --svelte-ui-snackbar-warning-outlined-text-color: var(--svelte-ui-warning-color);
665
717
  --svelte-ui-snackbar-warning-outlined-border-color: var(--svelte-ui-warning-color);
666
718
 
667
719
  /* Snackbar error */
668
- --svelte-ui-snackbar-error-filled-bg: var(--svelte-ui-error-color);
720
+ --svelte-ui-snackbar-error-filled-bg: color-mix(
721
+ in srgb,
722
+ var(--svelte-ui-error-color) 85%,
723
+ transparent
724
+ );
669
725
  --svelte-ui-snackbar-error-filled-text-color: var(--svelte-ui-text-on-filled-color);
670
- --svelte-ui-snackbar-error-outlined-bg: var(--svelte-ui-surface-color);
726
+ --svelte-ui-snackbar-error-outlined-bg: color-mix(
727
+ in srgb,
728
+ var(--svelte-ui-surface-color) 85%,
729
+ transparent
730
+ );
671
731
  --svelte-ui-snackbar-error-outlined-text-color: var(--svelte-ui-error-color);
672
732
  --svelte-ui-snackbar-error-outlined-border-color: var(--svelte-ui-error-color);
673
733
 
@@ -792,6 +852,11 @@
792
852
  --svelte-ui-slider-custom-thumb-focus-shadow: 0 0 0 3px Highlight;
793
853
  --svelte-ui-slider-custom-thumb-color: HighlightText;
794
854
 
855
+ /* Tab */
856
+ --svelte-ui-tab-item-text-color: CanvasText;
857
+ --svelte-ui-tab-item-selected-text-color: Highlight;
858
+ --svelte-ui-tab-item-selected-bar-color: Highlight;
859
+
795
860
  /* Radio */
796
861
  --svelte-ui-radio-border-color: CanvasText;
797
862
  --svelte-ui-radio-border-width: var(--svelte-ui-border-width-thick);
@@ -470,7 +470,7 @@
470
470
  }
471
471
 
472
472
  /* Hover effects */
473
- .button:before {
473
+ .button::before {
474
474
  content: '';
475
475
  display: block;
476
476
  position: absolute;
@@ -486,7 +486,7 @@
486
486
  }
487
487
 
488
488
  @media (hover: hover) {
489
- .button:hover:before {
489
+ .button:hover::before {
490
490
  opacity: 1;
491
491
  }
492
492
  }
@@ -497,16 +497,14 @@
497
497
  outline-offset: 2px;
498
498
  }
499
499
 
500
- /* Disabled state */
500
+ /* Disabled */
501
501
  .button:disabled {
502
502
  opacity: var(--svelte-ui-button-disabled-opacity);
503
- cursor: not-allowed;
504
- pointer-events: none;
505
- }
503
+ cursor: not-allowed !important;
506
504
 
507
- /* Loading state */
508
- .button--loading {
509
- cursor: wait;
505
+ &:hover::before {
506
+ opacity: 0;
507
+ }
510
508
  }
511
509
 
512
510
  /* Content sections */
@@ -536,7 +534,7 @@
536
534
  }
537
535
 
538
536
  .button__popup-icon {
539
- margin-right: -4px;
537
+ margin: -12px -4px -12px 0;
540
538
  display: flex;
541
539
  align-items: center;
542
540
  justify-content: center;
@@ -586,7 +584,7 @@
586
584
 
587
585
  /* Reduced motion */
588
586
  .button--no-motion,
589
- .button--no-motion:before,
587
+ .button--no-motion::before,
590
588
  .button--no-motion .button__label {
591
589
  transition-duration: 0.01s;
592
590
  }
@@ -594,7 +592,7 @@
594
592
  /* Prefers reduced motion */
595
593
  @media (prefers-reduced-motion: reduce) {
596
594
  .button,
597
- .button:before,
595
+ .button::before,
598
596
  .button__label {
599
597
  transition-duration: 0.01s;
600
598
  }
@@ -418,12 +418,13 @@
418
418
  ========================================================================= */
419
419
 
420
420
  /* Disabled state */
421
- .checkbox--disabled input[type='checkbox'] {
422
- cursor: not-allowed;
421
+ .checkbox--disabled {
422
+ opacity: var(--svelte-ui-button-disabled-opacity);
423
423
  }
424
424
 
425
+ .checkbox--disabled input[type='checkbox'],
426
+ .checkbox--disabled .checkbox__icon,
425
427
  .checkbox--disabled .checkbox__label {
426
- opacity: var(--svelte-ui-button-disabled-opacity);
427
428
  cursor: not-allowed;
428
429
  }
429
430
 
@@ -440,6 +441,7 @@
440
441
  input[type='checkbox']:indeterminate + .checkbox__icon::after {
441
442
  content: 'remove';
442
443
  width: var(--svelte-ui-checkbox-icon-width);
444
+ clip-path: inset(0 0 0 0);
443
445
  opacity: 1;
444
446
  transition-delay:
445
447
  var(--svelte-ui-transition-duration-fast), var(--svelte-ui-transition-duration-fast);
@@ -473,10 +475,6 @@
473
475
  font-size: inherit;
474
476
  }
475
477
 
476
- .checkbox--small .checkbox__label {
477
- min-height: var(--svelte-ui-checkbox-min-height-sm);
478
- }
479
-
480
478
  .checkbox--small .checkbox__icon {
481
479
  width: var(--svelte-ui-checkbox-size-sm);
482
480
  height: var(--svelte-ui-checkbox-size-sm);
@@ -490,10 +488,6 @@
490
488
  font-size: inherit;
491
489
  }
492
490
 
493
- .checkbox--large .checkbox__label {
494
- min-height: var(--svelte-ui-checkbox-min-height-lg);
495
- }
496
-
497
491
  .checkbox--large .checkbox__icon {
498
492
  width: var(--svelte-ui-checkbox-size-lg);
499
493
  height: var(--svelte-ui-checkbox-size-lg);
@@ -509,15 +503,15 @@
509
503
 
510
504
  /* Mobile touch targets */
511
505
  @media (hover: none) and (pointer: coarse) {
512
- .checkbox__label {
506
+ .checkbox {
513
507
  min-height: var(--svelte-ui-touch-target);
514
508
  }
515
509
 
516
- .checkbox--small .checkbox__label {
510
+ .checkbox--small {
517
511
  min-height: var(--svelte-ui-touch-target-sm);
518
512
  }
519
513
 
520
- .checkbox--large .checkbox__label {
514
+ .checkbox--large {
521
515
  min-height: var(--svelte-ui-touch-target-lg);
522
516
  }
523
517
  }
@@ -23,12 +23,16 @@
23
23
 
24
24
  // HTML属性系
25
25
  id?: string;
26
+ ariaLabel?: string;
26
27
  inputAttributes?: HTMLInputAttributes | undefined;
27
28
 
28
29
  // スタイル/レイアウト
29
30
  customStyle?: string;
30
31
  focusStyle?: 'background' | 'outline' | 'none';
31
32
  fullWidth?: boolean;
33
+ width?: string | number | null;
34
+ minWidth?: string | number | null;
35
+ maxWidth?: string | number | null;
32
36
  rounded?: boolean;
33
37
 
34
38
  // 状態/動作
@@ -85,12 +89,16 @@
85
89
 
86
90
  // HTML属性系
87
91
  id = `colorpicker-${Math.random().toString(36).substring(2, 15)}`,
92
+ ariaLabel,
88
93
  inputAttributes,
89
94
 
90
95
  // スタイル/レイアウト
91
96
  customStyle = '',
92
97
  focusStyle = 'outline',
93
98
  fullWidth = false,
99
+ width,
100
+ minWidth,
101
+ maxWidth,
94
102
  rounded = false,
95
103
 
96
104
  // 状態/動作
@@ -181,17 +189,19 @@
181
189
  // Methods
182
190
  // =========================================================================
183
191
 
184
- const handleChange = (value: string): void => {
185
- if (value && !value.startsWith('#')) {
186
- localValue = '#' + value;
187
- } else {
188
- localValue = value;
192
+ const handleChange = (newValue: string): void => {
193
+ let normalizedValue = newValue;
194
+
195
+ if (normalizedValue && !normalizedValue.startsWith('#')) {
196
+ normalizedValue = '#' + normalizedValue;
189
197
  }
190
198
 
191
- if (value !== prevValue || localValue !== prevValue) {
192
- value = localValue;
193
- prevValue = value;
194
- onchange(value);
199
+ localValue = normalizedValue;
200
+
201
+ if (normalizedValue !== prevValue || localValue !== prevValue) {
202
+ value = normalizedValue;
203
+ prevValue = normalizedValue;
204
+ onchange(normalizedValue);
195
205
  }
196
206
  };
197
207
 
@@ -352,6 +362,9 @@
352
362
  {clearButtonAriaLabel}
353
363
  {focusStyle}
354
364
  {fullWidth}
365
+ {width}
366
+ {minWidth}
367
+ {maxWidth}
355
368
  {rounded}
356
369
  customStyle={`padding-left: var(--svelte-ui-colorpicker-text-padding-left); ${customStyle}`}
357
370
  {inputAttributes}
@@ -386,6 +399,7 @@
386
399
  <div class="color-picker__trigger">
387
400
  <input
388
401
  type="color"
402
+ aria-label={ariaLabel ?? t('colorpicker.chooseColor')}
389
403
  bind:value
390
404
  onchange={handleValueChange}
391
405
  onfocus={handleFocus}
@@ -393,7 +407,7 @@
393
407
  onclick={handleClick}
394
408
  onkeydown={handleKeydown}
395
409
  {disabled}
396
- class="color-picker__color-input"
410
+ class="color-picker__trigger-input"
397
411
  {...inputAttributes}
398
412
  {...restProps}
399
413
  />
@@ -443,14 +457,13 @@
443
457
  }
444
458
  }
445
459
 
446
- .color-picker__color-input {
460
+ .color-picker__trigger-input {
447
461
  opacity: 0;
448
462
  position: absolute;
449
463
  width: 100%;
450
464
  height: 100%;
451
465
  padding: 0;
452
466
  z-index: 2;
453
- pointer-events: auto;
454
467
  cursor: pointer;
455
468
  }
456
469
 
@@ -458,7 +471,6 @@
458
471
  display: block;
459
472
  width: 100%;
460
473
  height: 100%;
461
- cursor: pointer;
462
474
  position: relative;
463
475
  border-radius: var(--svelte-ui-colorpicker-trigger-border-radius);
464
476
  z-index: 1;
@@ -475,10 +487,9 @@
475
487
  * 状態管理(disabled, focused等)
476
488
  * ============================================= */
477
489
  .color-picker--disabled {
478
- opacity: var(--svelte-ui-input-disabled-opacity);
479
- pointer-events: none;
480
-
481
- .color-picker__color-input {
490
+ /* Input 側は .input--disabledopacity を持つため、ラッパーでは opacity をかけない(二重防止) */
491
+ .color-picker__trigger {
492
+ opacity: var(--svelte-ui-input-disabled-opacity);
482
493
  cursor: not-allowed;
483
494
  pointer-events: none;
484
495
  }
@@ -488,7 +499,7 @@
488
499
  * 状態管理(readonly等)
489
500
  * ============================================= */
490
501
  .color-picker--readonly {
491
- .color-picker__color-input,
502
+ .color-picker__trigger-input,
492
503
  .color-picker__trigger {
493
504
  pointer-events: none;
494
505
  }
@@ -4,10 +4,14 @@ import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, Pointer
4
4
  export type ColorPickerProps = {
5
5
  value: string | null | undefined;
6
6
  id?: string;
7
+ ariaLabel?: string;
7
8
  inputAttributes?: HTMLInputAttributes | undefined;
8
9
  customStyle?: string;
9
10
  focusStyle?: 'background' | 'outline' | 'none';
10
11
  fullWidth?: boolean;
12
+ width?: string | number | null;
13
+ minWidth?: string | number | null;
14
+ maxWidth?: string | number | null;
11
15
  rounded?: boolean;
12
16
  disabled?: boolean;
13
17
  readonly?: boolean;
@@ -36,8 +36,9 @@
36
36
  focusStyle?: 'background' | 'outline' | 'none';
37
37
  placeholder?: string;
38
38
  fullWidth?: boolean;
39
- minWidth?: number | null;
40
- maxWidth?: number | null;
39
+ width?: string | number | null;
40
+ minWidth?: string | number | null;
41
+ maxWidth?: string | number | null;
41
42
  rounded?: boolean;
42
43
 
43
44
  // 状態/動作
@@ -106,8 +107,9 @@
106
107
  focusStyle = 'outline',
107
108
  placeholder = t('combobox.placeholder'),
108
109
  fullWidth = false,
109
- minWidth = inline ? null : 120,
110
- maxWidth = null,
110
+ width,
111
+ minWidth,
112
+ maxWidth,
111
113
  rounded = false,
112
114
 
113
115
  // 状態/動作
@@ -163,6 +165,7 @@
163
165
  let listElement = $state<HTMLDivElement>();
164
166
  let comboboxElement = $state<HTMLDivElement>();
165
167
  let popupRef = $state<any>();
168
+ let isPopupOpen = $state(false);
166
169
  let highlightedIndex = $state(-1);
167
170
  let isFocused = $state(false);
168
171
  let isKeyboardNavigation = $state(false);
@@ -172,6 +175,8 @@
172
175
  // =========================================================================
173
176
  // オプションを選択
174
177
  const selectOption = (option: string) => {
178
+ if (disabled || readonly) return;
179
+
175
180
  value = option;
176
181
  inputValue = option;
177
182
  popupRef?.close();
@@ -190,9 +195,14 @@
190
195
  // input要素のフォーカス/クリック時
191
196
  const handleInputFocus = (event: FocusEvent) => {
192
197
  if (disabled) return;
193
- isFocused = true;
194
- popupRef?.open();
195
- highlightedIndex = -1;
198
+
199
+ // readonly の場合はフォーカスイベントだけ伝播し、ポップアップは開かない
200
+ if (!readonly) {
201
+ isFocused = true;
202
+ popupRef?.open();
203
+ highlightedIndex = -1;
204
+ }
205
+
196
206
  onfocus(event);
197
207
  };
198
208
 
@@ -221,12 +231,14 @@
221
231
 
222
232
  const handleClick = (event: MouseEvent) => {
223
233
  if (disabled) return;
224
- // クリック時にもポップアップを開く
225
- if (!isFocused) {
234
+
235
+ // readonly の場合はクリックイベントだけ伝播し、ポップアップは開かない
236
+ if (!readonly && !isFocused) {
226
237
  isFocused = true;
227
238
  popupRef?.open();
228
239
  highlightedIndex = -1;
229
240
  }
241
+
230
242
  onclick?.(event);
231
243
  };
232
244
 
@@ -234,6 +246,9 @@
234
246
  if (disabled) return;
235
247
  onkeydown(event);
236
248
 
249
+ // readonly の場合はキーボード操作でのポップアップ制御や選択を無効化
250
+ if (readonly) return;
251
+
237
252
  switch (event.key) {
238
253
  case 'ArrowDown':
239
254
  event?.preventDefault?.();
@@ -368,7 +383,7 @@
368
383
  onpointercancel?.(event);
369
384
  };
370
385
 
371
- // Popup が閉じられたときの処理
386
+ // Popup が閉じられたときの処理(isPopupOpen は bind:isOpen で同期される)
372
387
  const handlePopupClose = () => {
373
388
  isFocused = false;
374
389
  highlightedIndex = -1;
@@ -404,12 +419,11 @@
404
419
  {id}
405
420
  class="combobox"
406
421
  class:combobox--full-width={fullWidth}
407
- style="max-width: {maxWidth}px; min-width: {minWidth}px"
408
422
  role="combobox"
409
- aria-expanded={!!popupRef}
410
- aria-controls={listboxId}
423
+ aria-expanded={isPopupOpen}
424
+ aria-controls={isPopupOpen ? listboxId : undefined}
411
425
  aria-haspopup="listbox"
412
- aria-owns={listboxId}
426
+ aria-owns={isPopupOpen ? listboxId : undefined}
413
427
  data-testid="combobox"
414
428
  >
415
429
  <!-- Inputコンポーネントを使用 -->
@@ -423,6 +437,9 @@
423
437
  {focusStyle}
424
438
  {placeholder}
425
439
  {fullWidth}
440
+ {width}
441
+ {minWidth}
442
+ {maxWidth}
426
443
  {disabled}
427
444
  {readonly}
428
445
  {required}
@@ -460,11 +477,12 @@
460
477
  {...restProps}
461
478
  role="textbox"
462
479
  aria-autocomplete="list"
463
- aria-controls={listboxId}
480
+ aria-controls={isPopupOpen ? listboxId : undefined}
464
481
  />
465
482
  <!-- オプションリスト -->
466
483
  <Popup
467
484
  bind:this={popupRef}
485
+ bind:isOpen={isPopupOpen}
468
486
  anchorElement={comboboxElement}
469
487
  position="bottom-left"
470
488
  mobileFullscreen={false}
@@ -13,8 +13,9 @@ export type ComboboxProps = {
13
13
  focusStyle?: 'background' | 'outline' | 'none';
14
14
  placeholder?: string;
15
15
  fullWidth?: boolean;
16
- minWidth?: number | null;
17
- maxWidth?: number | null;
16
+ width?: string | number | null;
17
+ minWidth?: string | number | null;
18
+ maxWidth?: string | number | null;
18
19
  rounded?: boolean;
19
20
  disabled?: boolean;
20
21
  readonly?: boolean;
@@ -22,6 +22,7 @@
22
22
  // スタイル/レイアウト
23
23
  danger?: boolean;
24
24
  width?: string | number;
25
+ scrollable?: boolean;
25
26
 
26
27
  // 状態/動作
27
28
  isOpen?: boolean;
@@ -45,6 +46,7 @@
45
46
  // スタイル/レイアウト
46
47
  danger = false,
47
48
  width = 400,
49
+ scrollable = false,
48
50
 
49
51
  // 状態/動作
50
52
  isOpen = $bindable(false),
@@ -88,6 +90,7 @@
88
90
  bind:isOpen
89
91
  {title}
90
92
  {width}
93
+ {scrollable}
91
94
  {closeIfClickOutside}
92
95
  id={id ? `${id}-dialog` : undefined}
93
96
  >
@@ -108,7 +111,3 @@
108
111
  </Button>
109
112
  {/snippet}
110
113
  </Dialog>
111
-
112
- <style>.confirm-dialog-message {
113
- padding: 16px 0;
114
- }</style>
@@ -6,6 +6,7 @@ export type ConfirmDialogProps = {
6
6
  id?: string;
7
7
  danger?: boolean;
8
8
  width?: string | number;
9
+ scrollable?: boolean;
9
10
  isOpen?: boolean;
10
11
  closeIfClickOutside?: boolean;
11
12
  onSubmit?: () => void;