@14ch/svelte-ui 0.0.38 → 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 +51 -28
- package/dist/components/MultiSelect.svelte.d.ts +4 -4
- 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) => {
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
// =========================================================================
|
|
15
15
|
export type MultiSelectProps = {
|
|
16
16
|
// 基本プロパティ
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
values: OptionValue[];
|
|
17
|
+
/** Selected values array. Supports `bind:value`. */
|
|
18
|
+
value: OptionValue[];
|
|
20
19
|
/** `{ label, value, disabled? }[]` */
|
|
21
20
|
options: Option[];
|
|
22
21
|
|
|
@@ -33,6 +32,7 @@
|
|
|
33
32
|
focusStyle?: 'background' | 'outline' | 'none';
|
|
34
33
|
fullWidth?: boolean;
|
|
35
34
|
rounded?: boolean;
|
|
35
|
+
customStyle?: string;
|
|
36
36
|
|
|
37
37
|
// 状態/動作
|
|
38
38
|
disabled?: boolean;
|
|
@@ -49,8 +49,7 @@
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
let {
|
|
52
|
-
|
|
53
|
-
values = $bindable([]),
|
|
52
|
+
value = $bindable([]),
|
|
54
53
|
options = [],
|
|
55
54
|
|
|
56
55
|
id = `multi-select-${Math.random().toString(36).substring(2, 15)}`,
|
|
@@ -62,6 +61,7 @@
|
|
|
62
61
|
focusStyle = 'outline',
|
|
63
62
|
fullWidth = false,
|
|
64
63
|
rounded = false,
|
|
64
|
+
customStyle = '',
|
|
65
65
|
|
|
66
66
|
disabled = false,
|
|
67
67
|
required = false,
|
|
@@ -85,19 +85,19 @@
|
|
|
85
85
|
const listboxId = $derived(`${id}-listbox`);
|
|
86
86
|
|
|
87
87
|
const selectedLabels = $derived(
|
|
88
|
-
options.filter((o) =>
|
|
88
|
+
options.filter((o) => value.includes(o.value)).map((o) => o.label)
|
|
89
89
|
);
|
|
90
90
|
|
|
91
91
|
// =========================================================================
|
|
92
92
|
// Methods
|
|
93
93
|
// =========================================================================
|
|
94
94
|
const toggleOption = (optionValue: OptionValue) => {
|
|
95
|
-
if (
|
|
96
|
-
|
|
95
|
+
if (value.includes(optionValue)) {
|
|
96
|
+
value = value.filter((v) => v !== optionValue);
|
|
97
97
|
} else {
|
|
98
|
-
|
|
98
|
+
value = [...value, optionValue];
|
|
99
99
|
}
|
|
100
|
-
onchange(
|
|
100
|
+
onchange(value);
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
const handleTriggerClick = () => {
|
|
@@ -129,8 +129,10 @@
|
|
|
129
129
|
triggerWidth = triggerEl?.offsetWidth ?? 0;
|
|
130
130
|
};
|
|
131
131
|
|
|
132
|
-
const handlePopupClose = () => {
|
|
133
|
-
|
|
132
|
+
const handlePopupClose = (reason: 'escape' | 'outside' | 'explicit') => {
|
|
133
|
+
if (reason !== 'outside') {
|
|
134
|
+
triggerEl?.focus();
|
|
135
|
+
}
|
|
134
136
|
};
|
|
135
137
|
</script>
|
|
136
138
|
|
|
@@ -156,24 +158,27 @@
|
|
|
156
158
|
aria-required={required ? 'true' : undefined}
|
|
157
159
|
{tabindex}
|
|
158
160
|
{disabled}
|
|
161
|
+
style={customStyle}
|
|
159
162
|
onfocus={handleFocus}
|
|
160
163
|
onblur={handleBlur}
|
|
161
164
|
onclick={handleTriggerClick}
|
|
162
165
|
onkeydown={handleTriggerKeydown}
|
|
163
166
|
>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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>
|
|
169
179
|
</span>
|
|
170
|
-
|
|
171
|
-
<span class="multi-select__placeholder">{placeholder}</span>
|
|
172
|
-
{/if}
|
|
180
|
+
</span>
|
|
173
181
|
</button>
|
|
174
|
-
<div class="multi-select__dropdown-icon" aria-hidden="true">
|
|
175
|
-
<Icon>arrow_drop_down</Icon>
|
|
176
|
-
</div>
|
|
177
182
|
|
|
178
183
|
<Popup
|
|
179
184
|
bind:this={popupRef}
|
|
@@ -196,7 +201,7 @@
|
|
|
196
201
|
{#each options as option, i (option.value)}
|
|
197
202
|
<li role="presentation" class="multi-select__item">
|
|
198
203
|
<Checkbox
|
|
199
|
-
value={
|
|
204
|
+
value={value.includes(option.value)}
|
|
200
205
|
disabled={option.disabled}
|
|
201
206
|
fullWidth
|
|
202
207
|
customStyle="padding: 8px 12px"
|
|
@@ -229,7 +234,7 @@
|
|
|
229
234
|
width: 100%;
|
|
230
235
|
min-height: var(--svelte-ui-select-height);
|
|
231
236
|
padding: var(--svelte-ui-select-padding);
|
|
232
|
-
padding-right:
|
|
237
|
+
padding-right: 0;
|
|
233
238
|
background: transparent;
|
|
234
239
|
border: none;
|
|
235
240
|
font-family: inherit;
|
|
@@ -247,6 +252,15 @@
|
|
|
247
252
|
outline-offset: var(--svelte-ui-focus-outline-offset-inner);
|
|
248
253
|
}
|
|
249
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
|
+
|
|
250
264
|
.multi-select__display-text {
|
|
251
265
|
display: flex;
|
|
252
266
|
flex-wrap: wrap;
|
|
@@ -269,11 +283,12 @@
|
|
|
269
283
|
justify-content: center;
|
|
270
284
|
align-items: center;
|
|
271
285
|
position: absolute;
|
|
272
|
-
top:
|
|
286
|
+
top: 0;
|
|
273
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);
|
|
274
290
|
width: 32px;
|
|
275
291
|
height: 32px;
|
|
276
|
-
transform: translateY(-50%);
|
|
277
292
|
font-size: var(--svelte-ui-select-dropdown-icon-size);
|
|
278
293
|
color: var(--svelte-ui-select-dropdown-icon-color);
|
|
279
294
|
pointer-events: none;
|
|
@@ -327,7 +342,7 @@
|
|
|
327
342
|
* ============================================= */
|
|
328
343
|
.multi-select.multi-select--inline .multi-select__trigger {
|
|
329
344
|
padding: inherit;
|
|
330
|
-
padding-right:
|
|
345
|
+
padding-right: 0;
|
|
331
346
|
background: transparent;
|
|
332
347
|
border: none;
|
|
333
348
|
border-radius: 0;
|
|
@@ -335,8 +350,16 @@
|
|
|
335
350
|
min-height: auto;
|
|
336
351
|
line-height: inherit;
|
|
337
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
|
+
}
|
|
338
359
|
.multi-select.multi-select--inline .multi-select__dropdown-icon {
|
|
339
360
|
right: 0;
|
|
361
|
+
margin-top: 0;
|
|
362
|
+
margin-bottom: 0;
|
|
340
363
|
}
|
|
341
364
|
|
|
342
365
|
/* =============================================
|
|
@@ -2,9 +2,8 @@ import type { Option, OptionValue } from '../types/options';
|
|
|
2
2
|
import type { BivariantValueHandler, FocusHandler } from '../types/callbackHandlers';
|
|
3
3
|
import type { PopupPosition } from '../types/propOptions';
|
|
4
4
|
export type MultiSelectProps = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
values: OptionValue[];
|
|
5
|
+
/** Selected values array. Supports `bind:value`. */
|
|
6
|
+
value: OptionValue[];
|
|
8
7
|
/** `{ label, value, disabled? }[]` */
|
|
9
8
|
options: Option[];
|
|
10
9
|
id?: string | null;
|
|
@@ -17,6 +16,7 @@ export type MultiSelectProps = {
|
|
|
17
16
|
focusStyle?: 'background' | 'outline' | 'none';
|
|
18
17
|
fullWidth?: boolean;
|
|
19
18
|
rounded?: boolean;
|
|
19
|
+
customStyle?: string;
|
|
20
20
|
disabled?: boolean;
|
|
21
21
|
required?: boolean;
|
|
22
22
|
/** @default 'bottom-left' */
|
|
@@ -25,6 +25,6 @@ export type MultiSelectProps = {
|
|
|
25
25
|
onblur?: FocusHandler;
|
|
26
26
|
onchange?: BivariantValueHandler<OptionValue[]>;
|
|
27
27
|
};
|
|
28
|
-
declare const MultiSelect: import("svelte").Component<MultiSelectProps, {}, "
|
|
28
|
+
declare const MultiSelect: import("svelte").Component<MultiSelectProps, {}, "value">;
|
|
29
29
|
type MultiSelect = ReturnType<typeof MultiSelect>;
|
|
30
30
|
export default MultiSelect;
|
|
@@ -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';
|