@14ch/svelte-ui 0.0.15 → 0.0.17
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/assets/styles/optional/reset.scss +4 -4
- package/dist/assets/styles/variables.scss +85 -22
- package/dist/components/Button.svelte +10 -12
- package/dist/components/Checkbox.svelte +8 -14
- package/dist/components/ColorPicker.svelte +29 -18
- package/dist/components/ColorPicker.svelte.d.ts +4 -0
- package/dist/components/Combobox.svelte +35 -17
- package/dist/components/Combobox.svelte.d.ts +3 -2
- package/dist/components/ConfirmDialog.svelte +3 -4
- package/dist/components/ConfirmDialog.svelte.d.ts +1 -0
- package/dist/components/Datepicker.svelte +47 -22
- package/dist/components/Datepicker.svelte.d.ts +5 -3
- package/dist/components/Dialog.svelte +1 -5
- package/dist/components/Drawer.svelte +2 -3
- package/dist/components/Fab.svelte +7 -8
- package/dist/components/FileUploader.svelte +2 -1
- package/dist/components/IconButton.svelte +19 -14
- package/dist/components/Input.svelte +467 -244
- package/dist/components/Input.svelte.d.ts +16 -1
- package/dist/components/Pagination.svelte +3 -3
- package/dist/components/Popup.svelte +3 -4
- package/dist/components/PopupMenuButton.svelte +6 -4
- package/dist/components/Radio.svelte +7 -6
- package/dist/components/SegmentedControl.svelte +5 -1
- package/dist/components/Select.svelte +9 -6
- package/dist/components/Select.svelte.d.ts +1 -0
- package/dist/components/Slider.svelte +5 -1
- package/dist/components/Slider.svelte.d.ts +1 -0
- package/dist/components/Snackbar.svelte +3 -4
- package/dist/components/SnackbarItem.svelte +9 -4
- package/dist/components/Switch.svelte +8 -6
- package/dist/components/Tab.svelte +28 -13
- package/dist/components/TabItem.svelte +89 -40
- package/dist/components/TabItem.svelte.d.ts +4 -3
- package/dist/components/Textarea.svelte +261 -186
- package/dist/components/Textarea.svelte.d.ts +8 -4
- package/dist/components/skeleton/Skeleton.svelte +48 -6
- package/dist/i18n/index.d.ts +120 -0
- package/dist/i18n/locales/de.d.ts +20 -0
- package/dist/i18n/locales/de.js +22 -2
- package/dist/i18n/locales/en.d.ts +20 -0
- package/dist/i18n/locales/en.js +22 -2
- package/dist/i18n/locales/es.d.ts +20 -0
- package/dist/i18n/locales/es.js +22 -2
- package/dist/i18n/locales/fr.d.ts +20 -0
- package/dist/i18n/locales/fr.js +22 -2
- package/dist/i18n/locales/ja.d.ts +20 -0
- package/dist/i18n/locales/ja.js +22 -2
- package/dist/i18n/locales/zh-cn.d.ts +20 -0
- package/dist/i18n/locales/zh-cn.js +22 -2
- package/dist/types/menuItem.d.ts +1 -0
- package/dist/utils/snackbar.svelte.d.ts +2 -0
- package/package.json +1 -1
|
@@ -13,7 +13,8 @@ export type InputProps = {
|
|
|
13
13
|
min?: number | null;
|
|
14
14
|
max?: number | null;
|
|
15
15
|
step?: number | null;
|
|
16
|
-
|
|
16
|
+
decimalPlaces?: number | null;
|
|
17
|
+
enableThousandsSeparator?: boolean;
|
|
17
18
|
autocomplete?: HTMLInputElement['autocomplete'] | null;
|
|
18
19
|
spellcheck?: boolean | null;
|
|
19
20
|
inputAttributes?: HTMLInputAttributes | undefined;
|
|
@@ -41,6 +42,8 @@ export type InputProps = {
|
|
|
41
42
|
required?: boolean;
|
|
42
43
|
clearable?: boolean;
|
|
43
44
|
linkify?: boolean;
|
|
45
|
+
enablePasswordVisibilityToggle?: boolean;
|
|
46
|
+
enableNumberStepper?: boolean;
|
|
44
47
|
onfocus?: FocusHandler;
|
|
45
48
|
onblur?: FocusHandler;
|
|
46
49
|
onkeydown?: KeyboardHandler;
|
|
@@ -68,6 +71,18 @@ export type InputProps = {
|
|
|
68
71
|
oninput?: BivariantValueHandler<string | number>;
|
|
69
72
|
onRightIconClick?: MouseHandler;
|
|
70
73
|
onLeftIconClick?: MouseHandler;
|
|
74
|
+
onRightIconMouseDown?: MouseHandler;
|
|
75
|
+
onLeftIconMouseDown?: MouseHandler;
|
|
76
|
+
onRightIconMouseUp?: MouseHandler;
|
|
77
|
+
onLeftIconMouseUp?: MouseHandler;
|
|
78
|
+
onRightIconMouseLeave?: MouseHandler;
|
|
79
|
+
onLeftIconMouseLeave?: MouseHandler;
|
|
80
|
+
onRightIconTouchStart?: TouchHandler;
|
|
81
|
+
onLeftIconTouchStart?: TouchHandler;
|
|
82
|
+
onRightIconTouchEnd?: TouchHandler;
|
|
83
|
+
onLeftIconTouchEnd?: TouchHandler;
|
|
84
|
+
onRightIconTouchCancel?: TouchHandler;
|
|
85
|
+
onLeftIconTouchCancel?: TouchHandler;
|
|
71
86
|
[key: string]: any;
|
|
72
87
|
};
|
|
73
88
|
declare const Input: import("svelte").Component<InputProps, {
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
color: var(--svelte-ui-pagination-button-text-color);
|
|
220
220
|
transition-duration: 0s;
|
|
221
221
|
}
|
|
222
|
-
.pagination__button
|
|
222
|
+
.pagination__button::before {
|
|
223
223
|
content: '';
|
|
224
224
|
display: block;
|
|
225
225
|
position: absolute;
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
@media (hover: hover) {
|
|
238
|
-
.pagination__button:hover
|
|
238
|
+
.pagination__button:hover::before {
|
|
239
239
|
opacity: 1;
|
|
240
240
|
}
|
|
241
241
|
}
|
|
@@ -252,7 +252,7 @@
|
|
|
252
252
|
pointer-events: none;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
.pagination__button:disabled
|
|
255
|
+
.pagination__button:disabled::before {
|
|
256
256
|
opacity: 0;
|
|
257
257
|
}
|
|
258
258
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import type { Snippet } from 'svelte';
|
|
18
18
|
import { onDestroy, tick, onMount } from 'svelte';
|
|
19
|
-
import { isMobileDevice, disableBodyScroll
|
|
19
|
+
import { isMobileDevice, disableBodyScroll } from '../utils/mobile';
|
|
20
20
|
import { announceOpenClose } from '../utils/accessibility';
|
|
21
21
|
import { popupManager } from '../utils/popupManager';
|
|
22
22
|
import type { PopupPosition } from '../types/propOptions';
|
|
@@ -95,7 +95,6 @@
|
|
|
95
95
|
let popupRef: HTMLDivElement | undefined = $state();
|
|
96
96
|
let previousActiveElement: HTMLElement | null = null;
|
|
97
97
|
let isMobile: boolean = $state(false);
|
|
98
|
-
let shouldUseFullscreen: boolean = $state(false);
|
|
99
98
|
let bodyScrollCleanup: (() => void) | undefined = $state();
|
|
100
99
|
|
|
101
100
|
// =========================================================================
|
|
@@ -103,8 +102,6 @@
|
|
|
103
102
|
// =========================================================================
|
|
104
103
|
onMount(() => {
|
|
105
104
|
isMobile = isMobileDevice();
|
|
106
|
-
// モバイルの場合のみ fullscreen にするかどうか
|
|
107
|
-
shouldUseFullscreen = isMobile && mobileFullscreen;
|
|
108
105
|
});
|
|
109
106
|
|
|
110
107
|
onDestroy(() => {
|
|
@@ -117,6 +114,7 @@
|
|
|
117
114
|
// =========================================================================
|
|
118
115
|
// Effects
|
|
119
116
|
// =========================================================================
|
|
117
|
+
|
|
120
118
|
$effect(() => {
|
|
121
119
|
if (isOpen) {
|
|
122
120
|
if (popupRef && popupRef.matches(':popover-open')) {
|
|
@@ -517,6 +515,7 @@
|
|
|
517
515
|
// =========================================================================
|
|
518
516
|
const generatedPopupId = $state(`popup-${Math.random().toString(36).substring(2, 15)}`);
|
|
519
517
|
const popupId = $derived(id || generatedPopupId);
|
|
518
|
+
const shouldUseFullscreen = $derived(isMobile && mobileFullscreen);
|
|
520
519
|
</script>
|
|
521
520
|
|
|
522
521
|
<div
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import IconButton from './IconButton.svelte';
|
|
5
5
|
import PopupMenu from './PopupMenu.svelte';
|
|
6
|
+
import { t } from '../i18n';
|
|
6
7
|
import type { MenuItem } from '../types/menuItem';
|
|
7
8
|
import type { SvelteComponent, Snippet } from 'svelte';
|
|
8
9
|
import type {
|
|
@@ -138,6 +139,7 @@
|
|
|
138
139
|
let anchorRef: HTMLElement | undefined = $state();
|
|
139
140
|
let popupMenuRef: SvelteComponent | undefined = $state();
|
|
140
141
|
let buttonId: string = $state(`menu-button-${Math.random().toString(36).substring(2, 15)}`);
|
|
142
|
+
const resolvedMenuId = $derived(`${id ?? buttonId}-menu`);
|
|
141
143
|
|
|
142
144
|
// =========================================================================
|
|
143
145
|
// Methods
|
|
@@ -289,7 +291,7 @@
|
|
|
289
291
|
|
|
290
292
|
<div class="button-block" bind:this={anchorRef} {id}>
|
|
291
293
|
<IconButton
|
|
292
|
-
{ariaLabel}
|
|
294
|
+
ariaLabel={ariaLabel ?? t('popupMenuButton.openMenu')}
|
|
293
295
|
{disabled}
|
|
294
296
|
{variant}
|
|
295
297
|
{size}
|
|
@@ -297,7 +299,7 @@
|
|
|
297
299
|
{rounded}
|
|
298
300
|
aria-haspopup="menu"
|
|
299
301
|
aria-expanded={popupMenuRef?.isOpen ? 'true' : 'false'}
|
|
300
|
-
aria-controls={popupMenuRef ? `${
|
|
302
|
+
aria-controls={popupMenuRef ? `${resolvedMenuId}-popup` : undefined}
|
|
301
303
|
onclick={handleClick}
|
|
302
304
|
onfocus={handleFocus}
|
|
303
305
|
onblur={handleBlur}
|
|
@@ -335,10 +337,10 @@
|
|
|
335
337
|
bind:this={popupMenuRef}
|
|
336
338
|
{menuItems}
|
|
337
339
|
anchorElement={anchorRef}
|
|
338
|
-
{ariaLabel}
|
|
340
|
+
ariaLabel={ariaLabel ?? t('popupMenuButton.openMenu')}
|
|
339
341
|
{position}
|
|
340
342
|
{mobileFullscreen}
|
|
341
|
-
id={
|
|
343
|
+
id={resolvedMenuId}
|
|
342
344
|
/>
|
|
343
345
|
|
|
344
346
|
<style>.button-block {
|
|
@@ -451,12 +451,13 @@
|
|
|
451
451
|
/* =============================================
|
|
452
452
|
* Status
|
|
453
453
|
* ============================================= */
|
|
454
|
-
.radio--disabled
|
|
455
|
-
|
|
454
|
+
.radio--disabled {
|
|
455
|
+
opacity: var(--svelte-ui-button-disabled-opacity);
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
.radio--disabled input[type='radio'],
|
|
459
|
+
.radio--disabled .radio__icon,
|
|
458
460
|
.radio--disabled .radio__label {
|
|
459
|
-
opacity: var(--svelte-ui-button-disabled-opacity);
|
|
460
461
|
cursor: not-allowed;
|
|
461
462
|
}
|
|
462
463
|
|
|
@@ -538,15 +539,15 @@
|
|
|
538
539
|
|
|
539
540
|
/* Mobile touch targets */
|
|
540
541
|
@media (hover: none) and (pointer: coarse) {
|
|
541
|
-
.
|
|
542
|
+
.radio {
|
|
542
543
|
min-height: var(--svelte-ui-touch-target);
|
|
543
544
|
}
|
|
544
545
|
|
|
545
|
-
.radio--small
|
|
546
|
+
.radio--small {
|
|
546
547
|
min-height: var(--svelte-ui-touch-target-sm);
|
|
547
548
|
}
|
|
548
549
|
|
|
549
|
-
.radio--large
|
|
550
|
+
.radio--large {
|
|
550
551
|
min-height: var(--svelte-ui-touch-target-lg);
|
|
551
552
|
}
|
|
552
553
|
}
|
|
@@ -507,7 +507,11 @@
|
|
|
507
507
|
|
|
508
508
|
.segmented-control--disabled {
|
|
509
509
|
opacity: var(--svelte-ui-button-disabled-opacity);
|
|
510
|
-
|
|
510
|
+
cursor: not-allowed;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
.segmented-control--disabled .segmented-control__label {
|
|
514
|
+
cursor: not-allowed;
|
|
511
515
|
}
|
|
512
516
|
|
|
513
517
|
.segmented-control__item {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import Icon from './Icon.svelte';
|
|
5
|
+
import { t } from '../i18n';
|
|
5
6
|
import type { HTMLSelectAttributes } from 'svelte/elements';
|
|
6
7
|
import type {
|
|
7
8
|
FocusHandler,
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
|
|
25
26
|
// HTML属性系
|
|
26
27
|
id?: string | null;
|
|
28
|
+
ariaLabel?: string;
|
|
27
29
|
tabindex?: number | null;
|
|
28
30
|
placeholder?: string;
|
|
29
31
|
selectAttributes?: HTMLSelectAttributes | undefined;
|
|
@@ -88,6 +90,7 @@
|
|
|
88
90
|
|
|
89
91
|
// HTML属性系
|
|
90
92
|
id = `select-${Math.random().toString(36).substring(2, 15)}`,
|
|
93
|
+
ariaLabel,
|
|
91
94
|
tabindex = null,
|
|
92
95
|
placeholder = '',
|
|
93
96
|
selectAttributes,
|
|
@@ -294,6 +297,7 @@ select--focus-{focusStyle}"
|
|
|
294
297
|
bind:this={selectRef}
|
|
295
298
|
{id}
|
|
296
299
|
{name}
|
|
300
|
+
aria-label={ariaLabel ?? t('select.accessibleName')}
|
|
297
301
|
bind:value
|
|
298
302
|
{disabled}
|
|
299
303
|
{required}
|
|
@@ -431,16 +435,15 @@ select--focus-{focusStyle}"
|
|
|
431
435
|
* ============================================= */
|
|
432
436
|
.select--disabled {
|
|
433
437
|
opacity: var(--svelte-ui-input-disabled-opacity);
|
|
434
|
-
|
|
438
|
+
cursor: not-allowed;
|
|
439
|
+
}
|
|
435
440
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
441
|
+
.select--disabled select {
|
|
442
|
+
cursor: not-allowed;
|
|
439
443
|
}
|
|
440
444
|
|
|
441
445
|
select:disabled {
|
|
442
|
-
opacity:
|
|
443
|
-
cursor: not-allowed;
|
|
446
|
+
opacity: 1;
|
|
444
447
|
}
|
|
445
448
|
|
|
446
449
|
/* =============================================
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
5
5
|
import { getStyleFromNumber } from '../utils/style';
|
|
6
|
+
import { t } from '../i18n';
|
|
6
7
|
import type {
|
|
7
8
|
FocusHandler,
|
|
8
9
|
KeyboardHandler,
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
id?: string;
|
|
22
23
|
|
|
23
24
|
// HTML属性系
|
|
25
|
+
ariaLabel?: string;
|
|
24
26
|
min?: number;
|
|
25
27
|
max?: number;
|
|
26
28
|
step?: number;
|
|
@@ -85,6 +87,7 @@
|
|
|
85
87
|
id = `slider-${Math.random().toString(36).substring(2, 15)}`,
|
|
86
88
|
|
|
87
89
|
// HTML属性系
|
|
90
|
+
ariaLabel,
|
|
88
91
|
min = 0,
|
|
89
92
|
max = 100,
|
|
90
93
|
step = 1,
|
|
@@ -317,6 +320,7 @@
|
|
|
317
320
|
<input
|
|
318
321
|
{id}
|
|
319
322
|
{name}
|
|
323
|
+
aria-label={ariaLabel ?? t('slider.accessibleName')}
|
|
320
324
|
bind:this={ref}
|
|
321
325
|
bind:value
|
|
322
326
|
type="range"
|
|
@@ -444,7 +448,7 @@
|
|
|
444
448
|
* ============================================= */
|
|
445
449
|
.slider--disabled {
|
|
446
450
|
opacity: var(--svelte-ui-input-disabled-opacity);
|
|
447
|
-
|
|
451
|
+
cursor: not-allowed;
|
|
448
452
|
}
|
|
449
453
|
|
|
450
454
|
.slider--disabled .slider__input {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<!-- Snackbar.svelte -->
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
|
-
import { onMount } from 'svelte';
|
|
5
4
|
import { snackbarManager, type SnackbarItem as SnackbarData } from '../utils/snackbar.svelte';
|
|
6
5
|
import SnackbarItem from './SnackbarItem.svelte';
|
|
7
6
|
import type { SnackbarPosition, SnackbarVariant } from '../types/propOptions';
|
|
@@ -30,10 +29,10 @@
|
|
|
30
29
|
}: SnackbarProps = $props();
|
|
31
30
|
|
|
32
31
|
// =========================================================================
|
|
33
|
-
//
|
|
32
|
+
// $effect
|
|
34
33
|
// =========================================================================
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
$effect(() => {
|
|
37
36
|
snackbarManager.setDefaults({
|
|
38
37
|
position,
|
|
39
38
|
maxVisible,
|
|
@@ -64,7 +63,7 @@
|
|
|
64
63
|
id={item.id}
|
|
65
64
|
variant={item.variant}
|
|
66
65
|
position={item.position}
|
|
67
|
-
iconVariant=
|
|
66
|
+
iconVariant={item.iconVariant ?? 'outlined'}
|
|
68
67
|
color={item.color}
|
|
69
68
|
textColor={item.textColor}
|
|
70
69
|
duration={item.duration}
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
</div>
|
|
167
167
|
{/if}
|
|
168
168
|
|
|
169
|
-
<div class="
|
|
169
|
+
<div class="snackbar-item__body">
|
|
170
170
|
{#if children}
|
|
171
171
|
{@render children()}
|
|
172
172
|
{:else}
|
|
@@ -176,8 +176,10 @@
|
|
|
176
176
|
|
|
177
177
|
{#if actionLabel && onAction}
|
|
178
178
|
<Button
|
|
179
|
-
variant=
|
|
180
|
-
color=
|
|
179
|
+
variant={variant === 'filled' ? 'outlined' : 'filled'}
|
|
180
|
+
color={variant === 'filled'
|
|
181
|
+
? 'var(--svelte-ui-text-on-filled-color)'
|
|
182
|
+
: `var(--svelte-ui-snackbar-${type}-filled-bg)`}
|
|
181
183
|
onclick={handleAction}
|
|
182
184
|
aria-label="{actionLabel} - {message}"
|
|
183
185
|
>
|
|
@@ -280,6 +282,8 @@
|
|
|
280
282
|
|
|
281
283
|
border-radius: var(--svelte-ui-snackbar-border-radius);
|
|
282
284
|
box-shadow: var(--svelte-ui-snackbar-shadow);
|
|
285
|
+
backdrop-filter: var(--svelte-ui-snackbar-backdrop-filter);
|
|
286
|
+
-webkit-backdrop-filter: var(--svelte-ui-snackbar-backdrop-filter);
|
|
283
287
|
|
|
284
288
|
font-size: var(--svelte-ui-snackbar-font-size);
|
|
285
289
|
line-height: var(--svelte-ui-snackbar-line-height);
|
|
@@ -308,12 +312,13 @@
|
|
|
308
312
|
flex-shrink: 0;
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
.snackbar-
|
|
315
|
+
.snackbar-item__body {
|
|
312
316
|
flex: 1;
|
|
313
317
|
min-width: 0;
|
|
314
318
|
}
|
|
315
319
|
|
|
316
320
|
.snackbar-item__message {
|
|
321
|
+
flex-grow: 1;
|
|
317
322
|
word-break: break-word;
|
|
318
323
|
}
|
|
319
324
|
|
|
@@ -340,6 +340,10 @@
|
|
|
340
340
|
contain: layout;
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
.switch--disabled {
|
|
344
|
+
cursor: not-allowed;
|
|
345
|
+
}
|
|
346
|
+
|
|
343
347
|
.switch-input {
|
|
344
348
|
position: absolute;
|
|
345
349
|
opacity: 0;
|
|
@@ -383,6 +387,7 @@
|
|
|
383
387
|
}
|
|
384
388
|
.switch--disabled .switch__track {
|
|
385
389
|
opacity: var(--svelte-ui-switch-disabled-opacity);
|
|
390
|
+
cursor: not-allowed;
|
|
386
391
|
}
|
|
387
392
|
|
|
388
393
|
.switch-thumb {
|
|
@@ -398,9 +403,6 @@
|
|
|
398
403
|
.switch--checked .switch-thumb {
|
|
399
404
|
transform: translateX(calc(var(--switch-width) - var(--switch-thumb-size) - var(--switch-thumb-margin) * 2));
|
|
400
405
|
}
|
|
401
|
-
.switch--disabled .switch-thumb {
|
|
402
|
-
opacity: var(--svelte-ui-switch-disabled-opacity);
|
|
403
|
-
}
|
|
404
406
|
|
|
405
407
|
/* =========================================================================
|
|
406
408
|
* Size Variants
|
|
@@ -445,13 +447,13 @@
|
|
|
445
447
|
* ========================================================================= */
|
|
446
448
|
/* Mobile touch targets */
|
|
447
449
|
@media (hover: none) and (pointer: coarse) {
|
|
448
|
-
.
|
|
450
|
+
.switch {
|
|
449
451
|
min-height: var(--svelte-ui-touch-target);
|
|
450
452
|
}
|
|
451
|
-
.switch--small
|
|
453
|
+
.switch--small {
|
|
452
454
|
min-height: var(--svelte-ui-touch-target-sm);
|
|
453
455
|
}
|
|
454
|
-
.switch--large
|
|
456
|
+
.switch--large {
|
|
455
457
|
min-height: var(--svelte-ui-touch-target-lg);
|
|
456
458
|
}
|
|
457
459
|
}
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
customPathMatcher,
|
|
33
33
|
currentPath,
|
|
34
34
|
|
|
35
|
-
//
|
|
36
|
-
textColor
|
|
37
|
-
selectedTextColor
|
|
38
|
-
selectedBarColor
|
|
35
|
+
// スタイル/レイアウト(未指定時は TabItem が variables の tab 用変数を直接参照)
|
|
36
|
+
textColor,
|
|
37
|
+
selectedTextColor,
|
|
38
|
+
selectedBarColor,
|
|
39
39
|
|
|
40
40
|
// ARIA/アクセシビリティ
|
|
41
41
|
ariaLabel = 'Tabs',
|
|
@@ -60,6 +60,11 @@
|
|
|
60
60
|
return '';
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
+
// currentPath が渡されたときやマウント時に選択状態を反映
|
|
64
|
+
$effect(() => {
|
|
65
|
+
resolvedCurrentPath = getCurrentPath();
|
|
66
|
+
});
|
|
67
|
+
|
|
63
68
|
afterNavigate(() => {
|
|
64
69
|
resolvedCurrentPath = getCurrentPath();
|
|
65
70
|
});
|
|
@@ -104,40 +109,49 @@
|
|
|
104
109
|
return normalizedCurrentPath !== '' && normalizedCurrentPath.startsWith(itemHref);
|
|
105
110
|
};
|
|
106
111
|
|
|
107
|
-
//
|
|
112
|
+
// 有効なタブのインデックス一覧(disabled を除く)
|
|
113
|
+
const enabledIndices = $derived(
|
|
114
|
+
tabItems.map((item, i) => (item.disabled ? -1 : i)).filter((i) => i >= 0)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// シンプルなキーボードナビゲーション(disabled タブはスキップ)
|
|
108
118
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
109
|
-
if (tabItems.length === 0) return;
|
|
119
|
+
if (tabItems.length === 0 || enabledIndices.length === 0) return;
|
|
110
120
|
|
|
111
121
|
const tabList = event.currentTarget as HTMLElement;
|
|
112
|
-
const tabs = Array.from(tabList.querySelectorAll('
|
|
122
|
+
const tabs = Array.from(tabList.querySelectorAll('[role="tab"]')) as HTMLElement[];
|
|
113
123
|
const currentTab = event.target as HTMLElement;
|
|
114
124
|
const currentIndex = tabs.indexOf(currentTab);
|
|
115
125
|
|
|
116
126
|
if (currentIndex === -1) return;
|
|
117
127
|
|
|
118
|
-
|
|
128
|
+
const currentEnabledPosition = enabledIndices.indexOf(currentIndex);
|
|
129
|
+
let nextEnabledPosition = currentEnabledPosition;
|
|
119
130
|
|
|
120
131
|
switch (event.key) {
|
|
121
132
|
case 'ArrowLeft':
|
|
122
133
|
event.preventDefault();
|
|
123
|
-
|
|
134
|
+
nextEnabledPosition =
|
|
135
|
+
currentEnabledPosition > 0 ? currentEnabledPosition - 1 : enabledIndices.length - 1;
|
|
124
136
|
break;
|
|
125
137
|
case 'ArrowRight':
|
|
126
138
|
event.preventDefault();
|
|
127
|
-
|
|
139
|
+
nextEnabledPosition =
|
|
140
|
+
currentEnabledPosition < enabledIndices.length - 1 ? currentEnabledPosition + 1 : 0;
|
|
128
141
|
break;
|
|
129
142
|
case 'Home':
|
|
130
143
|
event.preventDefault();
|
|
131
|
-
|
|
144
|
+
nextEnabledPosition = 0;
|
|
132
145
|
break;
|
|
133
146
|
case 'End':
|
|
134
147
|
event.preventDefault();
|
|
135
|
-
|
|
148
|
+
nextEnabledPosition = enabledIndices.length - 1;
|
|
136
149
|
break;
|
|
137
150
|
default:
|
|
138
151
|
return;
|
|
139
152
|
}
|
|
140
153
|
|
|
154
|
+
const nextIndex = enabledIndices[nextEnabledPosition];
|
|
141
155
|
tabs[nextIndex]?.focus();
|
|
142
156
|
};
|
|
143
157
|
|
|
@@ -173,6 +187,7 @@
|
|
|
173
187
|
{tabItem}
|
|
174
188
|
{pathPrefix}
|
|
175
189
|
isSelected={index === selectedTabIndex}
|
|
190
|
+
isDisabled={tabItem.disabled ?? false}
|
|
176
191
|
{textColor}
|
|
177
192
|
{selectedTextColor}
|
|
178
193
|
{selectedBarColor}
|
|
@@ -186,7 +201,7 @@
|
|
|
186
201
|
position: relative;
|
|
187
202
|
width: 100%;
|
|
188
203
|
height: 100%;
|
|
189
|
-
min-height:
|
|
204
|
+
min-height: var(--svelte-ui-tab-min-height);
|
|
190
205
|
overflow-x: auto;
|
|
191
206
|
overflow-y: visible;
|
|
192
207
|
-ms-overflow-style: none;
|