@14ch/svelte-ui 0.0.49 → 0.0.50
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/import.css +51 -0
- package/dist/assets/styles/variables.scss +53 -0
- package/dist/components/StepNav.svelte +634 -0
- package/dist/components/StepNav.svelte.d.ts +50 -0
- package/dist/i18n/index.d.ts +42 -0
- package/dist/i18n/locales/de.d.ts +7 -0
- package/dist/i18n/locales/de.js +7 -0
- package/dist/i18n/locales/en.d.ts +7 -0
- package/dist/i18n/locales/en.js +7 -0
- package/dist/i18n/locales/es.d.ts +7 -0
- package/dist/i18n/locales/es.js +7 -0
- package/dist/i18n/locales/fr.d.ts +7 -0
- package/dist/i18n/locales/fr.js +7 -0
- package/dist/i18n/locales/ja.d.ts +7 -0
- package/dist/i18n/locales/ja.js +7 -0
- package/dist/i18n/locales/zh-cn.d.ts +7 -0
- package/dist/i18n/locales/zh-cn.js +7 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -0
- package/dist/types/propOptions.d.ts +5 -0
- package/dist/types/stepItem.d.ts +22 -0
- package/dist/types/stepItem.js +1 -0
- package/package.json +1 -1
|
@@ -368,6 +368,45 @@
|
|
|
368
368
|
--svelte-ui-nav-bottom-sheet-overlay-bg: rgba(0, 0, 0, 0.4);
|
|
369
369
|
--svelte-ui-nav-bottom-sheet-border-radius: 16px 16px 0 0;
|
|
370
370
|
--svelte-ui-nav-bottom-sheet-padding: 16px 8px;
|
|
371
|
+
/* StepNav */
|
|
372
|
+
/* 強調色(現在/完了)。color prop 指定時は --internal-step-nav-accent が優先。 */
|
|
373
|
+
--svelte-ui-step-nav-accent-color: var(--svelte-ui-primary-color);
|
|
374
|
+
--svelte-ui-step-nav-completed-color: var(--svelte-ui-step-nav-accent-color);
|
|
375
|
+
--svelte-ui-step-nav-current-color: var(--svelte-ui-step-nav-accent-color);
|
|
376
|
+
/* マーカー */
|
|
377
|
+
--svelte-ui-step-nav-marker-size-sm: 24px;
|
|
378
|
+
--svelte-ui-step-nav-marker-size: 32px;
|
|
379
|
+
--svelte-ui-step-nav-marker-size-lg: 40px;
|
|
380
|
+
--svelte-ui-step-nav-marker-border-width: 2px;
|
|
381
|
+
--svelte-ui-step-nav-marker-text-color: var(--svelte-ui-text-on-filled-color);
|
|
382
|
+
--svelte-ui-step-nav-upcoming-marker-color: var(--svelte-ui-border-strong-color);
|
|
383
|
+
--svelte-ui-step-nav-upcoming-text-color: var(--svelte-ui-text-subtle-color);
|
|
384
|
+
--svelte-ui-step-nav-error-color: var(--svelte-ui-error-color);
|
|
385
|
+
/* ラベル・説明 */
|
|
386
|
+
--svelte-ui-step-nav-label-color: var(--svelte-ui-text-color);
|
|
387
|
+
--svelte-ui-step-nav-description-color: var(--svelte-ui-text-subtle-color);
|
|
388
|
+
--svelte-ui-step-nav-font-size-sm: 12px;
|
|
389
|
+
--svelte-ui-step-nav-font-size: 14px;
|
|
390
|
+
--svelte-ui-step-nav-font-size-lg: 16px;
|
|
391
|
+
--svelte-ui-step-nav-description-font-size: var(--svelte-ui-font-size-sm);
|
|
392
|
+
--svelte-ui-step-nav-label-line-height: 1.3;
|
|
393
|
+
/* コネクター(完了区間 / 未完了区間) */
|
|
394
|
+
--svelte-ui-step-nav-connector-color: var(--svelte-ui-border-color);
|
|
395
|
+
--svelte-ui-step-nav-connector-completed-color: var(--svelte-ui-step-nav-accent-color);
|
|
396
|
+
--svelte-ui-step-nav-connector-thickness: 2px;
|
|
397
|
+
/* 間隔 */
|
|
398
|
+
--svelte-ui-step-nav-marker-label-gap: 8px;
|
|
399
|
+
--svelte-ui-step-nav-step-gap: 8px;
|
|
400
|
+
/* 表示中リング(フォーカス表示とは別レイヤー・別色) */
|
|
401
|
+
--svelte-ui-step-nav-ring-color: color-mix(
|
|
402
|
+
in srgb,
|
|
403
|
+
var(--svelte-ui-step-nav-accent-color) 40%,
|
|
404
|
+
transparent
|
|
405
|
+
);
|
|
406
|
+
--svelte-ui-step-nav-ring-width: 2px;
|
|
407
|
+
--svelte-ui-step-nav-ring-offset: 2px;
|
|
408
|
+
/* フォーカス表示(表示中リングと衝突しない色) */
|
|
409
|
+
--svelte-ui-step-nav-focus-color: var(--svelte-ui-focus-color);
|
|
371
410
|
/* Input */
|
|
372
411
|
--svelte-ui-input-height: var(--svelte-ui-form-height);
|
|
373
412
|
--svelte-ui-input-padding: var(--svelte-ui-form-padding);
|
|
@@ -816,6 +855,18 @@
|
|
|
816
855
|
--svelte-ui-nav-item-underline-text-color: CanvasText;
|
|
817
856
|
--svelte-ui-nav-item-underline-selected-text-color: Highlight;
|
|
818
857
|
--svelte-ui-nav-item-underline-bar-color: Highlight;
|
|
858
|
+
/* StepNav */
|
|
859
|
+
--svelte-ui-step-nav-accent-color: Highlight;
|
|
860
|
+
--svelte-ui-step-nav-marker-text-color: HighlightText;
|
|
861
|
+
--svelte-ui-step-nav-upcoming-marker-color: CanvasText;
|
|
862
|
+
--svelte-ui-step-nav-upcoming-text-color: CanvasText;
|
|
863
|
+
--svelte-ui-step-nav-label-color: CanvasText;
|
|
864
|
+
--svelte-ui-step-nav-description-color: CanvasText;
|
|
865
|
+
--svelte-ui-step-nav-error-color: CanvasText;
|
|
866
|
+
--svelte-ui-step-nav-connector-color: CanvasText;
|
|
867
|
+
--svelte-ui-step-nav-connector-completed-color: Highlight;
|
|
868
|
+
--svelte-ui-step-nav-ring-color: Highlight;
|
|
869
|
+
--svelte-ui-step-nav-focus-color: CanvasText;
|
|
819
870
|
/* Radio */
|
|
820
871
|
--svelte-ui-radio-border-color: CanvasText;
|
|
821
872
|
--svelte-ui-radio-border-width: var(--svelte-ui-border-width-thick);
|
|
@@ -407,6 +407,46 @@
|
|
|
407
407
|
--svelte-ui-nav-bottom-sheet-border-radius: 16px 16px 0 0;
|
|
408
408
|
--svelte-ui-nav-bottom-sheet-padding: 16px 8px;
|
|
409
409
|
|
|
410
|
+
/* StepNav */
|
|
411
|
+
/* 強調色(現在/完了)。color prop 指定時は --internal-step-nav-accent が優先。 */
|
|
412
|
+
--svelte-ui-step-nav-accent-color: var(--svelte-ui-primary-color);
|
|
413
|
+
--svelte-ui-step-nav-completed-color: var(--svelte-ui-step-nav-accent-color);
|
|
414
|
+
--svelte-ui-step-nav-current-color: var(--svelte-ui-step-nav-accent-color);
|
|
415
|
+
/* マーカー */
|
|
416
|
+
--svelte-ui-step-nav-marker-size-sm: 24px;
|
|
417
|
+
--svelte-ui-step-nav-marker-size: 32px;
|
|
418
|
+
--svelte-ui-step-nav-marker-size-lg: 40px;
|
|
419
|
+
--svelte-ui-step-nav-marker-border-width: 2px;
|
|
420
|
+
--svelte-ui-step-nav-marker-text-color: var(--svelte-ui-text-on-filled-color);
|
|
421
|
+
--svelte-ui-step-nav-upcoming-marker-color: var(--svelte-ui-border-strong-color);
|
|
422
|
+
--svelte-ui-step-nav-upcoming-text-color: var(--svelte-ui-text-subtle-color);
|
|
423
|
+
--svelte-ui-step-nav-error-color: var(--svelte-ui-error-color);
|
|
424
|
+
/* ラベル・説明 */
|
|
425
|
+
--svelte-ui-step-nav-label-color: var(--svelte-ui-text-color);
|
|
426
|
+
--svelte-ui-step-nav-description-color: var(--svelte-ui-text-subtle-color);
|
|
427
|
+
--svelte-ui-step-nav-font-size-sm: 12px;
|
|
428
|
+
--svelte-ui-step-nav-font-size: 14px;
|
|
429
|
+
--svelte-ui-step-nav-font-size-lg: 16px;
|
|
430
|
+
--svelte-ui-step-nav-description-font-size: var(--svelte-ui-font-size-sm);
|
|
431
|
+
--svelte-ui-step-nav-label-line-height: 1.3;
|
|
432
|
+
/* コネクター(完了区間 / 未完了区間) */
|
|
433
|
+
--svelte-ui-step-nav-connector-color: var(--svelte-ui-border-color);
|
|
434
|
+
--svelte-ui-step-nav-connector-completed-color: var(--svelte-ui-step-nav-accent-color);
|
|
435
|
+
--svelte-ui-step-nav-connector-thickness: 2px;
|
|
436
|
+
/* 間隔 */
|
|
437
|
+
--svelte-ui-step-nav-marker-label-gap: 8px;
|
|
438
|
+
--svelte-ui-step-nav-step-gap: 8px;
|
|
439
|
+
/* 表示中リング(フォーカス表示とは別レイヤー・別色) */
|
|
440
|
+
--svelte-ui-step-nav-ring-color: color-mix(
|
|
441
|
+
in srgb,
|
|
442
|
+
var(--svelte-ui-step-nav-accent-color) 40%,
|
|
443
|
+
transparent
|
|
444
|
+
);
|
|
445
|
+
--svelte-ui-step-nav-ring-width: 2px;
|
|
446
|
+
--svelte-ui-step-nav-ring-offset: 2px;
|
|
447
|
+
/* フォーカス表示(表示中リングと衝突しない色) */
|
|
448
|
+
--svelte-ui-step-nav-focus-color: var(--svelte-ui-focus-color);
|
|
449
|
+
|
|
410
450
|
/* Input */
|
|
411
451
|
--svelte-ui-input-height: var(--svelte-ui-form-height);
|
|
412
452
|
--svelte-ui-input-padding: var(--svelte-ui-form-padding);
|
|
@@ -898,6 +938,19 @@
|
|
|
898
938
|
--svelte-ui-nav-item-underline-selected-text-color: Highlight;
|
|
899
939
|
--svelte-ui-nav-item-underline-bar-color: Highlight;
|
|
900
940
|
|
|
941
|
+
/* StepNav */
|
|
942
|
+
--svelte-ui-step-nav-accent-color: Highlight;
|
|
943
|
+
--svelte-ui-step-nav-marker-text-color: HighlightText;
|
|
944
|
+
--svelte-ui-step-nav-upcoming-marker-color: CanvasText;
|
|
945
|
+
--svelte-ui-step-nav-upcoming-text-color: CanvasText;
|
|
946
|
+
--svelte-ui-step-nav-label-color: CanvasText;
|
|
947
|
+
--svelte-ui-step-nav-description-color: CanvasText;
|
|
948
|
+
--svelte-ui-step-nav-error-color: CanvasText;
|
|
949
|
+
--svelte-ui-step-nav-connector-color: CanvasText;
|
|
950
|
+
--svelte-ui-step-nav-connector-completed-color: Highlight;
|
|
951
|
+
--svelte-ui-step-nav-ring-color: Highlight;
|
|
952
|
+
--svelte-ui-step-nav-focus-color: CanvasText;
|
|
953
|
+
|
|
901
954
|
/* Radio */
|
|
902
955
|
--svelte-ui-radio-border-color: CanvasText;
|
|
903
956
|
--svelte-ui-radio-border-width: var(--svelte-ui-border-width-thick);
|
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
<!-- StepNav.svelte -->
|
|
2
|
+
|
|
3
|
+
<script lang="ts">
|
|
4
|
+
import Icon from './Icon.svelte';
|
|
5
|
+
import type { StepItem } from '../types/stepItem';
|
|
6
|
+
import type { MenuItem } from '../types/menuItem';
|
|
7
|
+
import type { ComponentSize, StepNavOrientation } from '../types/propOptions';
|
|
8
|
+
import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
|
|
9
|
+
import type {
|
|
10
|
+
MouseHandler,
|
|
11
|
+
FocusHandler,
|
|
12
|
+
KeyboardHandler,
|
|
13
|
+
BivariantValueHandler
|
|
14
|
+
} from '../types/callbackHandlers';
|
|
15
|
+
import { getCurrentPath, matchPath } from '../utils/navPath';
|
|
16
|
+
import { subscribeUrlChange } from '../utils/urlChange';
|
|
17
|
+
import { t } from '../i18n';
|
|
18
|
+
|
|
19
|
+
// =========================================================================
|
|
20
|
+
// Props
|
|
21
|
+
// =========================================================================
|
|
22
|
+
export type StepNavProps = {
|
|
23
|
+
// 基本プロパティ
|
|
24
|
+
/** `{ label, value?, href?, description?, icon?, error?, disabled? }[]` */
|
|
25
|
+
items: StepItem[];
|
|
26
|
+
/** 表示中ステップの値(インページ方式)。`bind:value` 対応。`item.value` 省略時は配列 index の文字列('0','1',...)が暗黙の値になる。 */
|
|
27
|
+
value?: string;
|
|
28
|
+
/** 到達済みの最遠ステップの 0 始まりインデックス(進捗軸)。未指定時は表示中ステップ位置。 */
|
|
29
|
+
progress?: number;
|
|
30
|
+
|
|
31
|
+
// URL 方式(href 使用時)
|
|
32
|
+
/** 各 href のアクティブ判定に前置するパス。 */
|
|
33
|
+
pathPrefix?: string;
|
|
34
|
+
/** アクティブ判定をカスタムする関数。 */
|
|
35
|
+
customPathMatcher?: (currentPath: string, itemHref: string, item: StepItem) => boolean;
|
|
36
|
+
/** 自動検出した現在パスを上書き。 */
|
|
37
|
+
currentPath?: string;
|
|
38
|
+
|
|
39
|
+
// HTML属性
|
|
40
|
+
id?: string;
|
|
41
|
+
|
|
42
|
+
// スタイル/レイアウト
|
|
43
|
+
/** @default 'horizontal' */
|
|
44
|
+
orientation?: StepNavOrientation;
|
|
45
|
+
/** @default 'medium' */
|
|
46
|
+
size?: ComponentSize;
|
|
47
|
+
/** 現在/完了ステップの強調色。テーマ色より優先。 */
|
|
48
|
+
color?: string;
|
|
49
|
+
/** 番号の代わりに各ステップの icon を表示。 @default false */
|
|
50
|
+
showIcon?: boolean;
|
|
51
|
+
/** 追加インラインスタイル。 */
|
|
52
|
+
customStyle?: string;
|
|
53
|
+
|
|
54
|
+
// アイコン関連
|
|
55
|
+
iconVariant?: IconVariant;
|
|
56
|
+
iconWeight?: IconWeight;
|
|
57
|
+
iconGrade?: IconGrade;
|
|
58
|
+
iconOpticalSize?: IconOpticalSize;
|
|
59
|
+
|
|
60
|
+
// 状態/動作
|
|
61
|
+
/** インページ方式でステップのクリック遷移を許可。 @default false */
|
|
62
|
+
clickable?: boolean;
|
|
63
|
+
/** コンポーネント全体を無効化。 @default false */
|
|
64
|
+
disabled?: boolean;
|
|
65
|
+
/** アニメーションを抑制。 @default false */
|
|
66
|
+
reducedMotion?: boolean;
|
|
67
|
+
|
|
68
|
+
// ARIA/アクセシビリティ
|
|
69
|
+
ariaLabel?: string;
|
|
70
|
+
ariaLabelledby?: string;
|
|
71
|
+
|
|
72
|
+
// イベントハンドラ
|
|
73
|
+
/** 表示中ステップ変更(インページ方式)。 */
|
|
74
|
+
onchange?: BivariantValueHandler<string>;
|
|
75
|
+
onclick?: MouseHandler;
|
|
76
|
+
onfocus?: FocusHandler;
|
|
77
|
+
onblur?: FocusHandler;
|
|
78
|
+
onkeydown?: KeyboardHandler;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let {
|
|
82
|
+
// 基本プロパティ
|
|
83
|
+
items = [],
|
|
84
|
+
value = $bindable(''),
|
|
85
|
+
progress,
|
|
86
|
+
|
|
87
|
+
// URL 方式
|
|
88
|
+
pathPrefix = '',
|
|
89
|
+
customPathMatcher,
|
|
90
|
+
currentPath,
|
|
91
|
+
|
|
92
|
+
// HTML属性
|
|
93
|
+
id,
|
|
94
|
+
|
|
95
|
+
// スタイル/レイアウト
|
|
96
|
+
orientation = 'horizontal',
|
|
97
|
+
size = 'medium',
|
|
98
|
+
color,
|
|
99
|
+
showIcon = false,
|
|
100
|
+
customStyle = '',
|
|
101
|
+
|
|
102
|
+
// アイコン関連
|
|
103
|
+
iconVariant = 'outlined',
|
|
104
|
+
iconWeight = 300,
|
|
105
|
+
iconGrade = 0,
|
|
106
|
+
iconOpticalSize,
|
|
107
|
+
|
|
108
|
+
// 状態/動作
|
|
109
|
+
clickable = false,
|
|
110
|
+
disabled = false,
|
|
111
|
+
reducedMotion = false,
|
|
112
|
+
|
|
113
|
+
// ARIA/アクセシビリティ
|
|
114
|
+
ariaLabel,
|
|
115
|
+
ariaLabelledby,
|
|
116
|
+
|
|
117
|
+
// イベントハンドラ
|
|
118
|
+
onchange = () => {}, // No params for type inference
|
|
119
|
+
onclick = () => {}, // No params for type inference
|
|
120
|
+
onfocus = () => {}, // No params for type inference
|
|
121
|
+
onblur = () => {}, // No params for type inference
|
|
122
|
+
onkeydown = () => {} // No params for type inference
|
|
123
|
+
}: StepNavProps = $props();
|
|
124
|
+
|
|
125
|
+
// =========================================================================
|
|
126
|
+
// State & Effects
|
|
127
|
+
// =========================================================================
|
|
128
|
+
let rootEl: HTMLElement | undefined = $state();
|
|
129
|
+
let resolvedCurrentPath = $state('');
|
|
130
|
+
|
|
131
|
+
$effect(() => {
|
|
132
|
+
resolvedCurrentPath = getCurrentPath(currentPath);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
$effect(() => {
|
|
136
|
+
return subscribeUrlChange(() => {
|
|
137
|
+
resolvedCurrentPath = getCurrentPath(currentPath);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// =========================================================================
|
|
142
|
+
// Derived state
|
|
143
|
+
// =========================================================================
|
|
144
|
+
const isLinkMode = $derived(items.some((item) => item.href != null));
|
|
145
|
+
|
|
146
|
+
const activeIndex = $derived.by(() => {
|
|
147
|
+
if (isLinkMode) {
|
|
148
|
+
return items.findIndex(
|
|
149
|
+
(item) =>
|
|
150
|
+
item.href != null &&
|
|
151
|
+
matchPath(
|
|
152
|
+
resolvedCurrentPath,
|
|
153
|
+
item.href,
|
|
154
|
+
item as unknown as MenuItem,
|
|
155
|
+
pathPrefix,
|
|
156
|
+
customPathMatcher as
|
|
157
|
+
| ((currentPath: string, itemHref: string, item: MenuItem) => boolean)
|
|
158
|
+
| undefined
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return items.findIndex((item, index) => (item.value ?? String(index)) === value);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const progressIndex = $derived(progress ?? activeIndex);
|
|
166
|
+
|
|
167
|
+
const resolvedIconSize = $derived(
|
|
168
|
+
iconOpticalSize || (size === 'small' ? 16 : size === 'large' ? 24 : 20)
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const rootClasses = $derived(
|
|
172
|
+
[
|
|
173
|
+
'step-nav',
|
|
174
|
+
`step-nav--${orientation}`,
|
|
175
|
+
`step-nav--${size}`,
|
|
176
|
+
disabled && 'step-nav--disabled',
|
|
177
|
+
reducedMotion && 'step-nav--no-motion'
|
|
178
|
+
]
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.join(' ')
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const rootStyle = $derived(
|
|
184
|
+
[
|
|
185
|
+
// color 指定時は強調色を上書き。リングは半透明の色なので同率で合成した値も注入する。
|
|
186
|
+
color ? `--internal-step-nav-accent:${color}` : '',
|
|
187
|
+
color ? `--internal-step-nav-ring-color:color-mix(in srgb, ${color} 40%, transparent)` : '',
|
|
188
|
+
customStyle
|
|
189
|
+
]
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.join('; ')
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// =========================================================================
|
|
195
|
+
// Helpers
|
|
196
|
+
// =========================================================================
|
|
197
|
+
type StepStatus = 'completed' | 'current' | 'upcoming';
|
|
198
|
+
|
|
199
|
+
const getStatus = (index: number): StepStatus => {
|
|
200
|
+
if (index < progressIndex) return 'completed';
|
|
201
|
+
if (index === progressIndex) return 'current';
|
|
202
|
+
return 'upcoming';
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const isStepDisabled = (item: StepItem): boolean => disabled || item.disabled === true;
|
|
206
|
+
|
|
207
|
+
// 対話的なステップか(URL方式はリンク、インページ方式かつ clickable はボタン)。無効なら常に非対話。
|
|
208
|
+
const isStepInteractive = (item: StepItem): boolean =>
|
|
209
|
+
!isStepDisabled(item) && (isLinkMode || clickable);
|
|
210
|
+
|
|
211
|
+
// インページ方式でクリック遷移が有効なステップか(ボタン描画・クリック処理の判定)
|
|
212
|
+
const isStepButton = (item: StepItem): boolean => isStepInteractive(item) && !isLinkMode;
|
|
213
|
+
|
|
214
|
+
const stepClasses = (item: StepItem, status: StepStatus, isViewing: boolean): string =>
|
|
215
|
+
[
|
|
216
|
+
'step-nav__step',
|
|
217
|
+
`step-nav__step--${status}`,
|
|
218
|
+
isViewing && 'step-nav__step--viewing',
|
|
219
|
+
item.error && 'step-nav__step--error',
|
|
220
|
+
isStepDisabled(item) && 'step-nav__step--disabled'
|
|
221
|
+
]
|
|
222
|
+
.filter(Boolean)
|
|
223
|
+
.join(' ');
|
|
224
|
+
|
|
225
|
+
// スクリーンリーダー向けの状態テキスト
|
|
226
|
+
const statusText = (index: number, item: StepItem): string => {
|
|
227
|
+
const step = t('stepNav.step', { number: index + 1 });
|
|
228
|
+
const state = item.error
|
|
229
|
+
? t('stepNav.error')
|
|
230
|
+
: getStatus(index) === 'completed'
|
|
231
|
+
? t('stepNav.completed')
|
|
232
|
+
: getStatus(index) === 'current'
|
|
233
|
+
? t('stepNav.current')
|
|
234
|
+
: t('stepNav.upcoming');
|
|
235
|
+
return `${step}: ${state}`;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const handleStepClick = (item: StepItem, index: number, event: MouseEvent) => {
|
|
239
|
+
if (!isStepButton(item)) return;
|
|
240
|
+
const nextValue = item.value ?? String(index);
|
|
241
|
+
value = nextValue;
|
|
242
|
+
onchange(nextValue);
|
|
243
|
+
onclick(event);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Arrow/Home/End はフォーカス移動の補助。Enter/Space は要素本来の活性化に委ねる。
|
|
247
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
248
|
+
onkeydown(event);
|
|
249
|
+
|
|
250
|
+
const focusables = Array.from(
|
|
251
|
+
rootEl?.querySelectorAll<HTMLElement>('[data-step-nav-interactive]') ?? []
|
|
252
|
+
).filter((el) => el.tabIndex !== -1 && !(el as HTMLButtonElement).disabled);
|
|
253
|
+
if (focusables.length === 0) return;
|
|
254
|
+
|
|
255
|
+
const currentIndex = focusables.indexOf(event.target as HTMLElement);
|
|
256
|
+
if (currentIndex === -1) return;
|
|
257
|
+
|
|
258
|
+
const isVertical = orientation === 'vertical';
|
|
259
|
+
const prevKey = isVertical ? 'ArrowUp' : 'ArrowLeft';
|
|
260
|
+
const nextKey = isVertical ? 'ArrowDown' : 'ArrowRight';
|
|
261
|
+
|
|
262
|
+
switch (event.key) {
|
|
263
|
+
case prevKey:
|
|
264
|
+
event.preventDefault();
|
|
265
|
+
focusables[currentIndex > 0 ? currentIndex - 1 : focusables.length - 1]?.focus();
|
|
266
|
+
break;
|
|
267
|
+
case nextKey:
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
focusables[currentIndex < focusables.length - 1 ? currentIndex + 1 : 0]?.focus();
|
|
270
|
+
break;
|
|
271
|
+
case 'Home':
|
|
272
|
+
event.preventDefault();
|
|
273
|
+
focusables[0]?.focus();
|
|
274
|
+
break;
|
|
275
|
+
case 'End':
|
|
276
|
+
event.preventDefault();
|
|
277
|
+
focusables[focusables.length - 1]?.focus();
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
</script>
|
|
284
|
+
|
|
285
|
+
{#snippet marker(item: StepItem, index: number, status: StepStatus)}
|
|
286
|
+
<span class="step-nav__marker" aria-hidden="true">
|
|
287
|
+
{#if item.error}
|
|
288
|
+
<Icon
|
|
289
|
+
size={resolvedIconSize}
|
|
290
|
+
weight={iconWeight}
|
|
291
|
+
grade={iconGrade}
|
|
292
|
+
variant={iconVariant}
|
|
293
|
+
filled>error</Icon
|
|
294
|
+
>
|
|
295
|
+
{:else if status === 'completed'}
|
|
296
|
+
<Icon size={resolvedIconSize} weight={iconWeight} grade={iconGrade} variant={iconVariant}
|
|
297
|
+
>check</Icon
|
|
298
|
+
>
|
|
299
|
+
{:else if showIcon && item.icon}
|
|
300
|
+
<Icon
|
|
301
|
+
size={resolvedIconSize}
|
|
302
|
+
weight={iconWeight}
|
|
303
|
+
grade={iconGrade}
|
|
304
|
+
opticalSize={iconOpticalSize}
|
|
305
|
+
variant={iconVariant}>{item.icon}</Icon
|
|
306
|
+
>
|
|
307
|
+
{:else}
|
|
308
|
+
{index + 1}
|
|
309
|
+
{/if}
|
|
310
|
+
</span>
|
|
311
|
+
{/snippet}
|
|
312
|
+
|
|
313
|
+
{#snippet stepInner(item: StepItem, index: number, status: StepStatus)}
|
|
314
|
+
{@render marker(item, index, status)}
|
|
315
|
+
<span class="step-nav__body">
|
|
316
|
+
<span class="step-nav__label">{item.label}</span>
|
|
317
|
+
{#if item.description}
|
|
318
|
+
<span class="step-nav__description">{item.description}</span>
|
|
319
|
+
{/if}
|
|
320
|
+
</span>
|
|
321
|
+
<span class="sr-only">{statusText(index, item)}</span>
|
|
322
|
+
{/snippet}
|
|
323
|
+
|
|
324
|
+
{#if items.length > 0}
|
|
325
|
+
<nav
|
|
326
|
+
bind:this={rootEl}
|
|
327
|
+
class={rootClasses}
|
|
328
|
+
style={rootStyle || undefined}
|
|
329
|
+
{id}
|
|
330
|
+
aria-label={ariaLabelledby ? undefined : ariaLabel}
|
|
331
|
+
aria-labelledby={ariaLabelledby}
|
|
332
|
+
data-testid="step-nav"
|
|
333
|
+
>
|
|
334
|
+
<!-- キーボード補助はランドマークを汚さないよう presentation ラッパーで受ける -->
|
|
335
|
+
<div style="display: contents" role="presentation" onkeydown={handleKeyDown}>
|
|
336
|
+
<ol class="step-nav__list" role="list">
|
|
337
|
+
{#each items as item, index (index)}
|
|
338
|
+
{@const status = getStatus(index)}
|
|
339
|
+
{@const isViewing = index === activeIndex}
|
|
340
|
+
<li class="step-nav__item" role="listitem">
|
|
341
|
+
{#if !isStepInteractive(item)}
|
|
342
|
+
<!-- 非対話(URL/インページを問わず共通): 表示のみ。無効ステップは aria-disabled を付与 -->
|
|
343
|
+
<span
|
|
344
|
+
class={stepClasses(item, status, isViewing)}
|
|
345
|
+
aria-current={isViewing ? 'step' : undefined}
|
|
346
|
+
aria-invalid={item.error ? 'true' : undefined}
|
|
347
|
+
aria-disabled={isStepDisabled(item) ? 'true' : undefined}
|
|
348
|
+
aria-label={item.ariaLabel ?? undefined}
|
|
349
|
+
>
|
|
350
|
+
{@render stepInner(item, index, status)}
|
|
351
|
+
</span>
|
|
352
|
+
{:else if isLinkMode}
|
|
353
|
+
<!-- 対話 × URL 方式: リンクとして描画し標準遷移に委ねる -->
|
|
354
|
+
<a
|
|
355
|
+
href={item.href}
|
|
356
|
+
class={stepClasses(item, status, isViewing)}
|
|
357
|
+
aria-current={isViewing ? 'step' : undefined}
|
|
358
|
+
aria-label={item.ariaLabel ?? undefined}
|
|
359
|
+
data-step-nav-interactive
|
|
360
|
+
{onfocus}
|
|
361
|
+
{onblur}
|
|
362
|
+
>
|
|
363
|
+
{@render stepInner(item, index, status)}
|
|
364
|
+
</a>
|
|
365
|
+
{:else}
|
|
366
|
+
<!-- 対話 × インページ方式: ボタンとして表示中を更新 -->
|
|
367
|
+
<button
|
|
368
|
+
type="button"
|
|
369
|
+
class={stepClasses(item, status, isViewing)}
|
|
370
|
+
aria-current={isViewing ? 'step' : undefined}
|
|
371
|
+
aria-label={item.ariaLabel ?? undefined}
|
|
372
|
+
data-step-nav-interactive
|
|
373
|
+
onclick={(event) => handleStepClick(item, index, event)}
|
|
374
|
+
{onfocus}
|
|
375
|
+
{onblur}
|
|
376
|
+
>
|
|
377
|
+
{@render stepInner(item, index, status)}
|
|
378
|
+
</button>
|
|
379
|
+
{/if}
|
|
380
|
+
|
|
381
|
+
{#if index < items.length - 1}
|
|
382
|
+
<span
|
|
383
|
+
class="step-nav__connector"
|
|
384
|
+
class:step-nav__connector--completed={index < progressIndex}
|
|
385
|
+
aria-hidden="true"
|
|
386
|
+
></span>
|
|
387
|
+
{/if}
|
|
388
|
+
</li>
|
|
389
|
+
{/each}
|
|
390
|
+
</ol>
|
|
391
|
+
</div>
|
|
392
|
+
</nav>
|
|
393
|
+
{/if}
|
|
394
|
+
|
|
395
|
+
<style>@charset "UTF-8";
|
|
396
|
+
.step-nav {
|
|
397
|
+
--internal-step-nav-marker-size: var(--svelte-ui-step-nav-marker-size);
|
|
398
|
+
--internal-step-nav-font-size: var(--svelte-ui-step-nav-font-size);
|
|
399
|
+
display: block;
|
|
400
|
+
box-sizing: border-box;
|
|
401
|
+
max-width: 100%;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.step-nav--small {
|
|
405
|
+
--internal-step-nav-marker-size: var(--svelte-ui-step-nav-marker-size-sm);
|
|
406
|
+
--internal-step-nav-font-size: var(--svelte-ui-step-nav-font-size-sm);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.step-nav--large {
|
|
410
|
+
--internal-step-nav-marker-size: var(--svelte-ui-step-nav-marker-size-lg);
|
|
411
|
+
--internal-step-nav-font-size: var(--svelte-ui-step-nav-font-size-lg);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.step-nav__list {
|
|
415
|
+
display: flex;
|
|
416
|
+
margin: 0;
|
|
417
|
+
padding: 0;
|
|
418
|
+
list-style: none;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.step-nav__item {
|
|
422
|
+
display: flex;
|
|
423
|
+
position: relative;
|
|
424
|
+
box-sizing: border-box;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/* ---- 対話/非対話ステップ共通 ---- */
|
|
428
|
+
.step-nav__step {
|
|
429
|
+
display: flex;
|
|
430
|
+
align-items: center;
|
|
431
|
+
gap: var(--svelte-ui-step-nav-marker-label-gap);
|
|
432
|
+
position: relative;
|
|
433
|
+
border: none;
|
|
434
|
+
background: transparent;
|
|
435
|
+
padding: 0;
|
|
436
|
+
margin: 0;
|
|
437
|
+
font-family: inherit;
|
|
438
|
+
font-size: var(--internal-step-nav-font-size);
|
|
439
|
+
color: var(--svelte-ui-step-nav-label-color);
|
|
440
|
+
text-align: left;
|
|
441
|
+
text-decoration: none;
|
|
442
|
+
cursor: default;
|
|
443
|
+
box-sizing: border-box;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
a.step-nav__step,
|
|
447
|
+
button.step-nav__step {
|
|
448
|
+
cursor: pointer;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.step-nav__step--disabled {
|
|
452
|
+
opacity: var(--svelte-ui-disabled-opacity);
|
|
453
|
+
cursor: not-allowed;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.step-nav__step:focus-visible {
|
|
457
|
+
outline: var(--svelte-ui-focus-outline-outer, 2px solid);
|
|
458
|
+
outline-color: var(--svelte-ui-step-nav-focus-color);
|
|
459
|
+
outline-offset: var(--svelte-ui-focus-outline-offset-outer, 2px);
|
|
460
|
+
border-radius: var(--svelte-ui-border-radius, 4px);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* ---- マーカー ---- */
|
|
464
|
+
.step-nav__marker {
|
|
465
|
+
display: inline-flex;
|
|
466
|
+
align-items: center;
|
|
467
|
+
justify-content: center;
|
|
468
|
+
flex: 0 0 auto;
|
|
469
|
+
width: var(--internal-step-nav-marker-size);
|
|
470
|
+
height: var(--internal-step-nav-marker-size);
|
|
471
|
+
border-radius: 50%;
|
|
472
|
+
font-size: calc(var(--internal-step-nav-font-size) * 0.95);
|
|
473
|
+
font-weight: bold;
|
|
474
|
+
line-height: 1;
|
|
475
|
+
box-sizing: border-box;
|
|
476
|
+
position: relative;
|
|
477
|
+
z-index: 1;
|
|
478
|
+
transition-property: background-color, border-color, color;
|
|
479
|
+
transition-duration: var(--svelte-ui-transition-duration, 0.2s);
|
|
480
|
+
transition-timing-function: ease;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* upcoming: 淡いアウトライン + 番号 */
|
|
484
|
+
.step-nav__step--upcoming .step-nav__marker {
|
|
485
|
+
background: var(--svelte-ui-surface-color);
|
|
486
|
+
color: var(--svelte-ui-step-nav-upcoming-text-color);
|
|
487
|
+
border: var(--svelte-ui-step-nav-marker-border-width) solid var(--svelte-ui-step-nav-upcoming-marker-color);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* current: primary アウトライン + 番号 */
|
|
491
|
+
.step-nav__step--current .step-nav__marker {
|
|
492
|
+
background: var(--svelte-ui-surface-color);
|
|
493
|
+
color: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
|
|
494
|
+
border: var(--svelte-ui-step-nav-marker-border-width) solid var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/* completed: primary 塗り + check */
|
|
498
|
+
.step-nav__step--completed .step-nav__marker {
|
|
499
|
+
background: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
|
|
500
|
+
color: var(--svelte-ui-step-nav-marker-text-color);
|
|
501
|
+
border: var(--svelte-ui-step-nav-marker-border-width) solid var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* error: error 色 + error アイコン(進捗軸を上書き) */
|
|
505
|
+
.step-nav__step--error .step-nav__marker {
|
|
506
|
+
background: var(--svelte-ui-surface-color);
|
|
507
|
+
color: var(--svelte-ui-step-nav-error-color);
|
|
508
|
+
border: var(--svelte-ui-step-nav-marker-border-width) solid var(--svelte-ui-step-nav-error-color);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/* 表示中: 外周リング(フォーカス表示とは別レイヤー・別色) */
|
|
512
|
+
.step-nav__step--viewing .step-nav__marker {
|
|
513
|
+
box-shadow: 0 0 0 var(--svelte-ui-step-nav-ring-offset) var(--svelte-ui-surface-color), 0 0 0 calc(var(--svelte-ui-step-nav-ring-offset) + var(--svelte-ui-step-nav-ring-width)) var(--internal-step-nav-ring-color, var(--svelte-ui-step-nav-ring-color));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/* ---- ラベル/説明 ---- */
|
|
517
|
+
.step-nav__body {
|
|
518
|
+
display: flex;
|
|
519
|
+
flex-direction: column;
|
|
520
|
+
min-width: 0;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.step-nav__label {
|
|
524
|
+
line-height: var(--svelte-ui-step-nav-label-line-height);
|
|
525
|
+
white-space: nowrap;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* ラベル色の優先順位(同一詳細度のためソース順で解決): upcoming < viewing < error */
|
|
529
|
+
.step-nav__step--upcoming .step-nav__label {
|
|
530
|
+
color: var(--svelte-ui-step-nav-upcoming-text-color);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/* 表示中: 強調色 + 太字(Nav の選択中=primary 文字色と同じ規則。color prop で上書き可) */
|
|
534
|
+
.step-nav__step--viewing .step-nav__label {
|
|
535
|
+
color: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-accent-color));
|
|
536
|
+
font-weight: bold;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.step-nav__step--error .step-nav__label {
|
|
540
|
+
color: var(--svelte-ui-step-nav-error-color);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.step-nav__description {
|
|
544
|
+
font-size: var(--svelte-ui-step-nav-description-font-size);
|
|
545
|
+
color: var(--svelte-ui-step-nav-description-color);
|
|
546
|
+
line-height: var(--svelte-ui-step-nav-label-line-height);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/* ---- コネクター ---- */
|
|
550
|
+
.step-nav__connector {
|
|
551
|
+
position: absolute;
|
|
552
|
+
background: var(--svelte-ui-step-nav-connector-color);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.step-nav__connector--completed {
|
|
556
|
+
background: var(--internal-step-nav-accent, var(--svelte-ui-step-nav-connector-completed-color));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/* ======================= 水平レイアウト ======================= */
|
|
560
|
+
.step-nav--horizontal .step-nav__list {
|
|
561
|
+
flex-direction: row;
|
|
562
|
+
overflow-x: auto;
|
|
563
|
+
overscroll-behavior: contain;
|
|
564
|
+
/* overflow-x を指定すると overflow-y も auto に計算され、マーカー上端の
|
|
565
|
+
外周リング/フォーカス表示が切れる。上下に余白を確保して回避する。 */
|
|
566
|
+
padding-block: max(var(--svelte-ui-step-nav-ring-offset) + var(--svelte-ui-step-nav-ring-width), var(--svelte-ui-focus-outline-offset-outer, 2px) + 2px);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.step-nav--horizontal .step-nav__item {
|
|
570
|
+
/* 等幅カラムにしてマーカー中心を等間隔に揃える(コネクターの整列に必須)。
|
|
571
|
+
min-width をフロアにして、ステップが多いときは横スクロールで収める(Req 5.4)。 */
|
|
572
|
+
flex: 1 1 0;
|
|
573
|
+
min-width: calc(var(--internal-step-nav-marker-size) * 2.5);
|
|
574
|
+
flex-direction: column;
|
|
575
|
+
align-items: center;
|
|
576
|
+
text-align: center;
|
|
577
|
+
padding: 0 var(--svelte-ui-step-nav-step-gap);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.step-nav--horizontal .step-nav__step {
|
|
581
|
+
flex-direction: column;
|
|
582
|
+
align-items: center;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.step-nav--horizontal .step-nav__body {
|
|
586
|
+
align-items: center;
|
|
587
|
+
text-align: center;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/* 各ステップの右側に、次のマーカーへ向けてコネクターを描画(等幅前提で 100% 幅が隣の中心に一致) */
|
|
591
|
+
.step-nav--horizontal .step-nav__connector {
|
|
592
|
+
top: calc(var(--internal-step-nav-marker-size) / 2);
|
|
593
|
+
transform: translateY(-50%);
|
|
594
|
+
left: calc(50% + var(--internal-step-nav-marker-size) / 2);
|
|
595
|
+
width: calc(100% - var(--internal-step-nav-marker-size));
|
|
596
|
+
height: var(--svelte-ui-step-nav-connector-thickness);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/* ======================= 垂直レイアウト ======================= */
|
|
600
|
+
.step-nav--vertical .step-nav__list {
|
|
601
|
+
flex-direction: column;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.step-nav--vertical .step-nav__item {
|
|
605
|
+
flex-direction: row;
|
|
606
|
+
align-items: flex-start;
|
|
607
|
+
padding: var(--svelte-ui-step-nav-step-gap) 0;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.step-nav--vertical .step-nav__step {
|
|
611
|
+
flex-direction: row;
|
|
612
|
+
align-items: flex-start;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/* 各ステップの下側に、次のマーカーへ向けてコネクターを描画。
|
|
616
|
+
縦 padding(step-gap)ぶんマーカーが下がっているので、開始位置に加味する(自身の高さ基準で可変高さでも整合)。 */
|
|
617
|
+
.step-nav--vertical .step-nav__connector {
|
|
618
|
+
left: calc(var(--internal-step-nav-marker-size) / 2);
|
|
619
|
+
transform: translateX(-50%);
|
|
620
|
+
top: calc(var(--internal-step-nav-marker-size) + var(--svelte-ui-step-nav-step-gap));
|
|
621
|
+
height: calc(100% - var(--internal-step-nav-marker-size));
|
|
622
|
+
width: var(--svelte-ui-step-nav-connector-thickness);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/* ---- モーション抑制 ---- */
|
|
626
|
+
.step-nav--no-motion .step-nav__marker {
|
|
627
|
+
transition-duration: 0.01s;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
@media (prefers-reduced-motion: reduce) {
|
|
631
|
+
.step-nav__marker {
|
|
632
|
+
transition-duration: 0.01s;
|
|
633
|
+
}
|
|
634
|
+
}</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { StepItem } from '../types/stepItem';
|
|
2
|
+
import type { ComponentSize, StepNavOrientation } from '../types/propOptions';
|
|
3
|
+
import type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from '../types/icon';
|
|
4
|
+
import type { MouseHandler, FocusHandler, KeyboardHandler, BivariantValueHandler } from '../types/callbackHandlers';
|
|
5
|
+
export type StepNavProps = {
|
|
6
|
+
/** `{ label, value?, href?, description?, icon?, error?, disabled? }[]` */
|
|
7
|
+
items: StepItem[];
|
|
8
|
+
/** 表示中ステップの値(インページ方式)。`bind:value` 対応。`item.value` 省略時は配列 index の文字列('0','1',...)が暗黙の値になる。 */
|
|
9
|
+
value?: string;
|
|
10
|
+
/** 到達済みの最遠ステップの 0 始まりインデックス(進捗軸)。未指定時は表示中ステップ位置。 */
|
|
11
|
+
progress?: number;
|
|
12
|
+
/** 各 href のアクティブ判定に前置するパス。 */
|
|
13
|
+
pathPrefix?: string;
|
|
14
|
+
/** アクティブ判定をカスタムする関数。 */
|
|
15
|
+
customPathMatcher?: (currentPath: string, itemHref: string, item: StepItem) => boolean;
|
|
16
|
+
/** 自動検出した現在パスを上書き。 */
|
|
17
|
+
currentPath?: string;
|
|
18
|
+
id?: string;
|
|
19
|
+
/** @default 'horizontal' */
|
|
20
|
+
orientation?: StepNavOrientation;
|
|
21
|
+
/** @default 'medium' */
|
|
22
|
+
size?: ComponentSize;
|
|
23
|
+
/** 現在/完了ステップの強調色。テーマ色より優先。 */
|
|
24
|
+
color?: string;
|
|
25
|
+
/** 番号の代わりに各ステップの icon を表示。 @default false */
|
|
26
|
+
showIcon?: boolean;
|
|
27
|
+
/** 追加インラインスタイル。 */
|
|
28
|
+
customStyle?: string;
|
|
29
|
+
iconVariant?: IconVariant;
|
|
30
|
+
iconWeight?: IconWeight;
|
|
31
|
+
iconGrade?: IconGrade;
|
|
32
|
+
iconOpticalSize?: IconOpticalSize;
|
|
33
|
+
/** インページ方式でステップのクリック遷移を許可。 @default false */
|
|
34
|
+
clickable?: boolean;
|
|
35
|
+
/** コンポーネント全体を無効化。 @default false */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** アニメーションを抑制。 @default false */
|
|
38
|
+
reducedMotion?: boolean;
|
|
39
|
+
ariaLabel?: string;
|
|
40
|
+
ariaLabelledby?: string;
|
|
41
|
+
/** 表示中ステップ変更(インページ方式)。 */
|
|
42
|
+
onchange?: BivariantValueHandler<string>;
|
|
43
|
+
onclick?: MouseHandler;
|
|
44
|
+
onfocus?: FocusHandler;
|
|
45
|
+
onblur?: FocusHandler;
|
|
46
|
+
onkeydown?: KeyboardHandler;
|
|
47
|
+
};
|
|
48
|
+
declare const StepNav: import("svelte").Component<StepNavProps, {}, "value">;
|
|
49
|
+
type StepNav = ReturnType<typeof StepNav>;
|
|
50
|
+
export default StepNav;
|
package/dist/i18n/index.d.ts
CHANGED
|
@@ -58,6 +58,13 @@ export declare const TRANSLATIONS: {
|
|
|
58
58
|
readonly goToPage: "Go to page {{page}}";
|
|
59
59
|
readonly ellipsis: "Omitted pages";
|
|
60
60
|
};
|
|
61
|
+
readonly stepNav: {
|
|
62
|
+
readonly step: "Step {{number}}";
|
|
63
|
+
readonly completed: "Completed";
|
|
64
|
+
readonly current: "Current step";
|
|
65
|
+
readonly upcoming: "Not started";
|
|
66
|
+
readonly error: "Error";
|
|
67
|
+
};
|
|
61
68
|
};
|
|
62
69
|
readonly ja: {
|
|
63
70
|
readonly datepicker: {
|
|
@@ -117,6 +124,13 @@ export declare const TRANSLATIONS: {
|
|
|
117
124
|
readonly goToPage: "{{page}}ページ目へ移動";
|
|
118
125
|
readonly ellipsis: "省略されたページ";
|
|
119
126
|
};
|
|
127
|
+
readonly stepNav: {
|
|
128
|
+
readonly step: "ステップ{{number}}";
|
|
129
|
+
readonly completed: "完了";
|
|
130
|
+
readonly current: "現在のステップ";
|
|
131
|
+
readonly upcoming: "未到達";
|
|
132
|
+
readonly error: "エラー";
|
|
133
|
+
};
|
|
120
134
|
};
|
|
121
135
|
readonly fr: {
|
|
122
136
|
readonly datepicker: {
|
|
@@ -176,6 +190,13 @@ export declare const TRANSLATIONS: {
|
|
|
176
190
|
readonly goToPage: "Aller à la page {{page}}";
|
|
177
191
|
readonly ellipsis: "Pages omises";
|
|
178
192
|
};
|
|
193
|
+
readonly stepNav: {
|
|
194
|
+
readonly step: "Étape {{number}}";
|
|
195
|
+
readonly completed: "Terminé";
|
|
196
|
+
readonly current: "Étape actuelle";
|
|
197
|
+
readonly upcoming: "Non commencé";
|
|
198
|
+
readonly error: "Erreur";
|
|
199
|
+
};
|
|
179
200
|
};
|
|
180
201
|
readonly de: {
|
|
181
202
|
readonly datepicker: {
|
|
@@ -235,6 +256,13 @@ export declare const TRANSLATIONS: {
|
|
|
235
256
|
readonly goToPage: "Zu Seite {{page}} gehen";
|
|
236
257
|
readonly ellipsis: "Ausgelassene Seiten";
|
|
237
258
|
};
|
|
259
|
+
readonly stepNav: {
|
|
260
|
+
readonly step: "Schritt {{number}}";
|
|
261
|
+
readonly completed: "Abgeschlossen";
|
|
262
|
+
readonly current: "Aktueller Schritt";
|
|
263
|
+
readonly upcoming: "Noch nicht begonnen";
|
|
264
|
+
readonly error: "Fehler";
|
|
265
|
+
};
|
|
238
266
|
};
|
|
239
267
|
readonly es: {
|
|
240
268
|
readonly datepicker: {
|
|
@@ -294,6 +322,13 @@ export declare const TRANSLATIONS: {
|
|
|
294
322
|
readonly goToPage: "Ir a la página {{page}}";
|
|
295
323
|
readonly ellipsis: "Páginas omitidas";
|
|
296
324
|
};
|
|
325
|
+
readonly stepNav: {
|
|
326
|
+
readonly step: "Paso {{number}}";
|
|
327
|
+
readonly completed: "Completado";
|
|
328
|
+
readonly current: "Paso actual";
|
|
329
|
+
readonly upcoming: "No iniciado";
|
|
330
|
+
readonly error: "Error";
|
|
331
|
+
};
|
|
297
332
|
};
|
|
298
333
|
readonly 'zh-cn': {
|
|
299
334
|
readonly datepicker: {
|
|
@@ -353,6 +388,13 @@ export declare const TRANSLATIONS: {
|
|
|
353
388
|
readonly goToPage: "转到第{{page}}页";
|
|
354
389
|
readonly ellipsis: "省略的页面";
|
|
355
390
|
};
|
|
391
|
+
readonly stepNav: {
|
|
392
|
+
readonly step: "第{{number}}步";
|
|
393
|
+
readonly completed: "已完成";
|
|
394
|
+
readonly current: "当前步骤";
|
|
395
|
+
readonly upcoming: "未开始";
|
|
396
|
+
readonly error: "错误";
|
|
397
|
+
};
|
|
356
398
|
};
|
|
357
399
|
};
|
|
358
400
|
type NestedKeyOf<T> = T extends object ? {
|
|
@@ -56,4 +56,11 @@ export declare const de: {
|
|
|
56
56
|
readonly goToPage: "Zu Seite {{page}} gehen";
|
|
57
57
|
readonly ellipsis: "Ausgelassene Seiten";
|
|
58
58
|
};
|
|
59
|
+
readonly stepNav: {
|
|
60
|
+
readonly step: "Schritt {{number}}";
|
|
61
|
+
readonly completed: "Abgeschlossen";
|
|
62
|
+
readonly current: "Aktueller Schritt";
|
|
63
|
+
readonly upcoming: "Noch nicht begonnen";
|
|
64
|
+
readonly error: "Fehler";
|
|
65
|
+
};
|
|
59
66
|
};
|
package/dist/i18n/locales/de.js
CHANGED
|
@@ -55,5 +55,12 @@ export const de = {
|
|
|
55
55
|
nextPage: 'Zur nächsten Seite gehen',
|
|
56
56
|
goToPage: 'Zu Seite {{page}} gehen',
|
|
57
57
|
ellipsis: 'Ausgelassene Seiten'
|
|
58
|
+
},
|
|
59
|
+
stepNav: {
|
|
60
|
+
step: 'Schritt {{number}}',
|
|
61
|
+
completed: 'Abgeschlossen',
|
|
62
|
+
current: 'Aktueller Schritt',
|
|
63
|
+
upcoming: 'Noch nicht begonnen',
|
|
64
|
+
error: 'Fehler'
|
|
58
65
|
}
|
|
59
66
|
};
|
|
@@ -56,4 +56,11 @@ export declare const en: {
|
|
|
56
56
|
readonly goToPage: "Go to page {{page}}";
|
|
57
57
|
readonly ellipsis: "Omitted pages";
|
|
58
58
|
};
|
|
59
|
+
readonly stepNav: {
|
|
60
|
+
readonly step: "Step {{number}}";
|
|
61
|
+
readonly completed: "Completed";
|
|
62
|
+
readonly current: "Current step";
|
|
63
|
+
readonly upcoming: "Not started";
|
|
64
|
+
readonly error: "Error";
|
|
65
|
+
};
|
|
59
66
|
};
|
package/dist/i18n/locales/en.js
CHANGED
|
@@ -55,5 +55,12 @@ export const en = {
|
|
|
55
55
|
nextPage: 'Go to next page',
|
|
56
56
|
goToPage: 'Go to page {{page}}',
|
|
57
57
|
ellipsis: 'Omitted pages'
|
|
58
|
+
},
|
|
59
|
+
stepNav: {
|
|
60
|
+
step: 'Step {{number}}',
|
|
61
|
+
completed: 'Completed',
|
|
62
|
+
current: 'Current step',
|
|
63
|
+
upcoming: 'Not started',
|
|
64
|
+
error: 'Error'
|
|
58
65
|
}
|
|
59
66
|
};
|
|
@@ -56,4 +56,11 @@ export declare const es: {
|
|
|
56
56
|
readonly goToPage: "Ir a la página {{page}}";
|
|
57
57
|
readonly ellipsis: "Páginas omitidas";
|
|
58
58
|
};
|
|
59
|
+
readonly stepNav: {
|
|
60
|
+
readonly step: "Paso {{number}}";
|
|
61
|
+
readonly completed: "Completado";
|
|
62
|
+
readonly current: "Paso actual";
|
|
63
|
+
readonly upcoming: "No iniciado";
|
|
64
|
+
readonly error: "Error";
|
|
65
|
+
};
|
|
59
66
|
};
|
package/dist/i18n/locales/es.js
CHANGED
|
@@ -55,5 +55,12 @@ export const es = {
|
|
|
55
55
|
nextPage: 'Ir a la página siguiente',
|
|
56
56
|
goToPage: 'Ir a la página {{page}}',
|
|
57
57
|
ellipsis: 'Páginas omitidas'
|
|
58
|
+
},
|
|
59
|
+
stepNav: {
|
|
60
|
+
step: 'Paso {{number}}',
|
|
61
|
+
completed: 'Completado',
|
|
62
|
+
current: 'Paso actual',
|
|
63
|
+
upcoming: 'No iniciado',
|
|
64
|
+
error: 'Error'
|
|
58
65
|
}
|
|
59
66
|
};
|
|
@@ -56,4 +56,11 @@ export declare const fr: {
|
|
|
56
56
|
readonly goToPage: "Aller à la page {{page}}";
|
|
57
57
|
readonly ellipsis: "Pages omises";
|
|
58
58
|
};
|
|
59
|
+
readonly stepNav: {
|
|
60
|
+
readonly step: "Étape {{number}}";
|
|
61
|
+
readonly completed: "Terminé";
|
|
62
|
+
readonly current: "Étape actuelle";
|
|
63
|
+
readonly upcoming: "Non commencé";
|
|
64
|
+
readonly error: "Erreur";
|
|
65
|
+
};
|
|
59
66
|
};
|
package/dist/i18n/locales/fr.js
CHANGED
|
@@ -55,5 +55,12 @@ export const fr = {
|
|
|
55
55
|
nextPage: 'Aller à la page suivante',
|
|
56
56
|
goToPage: 'Aller à la page {{page}}',
|
|
57
57
|
ellipsis: 'Pages omises'
|
|
58
|
+
},
|
|
59
|
+
stepNav: {
|
|
60
|
+
step: 'Étape {{number}}',
|
|
61
|
+
completed: 'Terminé',
|
|
62
|
+
current: 'Étape actuelle',
|
|
63
|
+
upcoming: 'Non commencé',
|
|
64
|
+
error: 'Erreur'
|
|
58
65
|
}
|
|
59
66
|
};
|
|
@@ -56,4 +56,11 @@ export declare const ja: {
|
|
|
56
56
|
readonly goToPage: "{{page}}ページ目へ移動";
|
|
57
57
|
readonly ellipsis: "省略されたページ";
|
|
58
58
|
};
|
|
59
|
+
readonly stepNav: {
|
|
60
|
+
readonly step: "ステップ{{number}}";
|
|
61
|
+
readonly completed: "完了";
|
|
62
|
+
readonly current: "現在のステップ";
|
|
63
|
+
readonly upcoming: "未到達";
|
|
64
|
+
readonly error: "エラー";
|
|
65
|
+
};
|
|
59
66
|
};
|
package/dist/i18n/locales/ja.js
CHANGED
|
@@ -56,4 +56,11 @@ export declare const zhCn: {
|
|
|
56
56
|
readonly goToPage: "转到第{{page}}页";
|
|
57
57
|
readonly ellipsis: "省略的页面";
|
|
58
58
|
};
|
|
59
|
+
readonly stepNav: {
|
|
60
|
+
readonly step: "第{{number}}步";
|
|
61
|
+
readonly completed: "已完成";
|
|
62
|
+
readonly current: "当前步骤";
|
|
63
|
+
readonly upcoming: "未开始";
|
|
64
|
+
readonly error: "错误";
|
|
65
|
+
};
|
|
59
66
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { default as Radio } from './components/Radio.svelte';
|
|
|
25
25
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
26
26
|
export { default as Select } from './components/Select.svelte';
|
|
27
27
|
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
28
|
+
export { default as StepNav } from './components/StepNav.svelte';
|
|
28
29
|
export { default as Slider } from './components/Slider.svelte';
|
|
29
30
|
export { default as Snackbar } from './components/Snackbar.svelte';
|
|
30
31
|
export { default as SnackbarItem } from './components/SnackbarItem.svelte';
|
|
@@ -68,6 +69,7 @@ export type { RadioProps } from './components/Radio.svelte';
|
|
|
68
69
|
export type { RadioGroupProps } from './components/RadioGroup.svelte';
|
|
69
70
|
export type { SelectProps } from './components/Select.svelte';
|
|
70
71
|
export type { SegmentedControlProps } from './components/SegmentedControl.svelte';
|
|
72
|
+
export type { StepNavProps } from './components/StepNav.svelte';
|
|
71
73
|
export type { SliderProps } from './components/Slider.svelte';
|
|
72
74
|
export type { SnackbarProps } from './components/Snackbar.svelte';
|
|
73
75
|
export type { SnackbarItemProps } from './components/SnackbarItem.svelte';
|
|
@@ -83,8 +85,9 @@ export type { NavProps } from './components/Nav.svelte';
|
|
|
83
85
|
export type { NavItemProps, NavItemSelectedVariant } from './components/NavItem.svelte';
|
|
84
86
|
export type { TabProps } from './components/Tab.svelte';
|
|
85
87
|
export type { TextareaProps } from './components/Textarea.svelte';
|
|
86
|
-
export type { PopupPosition, SnackbarPosition, FabPosition, ButtonVariant, ButtonSize, ComponentSize, SnackbarType, SnackbarVariant, BadgeVariant, DatepickerMode, FocusStyle, NavVariant, ChildrenVariant } from './types/propOptions';
|
|
88
|
+
export type { PopupPosition, SnackbarPosition, FabPosition, ButtonVariant, ButtonSize, ComponentSize, SnackbarType, SnackbarVariant, BadgeVariant, DatepickerMode, FocusStyle, NavVariant, ChildrenVariant, StepNavOrientation } from './types/propOptions';
|
|
87
89
|
export type { MenuItem } from './types/menuItem';
|
|
90
|
+
export type { StepItem } from './types/stepItem';
|
|
88
91
|
export type { SegmentedControlItem } from './types/segmentedControlItem';
|
|
89
92
|
export type { Option, OptionValue } from './types/options';
|
|
90
93
|
export type { IconVariant, IconWeight, IconGrade, IconOpticalSize } from './types/icon';
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export { default as Radio } from './components/Radio.svelte';
|
|
|
26
26
|
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
27
27
|
export { default as Select } from './components/Select.svelte';
|
|
28
28
|
export { default as SegmentedControl } from './components/SegmentedControl.svelte';
|
|
29
|
+
export { default as StepNav } from './components/StepNav.svelte';
|
|
29
30
|
export { default as Slider } from './components/Slider.svelte';
|
|
30
31
|
export { default as Snackbar } from './components/Snackbar.svelte';
|
|
31
32
|
export { default as SnackbarItem } from './components/SnackbarItem.svelte';
|
|
@@ -62,6 +62,11 @@ export type FocusStyle = 'background' | 'outline' | 'none';
|
|
|
62
62
|
* Used by Nav component
|
|
63
63
|
*/
|
|
64
64
|
export type NavVariant = 'vertical' | 'horizontal' | 'mobile';
|
|
65
|
+
/**
|
|
66
|
+
* StepNav orientation type
|
|
67
|
+
* Used by StepNav component
|
|
68
|
+
*/
|
|
69
|
+
export type StepNavOrientation = 'horizontal' | 'vertical';
|
|
65
70
|
/**
|
|
66
71
|
* Display variant for Nav hierarchical (children) menus.
|
|
67
72
|
* - `popup`: child items appear in a floating panel (all variants)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type StepItem = {
|
|
2
|
+
/** 表示ラベル。 */
|
|
3
|
+
label: string;
|
|
4
|
+
/** インページ方式での識別子(value 制御時に一致判定へ使用)。省略時は配列 index の文字列(`String(index)`)がフォールバックに使われる。 */
|
|
5
|
+
value?: string;
|
|
6
|
+
/** URL 方式でのリンク先。指定があると URL 方式として扱われる。 */
|
|
7
|
+
href?: string;
|
|
8
|
+
/** ラベル下の補足説明。 */
|
|
9
|
+
description?: string;
|
|
10
|
+
/** 番号の代わりに表示するアイコン名(Material Symbols)。 */
|
|
11
|
+
icon?: string;
|
|
12
|
+
/** エラー状態。進捗軸の状態に上書きしてエラー表示する。 */
|
|
13
|
+
error?: boolean;
|
|
14
|
+
/** 個別無効化。 */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** URL 方式の追加マッチパス(`matchPath` 準拠)。 */
|
|
17
|
+
matchingPath?: string[];
|
|
18
|
+
/** URL 方式の完全一致指定。 */
|
|
19
|
+
strictMatch?: boolean;
|
|
20
|
+
/** アクセシブルネーム(アイコンのみ等)。 */
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|