@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.
- package/dist/components/Combobox.svelte +4 -3
- package/dist/components/MultiSelect.svelte +42 -17
- package/dist/components/MultiSelect.svelte.d.ts +1 -0
- package/dist/components/Popup.svelte +16 -8
- package/dist/components/Popup.svelte.d.ts +3 -3
- package/dist/components/PopupMenu.svelte +2 -2
- package/dist/types/propOptions.d.ts +8 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
/* =============================================
|
|
@@ -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',
|
|
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',
|
|
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
|
-
|
|
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';
|