@14ch/svelte-ui 0.0.13 → 0.0.15

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 (90) hide show
  1. package/dist/assets/styles/variables.scss +3 -7
  2. package/dist/components/Button.svelte +31 -5
  3. package/dist/components/Button.svelte.d.ts +5 -3
  4. package/dist/components/Checkbox.svelte +4 -4
  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 +18 -10
  9. package/dist/components/ColorPicker.svelte.d.ts +4 -4
  10. package/dist/components/Combobox.svelte +28 -8
  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 +8 -4
  19. package/dist/components/Drawer.svelte.d.ts +1 -1
  20. package/dist/components/Fab.svelte +28 -11
  21. package/dist/components/Fab.svelte.d.ts +8 -4
  22. package/dist/components/FileUploader.svelte +1 -1
  23. package/dist/components/FileUploader.svelte.d.ts +1 -1
  24. package/dist/components/Icon.svelte +20 -24
  25. package/dist/components/Icon.svelte.d.ts +1 -1
  26. package/dist/components/IconButton.svelte +4 -3
  27. package/dist/components/IconButton.svelte.d.ts +4 -3
  28. package/dist/components/ImageUploader.svelte +1 -1
  29. package/dist/components/ImageUploader.svelte.d.ts +1 -1
  30. package/dist/components/Input.svelte +110 -99
  31. package/dist/components/Input.svelte.d.ts +5 -3
  32. package/dist/components/Popup.svelte +69 -82
  33. package/dist/components/Popup.svelte.d.ts +3 -3
  34. package/dist/components/PopupMenu.svelte +40 -56
  35. package/dist/components/PopupMenu.svelte.d.ts +3 -3
  36. package/dist/components/PopupMenuButton.svelte +10 -23
  37. package/dist/components/PopupMenuButton.svelte.d.ts +5 -4
  38. package/dist/components/Radio.svelte +3 -2
  39. package/dist/components/Radio.svelte.d.ts +1 -1
  40. package/dist/components/RadioGroup.svelte +1 -1
  41. package/dist/components/RadioGroup.svelte.d.ts +1 -1
  42. package/dist/components/SegmentedControl.svelte +4 -5
  43. package/dist/components/SegmentedControl.svelte.d.ts +1 -1
  44. package/dist/components/Select.svelte +2 -2
  45. package/dist/components/Select.svelte.d.ts +1 -1
  46. package/dist/components/Slider.svelte +2 -3
  47. package/dist/components/Slider.svelte.d.ts +1 -1
  48. package/dist/components/Snackbar.svelte +3 -2
  49. package/dist/components/Snackbar.svelte.d.ts +3 -2
  50. package/dist/components/SnackbarItem.svelte +4 -3
  51. package/dist/components/SnackbarItem.svelte.d.ts +4 -3
  52. package/dist/components/Switch.svelte +2 -4
  53. package/dist/components/Switch.svelte.d.ts +1 -1
  54. package/dist/components/Tab.svelte +1 -0
  55. package/dist/components/TabItem.svelte +24 -3
  56. package/dist/components/TabItem.svelte.d.ts +1 -0
  57. package/dist/components/Textarea.svelte +74 -38
  58. package/dist/components/Textarea.svelte.d.ts +4 -3
  59. package/dist/components/skeleton/SkeletonAvatar.svelte +22 -32
  60. package/dist/components/skeleton/SkeletonAvatar.svelte.d.ts +6 -2
  61. package/dist/components/skeleton/SkeletonButton.svelte +18 -16
  62. package/dist/components/skeleton/SkeletonButton.svelte.d.ts +5 -2
  63. package/dist/components/skeleton/SkeletonHeading.svelte +15 -18
  64. package/dist/components/skeleton/SkeletonHeading.svelte.d.ts +3 -2
  65. package/dist/components/skeleton/SkeletonMedia.svelte +29 -41
  66. package/dist/components/skeleton/SkeletonMedia.svelte.d.ts +8 -2
  67. package/dist/components/skeleton/SkeletonText.svelte +12 -14
  68. package/dist/components/skeleton/SkeletonText.svelte.d.ts +4 -2
  69. package/dist/i18n/index.d.ts +143 -6
  70. package/dist/i18n/index.js +18 -40
  71. package/dist/i18n/locales/de.d.ts +35 -0
  72. package/dist/i18n/locales/de.js +35 -0
  73. package/dist/i18n/locales/es.d.ts +35 -0
  74. package/dist/i18n/locales/es.js +35 -0
  75. package/dist/i18n/locales/fr.d.ts +35 -0
  76. package/dist/i18n/locales/fr.js +35 -0
  77. package/dist/i18n/locales/zh-cn.d.ts +35 -0
  78. package/dist/i18n/locales/zh-cn.js +35 -0
  79. package/dist/index.d.ts +5 -2
  80. package/dist/index.js +1 -0
  81. package/dist/types/menuItem.d.ts +1 -1
  82. package/dist/types/propOptions.d.ts +54 -0
  83. package/dist/types/propOptions.js +5 -0
  84. package/dist/utils/formatText.d.ts +2 -2
  85. package/dist/utils/formatText.js +2 -2
  86. package/dist/utils/popupManager.d.ts +26 -0
  87. package/dist/utils/popupManager.js +34 -0
  88. package/package.json +1 -1
  89. /package/dist/types/{eventHandlers.d.ts → callbackHandlers.d.ts} +0 -0
  90. /package/dist/types/{eventHandlers.js → callbackHandlers.js} +0 -0
