@14ch/svelte-ui 0.0.64 → 0.0.65

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.
@@ -5,6 +5,7 @@
5
5
  import Button from './Button.svelte';
6
6
  import { convertToHtml } from '../utils/formatText';
7
7
  import type { SvelteComponent } from 'svelte';
8
+ import type { CloseReason } from '../types/propOptions';
8
9
 
9
10
  // =========================================================================
10
11
  // Props, States & Constants
@@ -35,7 +36,11 @@
35
36
 
36
37
  // イベントハンドラー
37
38
  onSubmit?: () => void;
39
+ /** Called on cancel, including dismissal via Escape or a backdrop click. */
38
40
  onCancel?: () => void;
41
+ onOpen?: () => void;
42
+ /** Called after the close animation completes, with the reason it was closed. */
43
+ onClose?: (reason: CloseReason) => void;
39
44
  };
40
45
 
41
46
  let {
@@ -60,7 +65,9 @@
60
65
 
61
66
  // イベントハンドラー
62
67
  onSubmit = () => {}, // No params for type inference
63
- onCancel = () => {} // No params for type inference
68
+ onCancel = () => {}, // No params for type inference
69
+ onOpen = () => {},
70
+ onClose = () => {}
64
71
  }: ConfirmDialogProps = $props();
65
72
 
66
73
  let dialogRef: SvelteComponent | undefined = $state();
@@ -78,6 +85,14 @@
78
85
  close();
79
86
  };
80
87
 
88
+ const handleClose = (reason: CloseReason): void => {
89
+ // Escape / 背景クリックでの離脱もキャンセルとして扱う
90
+ if (reason !== 'explicit') {
91
+ onCancel();
92
+ }
93
+ onClose(reason);
94
+ };
95
+
81
96
  /** Opens the dialog. */
