@14ch/svelte-ui 0.0.39 → 0.0.41

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.
@@ -292,7 +292,6 @@
292
292
  highlightedIndex = -1;
293
293
  isKeyboardNavigation = false;
294
294
  popupRef?.close();
295
- isFocused = false;
296
295
  break;
297
296
  }
298
297
  };
@@ -396,8 +395,10 @@
396
395
  };
397
396
 
398
397
  // Popup が閉じられたときの処理(isPopupOpen は bind:isOpen で同期される)
399
- const handlePopupClose = () => {
400
- isFocused = false;
398
+ const handlePopupClose = (reason: 'escape' | 'outside' | 'explicit') => {
399
+ if (reason !== 'escape') {
400
+ isFocused = false;
401
+ }
401
402
  highlightedIndex = -1;
402
403
  };
403
404
  const handleBlur = (event: FocusEvent) => {
@@ -32,6 +32,7 @@
32
32
  focusStyle?: 'background' | 'outline' | 'none';
33
33
  fullWidth?: boolean;
34
34
  rounded?: boolean;
35
+ customStyle?: string;
35
36
 
36
37
  // 状態/動作
37
38
  disabled?: boolean;
@@ -60,6 +61,7 @@
60
61
  focusStyle = 'outline',
61
62
  fullWidth = false,
62
63
  rounded = false,
64
+ customStyle = '',
63
65
 
64
66
  disabled = false,
65
67
  required = false,
@@ -127,8 +129,10 @@
127
129
  triggerWidth = triggerEl?.offsetWidth ?? 0;
128
130
  };
129
131
 
130
- const handlePopupClose = () => {
131
- triggerEl?.focus();
132
+ const handlePopupClose = (reason: 'escape' | 'outside' | 'explicit') => {
133
+ if (reason !== 'outside') {
134
+ triggerEl?.focus();
135
+ }
132
136
  };
133
137
  </script>
134
138
 
@@ -154,24 +158,29 @@
154
158
  aria-required={required ? 'true' : undefined}
155
159
  {tabindex}
156
160
  {disabled}
161
+ style={customStyle}
157
162
  onfocus={handleFocus}
158
163
  onblur={handleBlur}
159
164
  onclick={handleTriggerClick}
160
165
  onkeydown={handleTriggerKeydown}
161
166
  >
162
- {#if selectedLabels.length > 0}
163
- <span class="multi-select__display-text">
164
- {#each selectedLabels as label, i}
165
- <span>{label}{#if i < selectedLabels.length - 1},{/if}</span>
166
- {/each}
167
+ <span class="multi-select__inner">
168
+ {#if selectedLabels.length > 0}
169
+ <span class="multi-select__display-text">
170
+ {#each selectedLabels as label, i}
171
+ <span
172
+ >{label}{#if i < selectedLabels.length - 1},{/if}</span
173
+ >
174
+ {/each}
175
+ </span>
176
+ {:else if placeholder}
177
+ <span class="multi-select__placeholder">{placeholder}</span>
178
+ {/if}
179
+ <span class="multi-select__dropdown-icon" aria-hidden="true">
180
+ <Icon>arrow_drop_down</Icon>
167
181
  </span>
168
- {:else if placeholder}
169
- <span class="multi-select__placeholder">{placeholder}</span>
170
- {/if}
182
+ </span>
171
183
  </button>
172
- <div class="multi-select__dropdown-icon" aria-hidden="true">
173
- <Icon>arrow_drop_down</Icon>
174
- </div>
175
184
 
176
185
  <Popup
177
186
  bind:this={popupRef}
@@ -245,6 +254,14 @@
245
254
  outline-offset: var(--svelte-ui-focus-outline-offset-inner);
246
255
  }
247
256
 
257
+ .multi-select__inner {
258
+ position: relative;
259
+ flex: 1;
260
+ align-self: stretch;
261
+ display: flex;
262
+ align-items: center;
263
+ }
264
+
248
265
  .multi-select__display-text {
249
266
  display: flex;
250
267
  flex-wrap: wrap;
@@ -267,11 +284,13 @@
267
284
  justify-content: center;
268
285
  align-items: center;
269
286
  position: absolute;
270
- top: 50%;
287
+ top: 0;
271
288
  right: 4px;
289
+ margin-top: calc((var(--svelte-ui-select-height) - 32px) / 2);
290
+ margin-bottom: calc((var(--svelte-ui-select-height) - 32px) / 2);
291
+ margin-right: calc(0px - var(--svelte-ui-select-icon-space));
272
292
  width: 32px;
273
293
  height: 32px;
274
- transform: translateY(-50%);
275
294
  font-size: var(--svelte-ui-select-dropdown-icon-size);
276
295
  color: var(--svelte-ui-select-dropdown-icon-color);
277
296
  pointer-events: none;
@@ -333,8 +352,17 @@
333
352
  min-height: auto;
334
353
  line-height: inherit;
335
354
  }
355
+ .multi-select.multi-select--inline .multi-select__inner {
356
+ align-self: auto;
357
+ align-items: flex-start;
358
+ min-height: 1lh;
359
+ }
336
360
  .multi-select.multi-select--inline .multi-select__dropdown-icon {
361
+ top: calc((1lh - 32px) / 2);
337
362
  right: 0;
363
+ margin-top: 0;
364
+ margin-bottom: 0;
365
+ margin-right: calc(0px - var(--svelte-ui-input-icon-space-inline));
338
366
  }
339
367
 
340
368
  /* =============================================
@@ -16,6 +16,7 @@ export type MultiSelectProps = {
16
16
  focusStyle?: 'background' | 'outline' | 'none';
17
17
  fullWidth?: boolean;
18
18
  rounded?: boolean;
19
+ customStyle?: string;
19
20
  disabled?: boolean;
20
21
  required?: boolean;
21
22
  /** @default 'bottom-left' */
@@ -19,7 +19,7 @@
19
19
  import { isMobileDevice, disableBodyScroll } from '../utils/mobile';
20
20
  import { announceOpenClose } from '../utils/accessibility';
21
21
  import { popupManager } from '../utils/popupManager';
22
- import type { PopupPosition } from '../types/propOptions';
22
+ import type { CloseReason, PopupPosition } from '../types/propOptions';
23
23
 
24
24
  // =========================================================================
25
25
  // Props, States & Constants
@@ -64,7 +64,7 @@
64
64
 
65
65
  // イベントハンドラー
66
66
  onOpen?: () => void;
67
- onClose?: () => void;
67
+ onClose?: (reason: CloseReason) => void;
68
68
  };
69
69
 
70
70
  let {
@@ -105,6 +105,7 @@
105
105
  let previousActiveElement: HTMLElement | null = null;
106
106
  let isMobile: boolean = $state(false);
107
107
  let bodyScrollCleanup: (() => void) | undefined = $state();
108
+ let pendingCloseReason: CloseReason = 'explicit';
108
109
 
109
110
  // =========================================================================
110
111
  // Lifecycle
@@ -147,7 +148,7 @@
147
148
  switch (event.key) {
148
149
  case 'Escape':
149
150
  event.preventDefault();
150
- close();
151
+ close('escape');
151
152
  break;
152
153
  case 'Tab':
153
154
  if (focusTrap) {
@@ -343,15 +344,17 @@
343
344
  }
344
345
  };
345
346
 
347
+ const closeOnResize = () => close();
348
+
346
349
  const addEventListenersToClose = () => {
347
350
  if (typeof window === 'undefined' || typeof document === 'undefined') return;
348
- window.addEventListener('resize', close);
351
+ window.addEventListener('resize', closeOnResize);
349
352
  document.addEventListener('scroll', handleScroll, true);
350
353
  };
351
354
 
352
355
  const removeEventListenersToClose = () => {
353
356
  if (typeof window === 'undefined' || typeof document === 'undefined') return;
354
- window.removeEventListener('resize', close);
357
+ window.removeEventListener('resize', closeOnResize);
355
358
  document.removeEventListener('scroll', handleScroll, true);
356
359
  };
357
360
 
@@ -413,7 +416,9 @@
413
416
  previousActiveElement = null;
414
417
 
415
418
  // アニメーション完了後にonCloseを呼ぶ
416
- onClose?.();
419
+ const reason = pendingCloseReason;
420
+ pendingCloseReason = 'explicit';
421
+ onClose?.(reason);
417
422
  };
418
423
 
419
424
  const clickOutside = (element: HTMLElement, callbackFunction: () => void) => {
@@ -498,7 +503,10 @@
498
503
  };
499
504
 
500
505
  /** Closes the popup. */
501
- export const close = () => {
506
+ export const close = (reason: CloseReason = 'explicit') => {
507
+ if (pendingCloseReason === 'explicit') {
508
+ pendingCloseReason = reason;
509
+ }
502
510
  isOpen = false;
503
511
  removeEventListenersToClose();
504
512
  removeKeyboardListener();
@@ -544,7 +552,7 @@
544
552
  aria-modal={undefined}
545
553
  id={popupId}
546
554
  use:clickOutside={() => {
547
- close();
555
+ close('outside');
548
556
  }}
549
557
  >
550
558
  {@render children()}
@@ -11,7 +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 { PopupPosition } from '../types/propOptions';
14
+ import type { CloseReason, PopupPosition } from '../types/propOptions';
15
15
  export type PopupProps = {
16
16
  children: Snippet;
17
17
  /** The element the popup is anchored to. */
@@ -37,11 +37,11 @@ export type PopupProps = {
37
37
  ariaLabelledby?: string;
38
38
  ariaDescribedby?: string;
39
39
  onOpen?: () => void;
40
- onClose?: () => void;
40
+ onClose?: (reason: CloseReason) => void;
41
41
  };
42
42
  declare const Popup: import("svelte").Component<PopupProps, {
43
43
  /** Opens the popup. */ open: () => Promise<void>;
44
- /** Closes the popup. */ close: () => void;
44
+ /** Closes the popup. */ close: (reason?: CloseReason) => void;
45
45
  /** Toggles between open and closed. */ toggle: () => void;
46
46
  /** Returns whether the popup is currently open. */ getIsOpen: () => boolean;
47
47
  }, "isOpen">;
@@ -189,10 +189,10 @@
189
189
  announceMenuOpened();
190
190
  };
191
191
 
192
- const handlePopupClose = () => {
192
+ const handlePopupClose = (reason: 'escape' | 'outside' | 'explicit') => {
193
193
  activeIndex = -1;
194
194
 
195
- if (anchorElement) {
195
+ if (anchorElement && reason !== 'outside') {
196
196
  anchorElement.focus();
197
197
  }
198
198
  };
@@ -66,3 +66,11 @@ export type NavVariant = 'vertical' | 'horizontal' | 'mobile';
66
66
  * - `bottom-sheet`: child items appear in a fixed bottom sheet overlay (mobile only)
67
67
  */
68
68
  export type ChildrenVariant = 'popup' | 'accordion' | 'expanded' | 'bar' | 'bottom-sheet';
69
+ /**
70
+ * Reason why a Popup was closed.
71
+ * Used by Popup's onClose callback to allow consumers to differentiate close triggers.
72
+ * - `escape`: closed via Escape key
73
+ * - `outside`: closed by clicking outside the popup
74
+ * - `explicit`: closed programmatically (e.g. popupRef.close(), selecting an option)
75
+ */
76
+ export type CloseReason = 'escape' | 'outside' | 'explicit';
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.39",
5
+ "version": "0.0.41",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "svelte",