@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.
- package/dist/components/Combobox.svelte +4 -3
- package/dist/components/MultiSelect.svelte +43 -15
- 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,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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
/* =============================================
|
|
@@ -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';
|