82
97
  export const open = (): void => {
83
98
  isOpen = true;
@@ -102,6 +117,8 @@
102
117
  {scrollable}
103
118
  {closeIfClickOutside}
104
119
  {focusFirstOnOpen}
120
+ {onOpen}
121
+ onClose={handleClose}
105
122
  id={id ? `${id}-dialog` : undefined}
106
123
  >
107
124
  <div class="confirm-dialog-message">
@@ -1,3 +1,4 @@
1
+ import type { CloseReason } from '../types/propOptions';
1
2
  export type ConfirmDialogProps = {
2
3
  title?: string;
3
4
  description?: string;
@@ -15,7 +16,11 @@ export type ConfirmDialogProps = {
15
16
  closeIfClickOutside?: boolean;
16
17
  focusFirstOnOpen?: boolean;
17
18
  onSubmit?: () => void;
19
+ /** Called on cancel, including dismissal via Escape or a backdrop click. */
18
20
  onCancel?: () => void;
21
+ onOpen?: () => void;
22
+ /** Called after the close animation completes, with the reason it was closed. */
23
+ onClose?: (reason: CloseReason) => void;
19
24
  };
20
25
  declare const ConfirmDialog: import("svelte").Component<ConfirmDialogProps, {
21
26
  /** Opens the dialog. */ open: () => void;
@@ -14,6 +14,7 @@
14
14
  * This prevents state synchronization bugs and ensures consistent behavior.
15
15
  */
16
16
  import type { Snippet } from 'svelte';
17
+ import type { CloseReason } from '../types/propOptions';
17
18
  import Modal from './Modal.svelte';
18
19
  import { getStyleFromNumber } from '../utils/style';
19
20
 
@@ -56,6 +57,11 @@
56
57
  // ARIA/アクセシビリティ
57
58
  ariaLabel?: string;
58
59
  ariaDescribedby?: string;
60
+
61
+ // イベントハンドラー
62
+ onOpen?: () => void;
63
+ /** Called after the close animation completes, with the reason it was closed. */
64
+ onClose?: (reason: CloseReason) => void;
59
65
  };
60
66
 
61
67
  let {
@@ -85,7 +91,11 @@
85
91
 
86
92
  // ARIA/アクセシビリティ
87
93
  ariaLabel,
88
- ariaDescribedby
94
+ ariaDescribedby,
95
+
96
+ // イベントハンドラー
97
+ onOpen = () => {}, // No params for type inference
98
+ onClose = () => {}
89
99
  }: DialogProps = $props();
90
100
 
91
101
  let modalRef: Modal;
@@ -145,6 +155,8 @@
145
155
  {closeIfClickOutside}
146
156
  {restoreFocus}
147
157
  {focusFirstOnOpen}
158
+ {onOpen}
159
+ {onClose}
148
160
  componentType="Dialog"
149
161
  {ariaLabel}
150
162
  {ariaLabelledby}
@@ -11,6 +11,7 @@
11
11
  * This prevents state synchronization bugs and ensures consistent behavior.
12
12
  */
13
13
  import type { Snippet } from 'svelte';
14
+ import type { CloseReason } from '../types/propOptions';
14
15
  export type DialogProps = {
15
16
  header?: Snippet;
16
17
  children?: Snippet;
@@ -36,6 +37,9 @@ export type DialogProps = {
36
37
  focusFirstOnOpen?: boolean;
37
38
  ariaLabel?: string;
38
39
  ariaDescribedby?: string;
40
+ onOpen?: () => void;
41
+ /** Called after the close animation completes, with the reason it was closed. */
42
+ onClose?: (reason: CloseReason) => void;
39
43
  };
40
44
  declare const Dialog: import("svelte").Component<DialogProps, {
41
45
  /** Opens the dialog. */ open: () => void;
@@ -14,6 +14,7 @@
14
14
  * This prevents state synchronization bugs and ensures consistent behavior.
15
15
  */
16
16
  import type { Snippet } from 'svelte';
17
+ import type { CloseReason } from '../types/propOptions';
17
18
  import Modal from './Modal.svelte';
18
19
  import { getStyleFromNumber } from '../utils/style';
19
20
 
@@ -58,6 +59,11 @@
58
59
  // ARIA/アクセシビリティ
59
60
  ariaLabel?: string;
60
61
  ariaDescribedby?: string;
62
+
63
+ // イベントハンドラー
64
+ onOpen?: () => void;
65
+ /** Called after the close animation completes, with the reason it was closed. */
66
+ onClose?: (reason: CloseReason) => void;
61
67
  };
62
68
 
63
69
  let {
@@ -88,7 +94,11 @@
88
94
 
89
95
  // ARIA/アクセシビリティ
90
96
  ariaLabel = 'Drawer',
91
- ariaDescribedby
97
+ ariaDescribedby,
98
+
99
+ // イベントハンドラー
100
+ onOpen = () => {}, // No params for type inference
101
+ onClose = () => {}
92
102
  }: DrawerProps = $props();
93
103
 
94
104
  let modalRef: Modal;
@@ -159,6 +169,8 @@
159
169
  {closeIfClickOutside}
160
170
  {restoreFocus}
161
171
  {focusFirstOnOpen}
172
+ {onOpen}
173
+ {onClose}
162
174
  componentType="Drawer"
163
175
  {ariaLabel}
164
176
  {ariaLabelledby}
@@ -11,6 +11,7 @@
11
11
  * This prevents state synchronization bugs and ensures consistent behavior.
12
12
  */
13
13
  import type { Snippet } from 'svelte';
14
+ import type { CloseReason } from '../types/propOptions';
14
15
  export type DrawerProps = {
15
16
  header?: Snippet;
16
17
  children?: Snippet;
@@ -38,6 +39,9 @@ export type DrawerProps = {
38
39
  focusFirstOnOpen?: boolean;
39
40
  ariaLabel?: string;
40
41
  ariaDescribedby?: string;
42
+ onOpen?: () => void;
43
+ /** Called after the close animation completes, with the reason it was closed. */
44
+ onClose?: (reason: CloseReason) => void;
41
45
  };
42
46
  declare const Drawer: import("svelte").Component<DrawerProps, {
43
47
  /** Opens the drawer. */ open: () => void;
@@ -13,6 +13,7 @@
13
13
  * This prevents state synchronization bugs and ensures consistent behavior.
14
14
  */
15
15
  import type { Snippet } from 'svelte';
16
+ import type { CloseReason } from '../types/propOptions';
16
17
  import { announceOpenClose } from '../utils/accessibility';
17
18
 
18
19
  // =========================================================================
@@ -47,6 +48,11 @@
47
48
  ariaLabel?: string;
48
49
  ariaLabelledby?: string;
49
50
  ariaDescribedby?: string;
51
+
52
+ // イベントハンドラー
53
+ onOpen?: () => void;
54
+ /** Called after the close animation completes, with the reason it was closed. */
55
+ onClose?: (reason: CloseReason) => void;
50
56
  };
51
57
 
52
58
  let {
@@ -72,12 +78,19 @@
72
78
  // ARIA/アクセシビリティ
73
79
  ariaLabel,
74
80
  ariaLabelledby,
75
- ariaDescribedby
81
+ ariaDescribedby,
82
+
83
+ // イベントハンドラー
84
+ onOpen = () => {}, // No params for type inference
85
+ onClose = () => {}
76
86
  }: ModalProps = $props();
77
87
 
78
88
  let dialogRef: HTMLDialogElement;
79
89
  let containerRef: HTMLDivElement;
80
90
  let previousActiveElement: HTMLElement | null = null;
91
+ // isOpen は bind でも変わるため、実際に開いているかは内部で持つ
92
+ let isVisible = false;
93
+ let pendingCloseReason: CloseReason | null = null;
81
94
 
82
95
  // =========================================================================
83
96
  // Effects
@@ -89,7 +102,7 @@
89
102
  if (!closeIfClickOutside) return;
90
103
  if (!containerRef || !event.target) return;
91
104
  if (!containerRef.contains(event.target as Node)) {
92
- close();
105
+ close(undefined, 'outside');
93
106
  }
94
107
  };
95
108
 
@@ -110,7 +123,7 @@
110
123
  // クローズは抑止し、fade-out アニメーション付きの close() を使う。
111
124
  const handleCancel = (event: Event) => {
112
125
  event.preventDefault();
113
- close();
126
+ close(undefined, 'escape');
114
127
  };
115
128
 
116
129
  dialogRef.addEventListener('cancel', handleCancel);
@@ -172,11 +185,14 @@
172
185
  // =========================================================================
173
186
  /** Opens the modal. */
174
187
  export const open = (title?: string): void => {
175
- if (!dialogRef) return;
188
+ if (!dialogRef || isVisible) return;
176
189
 
190
+ isVisible = true;
177
191
  isOpen = true;
178
192
  previousActiveElement = document.activeElement as HTMLElement;
179
193
 
194
+ // 閉じるアニメーションの途中で開き直した場合は close を取り消す
195
+ pendingCloseReason = null;
180
196
  dialogRef.classList.add('fade-in');
181
197
  dialogRef.removeEventListener('animationend', closeEnd);
182
198
  dialogRef.showModal();
@@ -186,14 +202,17 @@
186
202
  dialogRef?.focus();
187
203
  }
188
204
  announceOpenClose(componentType, true, title || ariaLabel || '');
205
+ onOpen?.();
189
206
  }, 0);
190
207
  };