@@ -15,7 +15,8 @@
15
15
  TouchHandler,
16
16
  PointerHandler,
17
17
  BivariantValueHandler
18
- } from '../types/eventHandlers';
18
+ } from '../types/callbackHandlers';
19
+ import type { FocusStyle } from '../types/propOptions';
19
20
 
20
21
  // =========================================================================
21
22
  // Props, States & Constants
@@ -23,7 +24,7 @@
23
24
  export type InputProps = {
24
25
  // 基本プロパティ
25
26
  name?: string;
26
- value: string | number;
27
+ value: string | number | null | undefined;
27
28
 
28
29
  // HTML属性系
29
30
  id?: string | null;
@@ -41,7 +42,7 @@
41
42
 
42
43
  // スタイル/レイアウト
43
44
  inline?: boolean;
44
- focusStyle?: 'background' | 'outline' | 'none';
45
+ focusStyle?: FocusStyle;
45
46
  placeholder?: string;
46
47
  fullWidth?: boolean;
47
48
  width?: string | number | null;
@@ -49,6 +50,7 @@
49
50
  maxWidth?: string | number | null;
50
51
  rounded?: boolean;
51
52
  customStyle?: string;
53
+ unit?: string;
52
54
 
53
55
  // アイコン関連
54
56
  rightIcon?: string;
@@ -136,12 +138,13 @@
136
138
  // スタイル/レイアウト
137
139
  inline = false,
138
140
  focusStyle = 'outline',
139
- customStyle = '',
140
141
  fullWidth = false,
141
142
  width = null,
142
143
  minWidth = inline ? null : 120,
143
144
  maxWidth = null,
144
145
  rounded = false,
146
+ customStyle = '',
147
+ unit = '',
145
148
 
146
149
  // アイコン関連
147
150
  rightIcon = undefined,
@@ -380,16 +383,24 @@
380
383
  // =========================================================================
381
384
  // $derived
382
385
  // =========================================================================
383
- const getDisplayValue = (): string => {
386
+ const displayValue = $derived.by(() => {
387
+ if (!value) {
388
+ if (inline && !placeholder) {
389
+ return ' ';
390
+ }
391
+ return '';
392
+ }
384
393
  if (type === 'number' && typeof value === 'number') {
385
394
  return value.toLocaleString();
386
395
  }
387
- return String(value);
388
- };
396
+ return convertToHtmlWithLink(value);
397
+ });
398
+
399
+ const isLinkifyActive = $derived(linkify && (type === 'text' || type === 'url'));
389
400
 
390
401
  const linkHtmlValue = $derived.by(() => {
391
- if (!linkify) return '';
392
- const result = convertToHtmlWithLink(getDisplayValue());
402
+ if (!isLinkifyActive) return '';
403
+ const result = convertToHtmlWithLink(value);
393
404
  return typeof result === 'string' ? result : String(result ?? '');
394
405
  });
395
406
 
@@ -403,7 +414,7 @@
403
414
  input--focus-{focusStyle}
404
415
  input--type-{type}"
405
416
  class:input--inline={inline}
406
- class:input--linkify={linkify}
417
+ class:input--linkify={isLinkifyActive}
407
418
  class:input--auto-resize={inline}
408
419
  class:input--full-width={fullWidth}
409
420
  class:input--clearable={clearable}
@@ -417,12 +428,17 @@
417
428
  data-testid="input"
418
429
  style="width: {widthStyle}; max-width: {maxWidthStyle}; min-width: {minWidthStyle}"
419
430
  >
420
- <!-- inline時の表示用要素(text-overflow: ellipsisが効く) -->
421
- {#if inline}
422
- <div class="input__display-text" data-placeholder={placeholder} style={customStyle}>
423
- {getDisplayValue()}
431
+ <!-- 表示用テキスト -->
432
+ <div class="input__display-text" data-placeholder={placeholder} style={customStyle}>
433
+ <div class="input__display-text-content">
434
+ {@html displayValue}
435
+ {#if type === 'number' && unit !== ''}
436
+ <span class="input__unit-text">
437
+ {unit}
438
+ </span>
439
+ {/if}
424
440
  </div>
425
- {/if}
441
+ </div>
426
442
  <!-- 入力用要素 -->
427
443
  <div class="input__wrapper">
428
444
  <input
@@ -476,9 +492,11 @@
476
492
  {...restProps}
477
493
  />
478
494
  </div>
479
- {#if linkify}
495
+ {#if isLinkifyActive}
480
496
  <div class="input__link-text" style={customStyle}>
481
- {@html linkHtmlValue}
497
+ <div class="input__link-text-content">
498
+ {@html linkHtmlValue}
499
+ </div>
482
500
  </div>
483
501
  {/if}
484
502
  <!-- クリアボタン -->
@@ -486,7 +504,7 @@
486
504
  <div class="input__clear-button">
487
505
  <IconButton
488
506
  ariaLabel={t('input.clear')}
489
- color="var(--svelte-ui-input-text-color)"
507
+ color="var(--svelte-ui-text-color)"
490
508
  onclick={(event) => {
491
509
  event.stopPropagation();
492
510
  clear();
@@ -566,12 +584,19 @@
566
584
  .input {
567
585
  display: inline-block;
568
586
  position: relative;
587
+ vertical-align: top;
569
588
  width: auto;
589
+ height: var(--svelte-ui-input-height);
570
590
  max-width: 100%;
571
591
  height: inherit;
572
592
  }
573
593
 
574
594
  .input__wrapper {
595
+ position: absolute;
596
+ top: 0;
597
+ left: 0;
598
+ width: 100%;
599
+ height: 100%;
575
600
  padding: inherit;
576
601
  border: none;
577
602
  font-size: inherit;
@@ -579,7 +604,7 @@
579
604
  color: inherit;
580
605
  line-height: inherit;
581
606
  text-align: inherit;
582
- opacity: 0;
607
+ opacity: 1;
583
608
  }
584
609
 
585
610
  /* =============================================
@@ -608,63 +633,40 @@
608
633
  }
609
634
  }
610
635
 
611
- .input__display-text {
612
- display: inline-block;
613
- vertical-align: top;
636
+ .input__display-text,
637
+ .input__link-text {
638
+ display: flex;
639
+ align-items: center;
614
640
  width: 100%;
615
641
  min-width: 1em;
616
642
  padding: inherit;
617
- background: inherit;
618
- border: inherit;
619
643
  font-size: inherit;
620
644
  font-weight: inherit;
621
645
  color: inherit;
622
646
  line-height: inherit;
623
647
  text-align: inherit;
624
648
  white-space: nowrap;
625
- vertical-align: top;
626
649
  overflow: hidden;
627
650
  text-overflow: ellipsis;
628
651
  opacity: 1;
629
652
  transition: none;
630
- cursor: text !important;
631
-
632
- &::before {
633
- content: '';
634
- }
635
-
636
- &:empty::before {
637
- content: attr(data-placeholder);
638
- }
639
653
  }
640
654
 
641
- /* リンク表示用オーバーレイ */
642
655
  .input__link-text {
643
656
  position: absolute;
644
657
  top: 0;
645
658
  left: 0;
646
- width: 100%;
647
659
  height: 100%;
648
- display: flex;
649
- align-items: center;
650
- padding: inherit;
651
- background: transparent;
652
- border-radius: inherit;
653
- font-size: inherit;
654
- font-weight: inherit;
655
- color: inherit;
656
- line-height: inherit;
657
- text-align: inherit;
658
- white-space: nowrap;
659
- overflow: hidden;
660
- text-overflow: ellipsis;
661
660
  pointer-events: none;
662
661
  z-index: 1;
663
662
  }
664
663
 
665
664
  .input__link-text :global(a) {
666
665
  pointer-events: auto;
667
- text-decoration: underline;
666
+ }
667
+
668
+ .input__unit-text {
669
+ font-size: var(--svelte-ui-input-unit-font-size);
668
670
  }
669
671
 
670
672
  .input__clear-button {
@@ -739,26 +741,45 @@
739
741
  }
740
742
 
741
743
  /* =============================================
742
- * デザインバリアント:default
744
+ * タイプ別スタイル
743
745
  * ============================================= */
744
- .input:not(.input--inline) {
745
- .input__wrapper {
746
- position: static;
747
- opacity: 1;
746
+ /* type-number */
747
+ .input--type-number {
748
+ .input__display-text,
749
+ .input__link-text {
750
+ justify-content: flex-end;
748
751
  }
752
+ }
753
+
754
+ /* type-password */
755
+ .input--type-password {
756
+ &:not(.input--linkify) input {
757
+ color: inherit;
758
+ caret-color: inherit;
759
+ text-shadow: inherit;
760
+ }
761
+
762
+ .input__display-text {
763
+ opacity: 0;
764
+ }
765
+ }
749
766
 
767
+ /* =============================================
768
+ * デザインバリアント:default
769
+ * ============================================= */
770
+ .input:not(.input--inline) {
750
771
  input {
751
772
  min-height: var(--svelte-ui-input-height);
752
773
  background-color: var(--svelte-ui-input-bg);
753
774
  box-shadow: 0 0 0 var(--svelte-ui-border-width) inset var(--svelte-ui-input-border-color);
754
775
  border: none;
755
776
  border-radius: var(--svelte-ui-input-border-radius);
756
- color: var(--svelte-ui-input-text-color);
757
777
  }
758
778
 
759
779
  input,
760
780
  .input__display-text,
761
781
  .input__link-text {
782
+ height: var(--svelte-ui-input-height);
762
783
  padding: var(--svelte-ui-input-padding);
763
784
  }
764
785
 
@@ -804,34 +825,10 @@
804
825
  }
805
826
  }
806
827
 
807
- /* linkify=true かつフォーカスがないときは、input のテキストカラーだけ透明にして二重描画を防ぐ */
808
- .input--linkify:not(.input--focused) input {
809
- color: transparent;
810
- caret-color: transparent;
811
- text-shadow: none;
812
- }
813
-
814
- /* フォーカス時はリンク用オーバーレイも非表示にして(display:none)、リンクが反応しないようにする */
815
- .input--focused .input__link-text {
816
- display: none;
817
- }
818
-
819
828
  /* =============================================
820
829
  * デザインバリアント:inline
821
830
  * ============================================= */
822
831
  .input--inline {
823
- &.input--type-number .input__display-text {
824
- text-align: right;
825
- }
826
-
827
- .input__wrapper {
828
- position: absolute;
829
- top: 0;
830
- left: 0;
831
- width: 100%;
832
- height: 100%;
833
- }
834
-
835
832
  &.input--has-left-icon {
836
833
  input,
837
834
  .input__display-text,
@@ -863,25 +860,6 @@
863
860
  padding-right: var(--svelte-ui-input-icon-space-double-inline);
864
861
  }
865
862
  }
866
-
867
- &.input--focused {
868
- .input__display-text {
869
- opacity: 0;
870
- }
871
-
872
- .input__wrapper {
873
- opacity: 1;
874
- }
875
- }
876
- }
877
-
878
- /* inline + linkify のときは、display-text を常に隠し、wrapper を常に表示 */
879
- .input--inline.input--linkify .input__display-text {
880
- opacity: 0;
881
- }
882
-
883
- .input--inline.input--linkify .input__wrapper {
884
- opacity: 1;
885
863
  }
886
864
 
887
865
  /* =============================================
@@ -922,6 +900,39 @@
922
900
  }
923
901
  }
924
902
 
903
+ /* not-linkify + not-focused: inputを不可視化、display-textを表示 */
904
+ /* type=password のときは除外(常に input を表示) */
905
+ .input:not(.input--linkify):not(.input--focused):not(.input--type-password) input {
906
+ color: transparent;
907
+ caret-color: transparent;
908
+ text-shadow: none;
909
+ }
910
+
911
+ /* not-linkify + focused: display-textをopacity: 0 */
912
+ .input:not(.input--linkify).input--focused .input__display-text {
913
+ opacity: 0;
914
+ }
915
+
916
+ /* linkify + not-focused: inputを不可視化、display-textをopacity: 0、link-textを表示 */
917
+ .input--linkify:not(.input--focused) input {
918
+ color: transparent;
919
+ caret-color: transparent;
920
+ text-shadow: none;
921
+ }
922
+
923
+ .input--linkify:not(.input--focused) .input__display-text {
924
+ opacity: 0;
925
+ }
926
+
927
+ /* linkify + focused: display-textをopacity: 0、link-textをdisplay: none */
928
+ .input--linkify.input--focused .input__display-text {
929
+ opacity: 0;
930
+ }
931
+
932
+ .input--focused .input__link-text {
933
+ display: none;
934
+ }
935
+
925
936
  /* =============================================
926
937
  * プレースホルダー・テキスト表示
927
938
  * ============================================= */
@@ -1,9 +1,10 @@
1
1
  import type { HTMLInputAttributes } from 'svelte/elements';
2
2
  import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
3
- import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler, BivariantValueHandler } from '../types/eventHandlers';
3
+ import type { FocusHandler, KeyboardHandler, MouseHandler, TouchHandler, PointerHandler, BivariantValueHandler } from '../types/callbackHandlers';
4
+ import type { FocusStyle } from '../types/propOptions';
4
5
  export type InputProps = {
5
6
  name?: string;
6
- value: string | number;
7
+ value: string | number | null | undefined;
7
8
  id?: string | null;
8
9
  type?: 'text' | 'password' | 'email' | 'tel' | 'url' | 'number';
9
10
  tabindex?: number | null;
@@ -17,7 +18,7 @@ export type InputProps = {
17
18
  spellcheck?: boolean | null;
18
19
  inputAttributes?: HTMLInputAttributes | undefined;
19
20
  inline?: boolean;
20
- focusStyle?: 'background' | 'outline' | 'none';
21
+ focusStyle?: FocusStyle;
21
22
  placeholder?: string;
22
23
  fullWidth?: boolean;
23
24
  width?: string | number | null;
@@ -25,6 +26,7 @@ export type InputProps = {
25
26
  maxWidth?: string | number | null;
26
27
  rounded?: boolean;
27
28
  customStyle?: string;
29
+ unit?: string;
28
30
  rightIcon?: string;
29
31
  leftIcon?: string;
30
32
  leftIconAriaLabel?: string;
@@ -18,6 +18,8 @@
18
18
  import { onDestroy, tick, onMount } from 'svelte';
19
19
  import { isMobileDevice, disableBodyScroll, getViewportSize } from '../utils/mobile';
20
20
  import { announceOpenClose } from '../utils/accessibility';
21
+ import { popupManager } from '../utils/popupManager';
22
+ import type { PopupPosition } from '../types/propOptions';
21
23
 
22
24
  // =========================================================================
23
25
  // Props, States & Constants
@@ -27,7 +29,7 @@
27
29
  children: Snippet;
28
30
 
29
31
  // DOM参照
30
- anchorElement: HTMLElement;
32
+ anchorElement: HTMLElement | null | undefined;
31
33
 
32
34
  // 基本プロパティ
33
35
  role?: string;
@@ -36,24 +38,7 @@
36
38
  id?: string;
37
39
 
38
40
  // スタイル/レイアウト
39
- position?:
40
- | 'top'
41
- | 'bottom'
42
- | 'left'
43
- | 'right'
44
- | 'top-left'
45
- | 'top-center'
46
- | 'top-right'
47
- | 'bottom-left'
48
- | 'bottom-center'
49
- | 'bottom-right'
50
- | 'left-top'
51
- | 'left-center'
52
- | 'left-bottom'
53
- | 'right-top'
54
- | 'right-center'
55
- | 'right-bottom'
56
- | 'auto';
41
+ position?: PopupPosition;
57
42
  margin?: number;
58
43
 
59
44
  // 状態/動作
@@ -61,7 +46,6 @@
61
46
  focusTrap?: boolean;
62
47
  restoreFocus?: boolean;
63
48
  mobileFullscreen?: boolean;
64
- mobileBehavior?: 'auto' | 'fullscreen' | 'popup';
65
49
  enableAutoReposition?: boolean;
66
50
 
67
51
  // ARIA/アクセシビリティ
@@ -95,8 +79,7 @@
95
79
  isOpen = $bindable(false),
96
80
  focusTrap = false,
97
81
  restoreFocus = false,
98
- mobileFullscreen = false,
99
- mobileBehavior = 'auto',
82
+ mobileFullscreen = true,
100
83
  enableAutoReposition = true,
101
84
 
102
85
  // ARIA/アクセシビリティ
@@ -110,7 +93,6 @@
110
93
  }: PopupProps = $props();
111
94
 
112
95
  let popupRef: HTMLDivElement | undefined = $state();
113
- let popupId: string = $state(id || `popup-${Math.random().toString(36).substring(2, 15)}`);
114
96
  let previousActiveElement: HTMLElement | null = null;
115
97
  let isMobile: boolean = $state(false);
116
98
  let shouldUseFullscreen: boolean = $state(false);
@@ -121,20 +103,15 @@
121
103
  // =========================================================================
122
104
  onMount(() => {
123
105
  isMobile = isMobileDevice();
124
-
125
- if (mobileBehavior === 'auto') {
126
- shouldUseFullscreen = isMobile;
127
- } else if (mobileBehavior === 'fullscreen') {
128
- shouldUseFullscreen = true;
129
- } else {
130
- shouldUseFullscreen = mobileFullscreen;
131
- }
106
+ // モバイルの場合のみ fullscreen にするかどうか
107
+ shouldUseFullscreen = isMobile && mobileFullscreen;
132
108
  });
133
109
 
134
110
  onDestroy(() => {
135
111
  removeEventListenersToClose();
136
112
  removeKeyboardListener();
137
113
  cleanupMobileFeatures();
114
+ popupManager.unregister(close);
138
115
  });
139
116
 
140
117
  // =========================================================================
@@ -458,18 +435,44 @@
458
435
  };
459
436
 
460
437
  export const open = async () => {
438
+ // 他の開いているPopupをすべて閉じる
439
+ popupManager.closeOthers(close);
440
+
461
441
  previousActiveElement = document.activeElement as HTMLElement;
462
442
 
463
443
  setTimeout(async () => {
464
444
  popupRef?.removeEventListener('animationend', closeEnd);
445
+
446
+ if (shouldUseFullscreen && popupRef) {
447
+ // ボトムシート: showPopover()の前に位置を画面下部に設定
448
+ popupRef.style.position = 'fixed';
449
+ popupRef.style.top = 'auto';
450
+ popupRef.style.bottom = '0';
451
+ popupRef.style.left = '0';
452
+ popupRef.style.right = '0';
453
+ popupRef.style.width = '100%';
454
+ popupRef.style.margin = '0';
455
+ }
456
+
465
457
  popupRef?.showPopover();
466
458
  isOpen = true;
467
459
  addEventListenersToClose();
468
460
  addKeyboardListener();
469
461
 
462
+ popupManager.register(close);
463
+
470
464
  await tick();
471
465
 
472
- if (!shouldUseFullscreen) {
466
+ if (shouldUseFullscreen && popupRef) {
467
+ // ボトムシート: 位置を再確認・再設定(ブラウザが上書きする可能性があるため)
468
+ popupRef.style.position = 'fixed';
469
+ popupRef.style.top = 'auto';
470
+ popupRef.style.bottom = '0';
471
+ popupRef.style.left = '0';
472
+ popupRef.style.right = '0';
473
+ popupRef.style.width = '100%';
474
+ popupRef.style.margin = '0';
475
+ } else {
473
476
  setPosition();
474
477
  await new Promise((resolve) => requestAnimationFrame(resolve));
475
478
  setPosition();
@@ -491,6 +494,8 @@
491
494
  removeEventListenersToClose();
492
495
  removeKeyboardListener();
493
496
  cleanupMobileFeatures();
497
+ popupManager.unregister(close);
498
+ // アニメーション完了後に閉じる(fullscreen/通常共通)
494
499
  popupRef?.addEventListener('animationend', closeEnd, { once: true });
495
500
  // onCloseはアニメーション完了後に呼ぶ(closeEndで実行)
496
501
  };
@@ -506,6 +511,12 @@
506
511
  export const getIsOpen = () => {
507
512
  return isOpen;
508
513
  };
514
+
515
+ // =========================================================================
516
+ // $derived
517
+ // =========================================================================
518
+ const generatedPopupId = $state(`popup-${Math.random().toString(36).substring(2, 15)}`);
519
+ const popupId = $derived(id || generatedPopupId);
509
520
  </script>
510
521
 
511
522
  <div
@@ -513,7 +524,6 @@
513
524
  bind:this={popupRef}
514
525
  class="popup"
515
526
  class:popup--fade-out={!isOpen}
516
- class:popup--mobile={isMobile}
517
527
  class:popup--fullscreen={shouldUseFullscreen}
518
528
  {role}
519
529
  aria-label={ariaLabel}
@@ -525,15 +535,7 @@
525
535
  close();
526
536
  }}
527
537
  >
528
- {#if shouldUseFullscreen}
529
- <div class="popup__mobile">
530
- <div class="popup__mobile-content">
531
- {@render children()}
532
- </div>
533
- </div>
534
- {:else}
535
- {@render children()}
536
- {/if}
538
+ {@render children()}
537
539
  </div>
538
540
 
539
541
  <style>@charset "UTF-8";
@@ -581,46 +583,39 @@
581
583
  }
582
584
 
583
585
  /* =============================================
584
- * Mobile-specific styles
586
+ * Fullscreen variant (Bottom Sheet)
585
587
  * ============================================= */
586
- :popover-open.popup--mobile {
587
- position: fixed;
588
- inset: 0;
589
- width: 100%;
590
- height: 100%;
591
- margin: 0;
588
+ .popup--fullscreen {
589
+ position: fixed !important;
590
+ top: auto !important;
591
+ bottom: 0 !important;
592
+ left: 0 !important;
593
+ right: 0 !important;
594
+ width: 100% !important;
595
+ max-height: 90vh;
596
+ margin: 0 !important;
592
597
  border: none;
593
- border-radius: 0;
594
- box-shadow: none;
595
- background: transparent;
598
+ border-top-left-radius: var(--svelte-ui-popup-mobile-border-radius);
599
+ border-top-right-radius: var(--svelte-ui-popup-mobile-border-radius);
600
+ border-bottom-left-radius: 0;
601
+ border-bottom-right-radius: 0;
602
+ box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.1), 0 -2px 4px -1px rgba(0, 0, 0, 0.06);
603
+ background: var(--svelte-ui-surface-color);
596
604
  z-index: var(--svelte-ui-z-modal);
605
+ overflow-y: auto;
606
+ overflow-x: hidden;
607
+ padding-bottom: env(safe-area-inset-bottom, 0px);
597
608
  }
598
609
 
599
- :popover-open.popup--mobile.popup--fullscreen {
600
- padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
601
- }
602
-
603
- .popup__mobile {
604
- position: absolute;
605
- bottom: 0;
606
- left: 0;
607
- right: 0;
608
- background: var(--svelte-ui-surface-color);
609
- border-top-left-radius: var(--svelte-ui-popup-mobile-border-radius);
610
- border-top-right-radius: var(--svelte-ui-popup-mobile-border-radius);
611
- max-height: 90vh;
612
- overflow: hidden;
610
+ :popover-open.popup--fullscreen {
611
+ transform: translateY(0);
613
612
  animation: slideUpMobile 300ms ease-out;
614
- box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.1), 0 -2px 4px -1px rgba(0, 0, 0, 0.06);
615
613
  }
616
614
 
617
- .popup__mobile-content {
618
- padding: var(--svelte-ui-popup-mobile-margin);
619
- max-height: calc(90vh - 60px);
620
- overflow-y: auto;
615
+ :popover-open.popup--fullscreen.popup--fade-out {
616
+ animation: slideDownMobile 300ms ease-in;
621
617
  }
622
618
 
623
- /* Mobile animations */
624
619
  @keyframes slideUpMobile {
625
620
  from {
626
621
  transform: translateY(100%);
@@ -629,10 +624,6 @@
629
624
  transform: translateY(0);
630
625
  }
631
626
  }
632
- :popover-open.popup--mobile.popup--fullscreen.popup--fade-out .popup__mobile {
633
- animation: slideDownMobile 300ms ease-in;
634
- }
635
-
636
627
  @keyframes slideDownMobile {
637
628
  from {
638
629
  transform: translateY(0);
@@ -641,14 +632,10 @@
641
632
  transform: translateY(100%);
642
633
  }
643
634
  }
644
- /* Responsive design adjustments */
645
635
  @media (max-width: 480px) {
646
- .popup__mobile {
647
- border-radius: 0;
636
+ :popover-open.popup--fullscreen {
648
637
  max-height: 100vh;
649
- }
650
- .popup__mobile-content {
651
- max-height: calc(100vh - 60px);
638
+ border-radius: 0;
652
639
  }
653
640
  }
654
641
  /* Reduced motion support */