@14ch/svelte-ui 0.0.39 → 0.0.40

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,27 @@
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>{label}{#if i < selectedLabels.length - 1},{/if}</span>
172
+ {/each}
173
+ </span>
174
+ {:else if placeholder}
175
+ <span class="multi-select__placeholder">{placeholder}</span>
176
+ {/if}
177
+ <span class="multi-select__dropdown-icon" aria-hidden="true">
178
+ <Icon>arrow_drop_down</Icon>
167
179
  </span>
168
- {:else if placeholder}
169
- <span class="multi-select__placeholder">{placeholder}</span>
170
- {/if}
180
+ </span>
171
181
  </button>
172
- <div class="multi-select__dropdown-icon" aria-hidden="true">
173
- <Icon>arrow_drop_down</Icon>
174
- </div>
175
182
 
176
183
  <Popup
177
184
  bind:this={popupRef}
@@ -227,7 +234,7 @@
227
234
  width: 100%;
228
235
  min-height: var(--svelte-ui-select-height);
229
236
  padding: var(--svelte-ui-select-padding);
230
- padding-right: var(--svelte-ui-select-icon-space);
237
+ padding-right: 0;
231
238
  background: transparent;
232
239
  border: none;
233
240
  font-family: inherit;
@@ -245,6 +252,15 @@
245
252
  outline-offset: var(--svelte-ui-focus-outline-offset-inner);
246
253
  }
247
254
 
255
+ .multi-select__inner {
256
+ position: relative;
257
+ flex: 1;
258
+ align-self: stretch;
259
+ display: flex;
260
+ align-items: center;
261
+ padding-right: var(--svelte-ui-select-icon-space);
262
+ }
263
+
248
264
  .multi-select__display-text {
249
265
  display: flex;
250
266
  flex-wrap: wrap;
@@ -267,11 +283,12 @@
267
283
  justify-content: center;
268
284
  align-items: center;
269
285
  position: absolute;
270
- top: 50%;
286
+ top: 0;
271
287
  right: 4px;
288
+ margin-top: calc((var(--svelte-ui-select-height) - 32px) / 2);
289
+ margin-bottom: calc((var(--svelte-ui-select-height) - 32px) / 2);
272
290
  width: 32px;
273
291
  height: 32px;
274
- transform: translateY(-50%);
275
292
  font-size: var(--svelte-ui-select-dropdown-icon-size);
276
293
  color: var(--svelte-ui-select-dropdown-icon-color);
277
294
  pointer-events: none;
@@ -325,7 +342,7 @@
325
342
  * ============================================= */
326
343
  .multi-select.multi-select--inline .multi-select__trigger {
327
344
  padding: inherit;
328
- padding-right: var(--svelte-ui-input-icon-space-inline);
345
+ padding-right: 0;
329
346
  background: transparent;
330
347
  border: none;
331
348
  border-radius: 0;
@@ -333,8 +350,16 @@
333
350
  min-height: auto;
334
351
  line-height: inherit;
335
352
  }
353
+ .multi-select.multi-select--inline .multi-select__inner {
354
+ align-self: auto;
355
+ align-items: flex-start;
356
+ min-height: 1lh;
357
+ padding-right: var(--svelte-ui-input-icon-space-inline);
358
+ }
336
359
  .multi-select.multi-select--inline .multi-select__dropdown-icon {
337
360
  right: 0;
361
+ margin-top: 0;
362
+ margin-bottom: 0;
338
363
  }
339
364
 
340
365
  /* =============================================
@@ -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.40",
6
6
  "type": "module",
7
7
  "keywords": [
8
8
  "svelte",