191
208
 
192
209
  /** Closes the modal. */
193
- export const close = (title?: string): void => {
194
- if (!dialogRef) return;
210
+ export const close = (title?: string, reason: CloseReason = 'explicit'): void => {
211
+ if (!dialogRef || !isVisible) return;
195
212
 
213
+ isVisible = false;
196
214
  isOpen = false;
215
+ pendingCloseReason = reason;
197
216
  dialogRef.classList.add('fade-out');
198
217
  dialogRef.addEventListener('animationend', closeEnd, { once: true });
199
218
 
@@ -211,11 +230,18 @@
211
230
  previousActiveElement.focus();
212
231
  }
213
232
  previousActiveElement = null;
233
+
234
+ // アニメーション完了後に onClose を呼ぶ
235
+ if (pendingCloseReason) {
236
+ const reason = pendingCloseReason;
237
+ pendingCloseReason = null;
238
+ onClose?.(reason);
239
+ }
214
240
  };
215
241
 
216
242
  /** Toggles between open and closed. */
217
243
  export const toggle = (title?: string): void => {
218
- if (isOpen) {
244
+ if (isVisible) {
219
245
  close(title);
220
246
  } else {
221
247
  open(title);
@@ -11,6 +11,7 @@
11
11
  * This prevents state synchronization bugs and ensures consistent behavior.
12
12
  */
13
13
  import type { Snippet } from 'svelte';
14
+ import type { CloseReason } from '../types/propOptions';
14
15
  export type ModalProps = {
15
16
  children?: Snippet;
16
17
  /** Internal label used for accessibility announcements. @default 'Modal' */
@@ -29,10 +30,13 @@ export type ModalProps = {
29
30
  ariaLabel?: string;
30
31
  ariaLabelledby?: string;
31
32
  ariaDescribedby?: string;
33
+ onOpen?: () => void;
34
+ /** Called after the close animation completes, with the reason it was closed. */
35
+ onClose?: (reason: CloseReason) => void;
32
36
  };
33
37
  declare const Modal: import("svelte").Component<ModalProps, {
34
38
  /** Opens the modal. */ open: (title?: string) => void;
35
- /** Closes the modal. */ close: (title?: string) => void;
39
+ /** Closes the modal. */ close: (title?: string, reason?: CloseReason) => void;
36
40
  /** @internal Completes the close animation. Called automatically. */ closeEnd: () => void;
37
41
  /** Toggles between open and closed. */ toggle: (title?: string) => void;
38
42
  }, "isOpen">;
package/dist/index.d.ts CHANGED
@@ -85,7 +85,7 @@ export type { NavProps } from './components/Nav.svelte';
85
85
  export type { NavItemProps, NavItemSelectedVariant } from './components/NavItem.svelte';
86
86
  export type { TabProps } from './components/Tab.svelte';
87
87
  export type { TextareaProps } from './components/Textarea.svelte';
88
- export type { PopupPosition, SnackbarPosition, FabPosition, ButtonVariant, ButtonSize, IconPosition, ComponentSize, SnackbarType, SnackbarVariant, BadgeVariant, DatepickerMode, FocusStyle, NavVariant, ChildrenVariant, StepNavOrientation } from './types/propOptions';
88
+ export type { PopupPosition, SnackbarPosition, FabPosition, ButtonVariant, ButtonSize, IconPosition, ComponentSize, SnackbarType, SnackbarVariant, BadgeVariant, DatepickerMode, FocusStyle, NavVariant, ChildrenVariant, StepNavOrientation, CloseReason } from './types/propOptions';
89
89
  export type { MenuItem } from './types/menuItem';
90
90
  export type { StepItem } from './types/stepItem';
91
91
  export type { SegmentedControlItem } from './types/segmentedControlItem';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@14ch/svelte-ui",
3
3
  "description": "Modern Svelte UI components library with TypeScript support",
4
4
  "private": false,
5
- "version": "0.0.64",
5
+ "version": "0.0.65",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "svelte",