@xleddyl/443-components-vue 0.10.2 → 0.11.0
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/CATALOG.md +185 -175
- package/README.md +47 -21
- package/dist/components/data/Tag.vue +2 -0
- package/dist/components/data/Tag.vue.d.ts +2 -0
- package/dist/components/data/TagList.vue +239 -0
- package/dist/components/data/TagList.vue.d.ts +53 -0
- package/dist/components/data/index.d.ts +1 -0
- package/dist/components/data/index.mjs +1 -0
- package/dist/components/data/types.d.ts +15 -0
- package/dist/components/forms/Combobox.vue +33 -4
- package/dist/components/forms/Combobox.vue.d.ts +3 -1
- package/dist/components/forms/NumberInput.vue.d.ts +1 -1
- package/dist/components/forms/PinInput.vue.d.ts +2 -2
- package/dist/components/forms/types.d.ts +2 -0
- package/dist/components/layout/AppShell.vue +14 -2
- package/dist/components/layout/AppShell.vue.d.ts +3 -1
- package/dist/components/layout/Stepper.vue +148 -0
- package/dist/components/layout/Stepper.vue.d.ts +420 -0
- package/dist/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.mjs +1 -0
- package/dist/components/layout/types.d.ts +12 -0
- package/dist/components/overlays/Toaster.vue +10 -0
- package/dist/components/overlays/Toaster.vue.d.ts +1 -1
- package/dist/components/templates/DashboardLayout.vue +2 -0
- package/dist/components/templates/DashboardLayout.vue.d.ts +3 -1
- package/dist/components/templates/types.d.ts +2 -1
- package/dist/composables/data.d.ts +1 -0
- package/dist/composables/data.mjs +1 -0
- package/dist/composables/useSortable.d.ts +38 -0
- package/dist/composables/useSortable.mjs +215 -0
- package/dist/grid/cells/BadgeCell.vue +90 -18
- package/dist/grid/cells/BadgeCell.vue.d.ts +14 -14
- package/dist/grid/data-grid.css +24 -0
- package/dist/grid/spreadsheet.mjs +8 -4
- package/dist/grid/types.d.ts +2 -0
- package/dist/internal/data/sortable.d.ts +5 -0
- package/dist/internal/data/sortable.mjs +23 -0
- package/dist/internal/overlays/avoid.d.ts +26 -0
- package/dist/internal/overlays/avoid.mjs +134 -0
- package/dist/locale/de.mjs +20 -0
- package/dist/locale/en.mjs +20 -0
- package/dist/locale/it.mjs +20 -0
- package/dist/locale/types.d.ts +20 -0
- package/dist/nuxt/module.mjs +14 -0
- package/dist/nuxt/runtime/layouts/dashboard.vue +8 -1
- package/dist/styles/data/tag-list.css +93 -0
- package/dist/styles/data.css +1 -0
- package/dist/styles/forced-colors.css +10 -0
- package/dist/styles/layout/app-shell.css +23 -0
- package/dist/styles/layout/stepper.css +244 -0
- package/dist/styles/layout.css +1 -0
- package/dist/styles/overlays/toast.css +17 -12
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:class="[
|
|
21
21
|
`k-field-${size}`,
|
|
22
22
|
{
|
|
23
|
-
'is-invalid':
|
|
23
|
+
'is-invalid': invalid,
|
|
24
24
|
'is-disabled': control.disabled.value,
|
|
25
25
|
'is-readonly': readonly
|
|
26
26
|
}
|
|
@@ -48,8 +48,14 @@
|
|
|
48
48
|
v-bind="$attrs"
|
|
49
49
|
:disabled="control.disabled.value || void 0"
|
|
50
50
|
:aria-required="control.required.value ? 'true' : void 0"
|
|
51
|
-
:aria-invalid="
|
|
52
|
-
:aria-describedby="
|
|
51
|
+
:aria-invalid="invalid ? 'true' : void 0"
|
|
52
|
+
:aria-describedby="
|
|
53
|
+
joinIds(
|
|
54
|
+
$attrs['aria-describedby'],
|
|
55
|
+
control.describedBy.value,
|
|
56
|
+
localError ? errorId : void 0
|
|
57
|
+
)
|
|
58
|
+
"
|
|
53
59
|
class="k-combobox-input"
|
|
54
60
|
type="text"
|
|
55
61
|
role="combobox"
|
|
@@ -167,6 +173,7 @@
|
|
|
167
173
|
<slot name="empty" :query="query">{{ emptyLabel }}</slot>
|
|
168
174
|
</div>
|
|
169
175
|
</div>
|
|
176
|
+
<FieldMessage v-if="localError" :id="errorId" :message="createError" />
|
|
170
177
|
<LiveRegion :message="live.message.value" />
|
|
171
178
|
<template v-if="formName">
|
|
172
179
|
<input
|
|
@@ -203,6 +210,7 @@ import { normalizeSearch } from "../../internal/core/text";
|
|
|
203
210
|
import { translateOr } from "../../locale";
|
|
204
211
|
import { joinIds } from "../../internal/forms/control";
|
|
205
212
|
import Icon from "../core/Icon.vue";
|
|
213
|
+
import FieldMessage from "../core/FieldMessage.vue";
|
|
206
214
|
import Spinner from "../core/Spinner.vue";
|
|
207
215
|
defineOptions({ inheritAttrs: false });
|
|
208
216
|
const props = defineProps({
|
|
@@ -213,6 +221,8 @@ const props = defineProps({
|
|
|
213
221
|
multiple: { type: Boolean, required: false },
|
|
214
222
|
hideTags: { type: Boolean, required: false },
|
|
215
223
|
creatable: { type: [Boolean, Function], required: false },
|
|
224
|
+
createValidate: { type: Function, required: false },
|
|
225
|
+
createTransform: { type: Function, required: false },
|
|
216
226
|
debounce: { type: Number, required: false, default: 250 },
|
|
217
227
|
minQueryLength: { type: Number, required: false, default: 0 },
|
|
218
228
|
maxSelected: { type: Number, required: false },
|
|
@@ -260,6 +270,8 @@ const rootRef = useTemplateRef("rootRef");
|
|
|
260
270
|
const controlRef = useTemplateRef("controlRef");
|
|
261
271
|
const panelRef = useTemplateRef("panelRef");
|
|
262
272
|
const inputRef = useTemplateRef("inputRef");
|
|
273
|
+
const errorId = `${baseId}-create-error`;
|
|
274
|
+
const createError = ref();
|
|
263
275
|
const editing = ref(false);
|
|
264
276
|
const activeIndex = ref(-1);
|
|
265
277
|
const results = shallowRef([]);
|
|
@@ -294,6 +306,13 @@ const layer = useFloatingLayer({
|
|
|
294
306
|
onOpen: () => emit("open"),
|
|
295
307
|
onClose: () => emit("close")
|
|
296
308
|
});
|
|
309
|
+
const invalid = computed(() => control.invalid.value || Boolean(createError.value));
|
|
310
|
+
const localError = computed(() => Boolean(createError.value) && !control.field);
|
|
311
|
+
function setCreateError(message) {
|
|
312
|
+
if (createError.value === message) return;
|
|
313
|
+
createError.value = message;
|
|
314
|
+
control.setControlError(message);
|
|
315
|
+
}
|
|
297
316
|
const interactive = computed(() => !control.disabled.value && !props.readonly);
|
|
298
317
|
const busy = computed(() => props.loading || search.searching.value);
|
|
299
318
|
const formName = computed(() => props.name ?? control.field?.name.value);
|
|
@@ -336,7 +355,9 @@ const filteredOptions = computed(() => {
|
|
|
336
355
|
});
|
|
337
356
|
const createCandidate = computed(() => {
|
|
338
357
|
if (!props.creatable || busy.value) return void 0;
|
|
339
|
-
const
|
|
358
|
+
const raw = activeQuery.value;
|
|
359
|
+
if (!raw) return void 0;
|
|
360
|
+
const text = (props.createTransform ? props.createTransform(raw) ?? "" : raw).trim();
|
|
340
361
|
if (!text) return void 0;
|
|
341
362
|
const needle = normalizeSearch(text);
|
|
342
363
|
const exists = [...filteredOptions.value, ...known.values()].some(
|
|
@@ -423,6 +444,7 @@ function resetQuery() {
|
|
|
423
444
|
editing.value = false;
|
|
424
445
|
query.value = "";
|
|
425
446
|
}
|
|
447
|
+
watch(query, () => setCreateError(void 0));
|
|
426
448
|
function onInput(event) {
|
|
427
449
|
query.value = event.target.value;
|
|
428
450
|
editing.value = true;
|
|
@@ -531,6 +553,13 @@ function onKeydown(event) {
|
|
|
531
553
|
async function choose(item) {
|
|
532
554
|
if (item.option.disabled) return;
|
|
533
555
|
if (item.kind === "create") {
|
|
556
|
+
const verdict = props.createValidate?.(item.option.label);
|
|
557
|
+
if (typeof verdict === "string" && verdict) {
|
|
558
|
+
setCreateError(verdict);
|
|
559
|
+
if (!control.field) live.announce(verdict);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
setCreateError(void 0);
|
|
534
563
|
let created;
|
|
535
564
|
try {
|
|
536
565
|
created = typeof props.creatable === "function" ? await props.creatable(item.option.label) : item.option;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { IconName } from '../../composables/useIcons';
|
|
2
2
|
import type { FieldSize } from '../core/types';
|
|
3
|
-
import type { ComboboxCreate, ComboboxOption, ComboboxSearch } from './types';
|
|
3
|
+
import type { ComboboxCreate, ComboboxCreateTransform, ComboboxCreateValidate, ComboboxOption, ComboboxSearch } from './types';
|
|
4
4
|
declare const __VLS_export: <T extends string | number, M extends T | T[] | null | undefined = T | T[] | null | undefined>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
5
5
|
props: import("vue").PublicProps & __VLS_PrettifyLocal<({
|
|
6
6
|
options?: ComboboxOption<T>[];
|
|
@@ -10,6 +10,8 @@ declare const __VLS_export: <T extends string | number, M extends T | T[] | null
|
|
|
10
10
|
multiple?: boolean;
|
|
11
11
|
hideTags?: boolean;
|
|
12
12
|
creatable?: boolean | ComboboxCreate<T>;
|
|
13
|
+
createValidate?: ComboboxCreateValidate;
|
|
14
|
+
createTransform?: ComboboxCreateTransform;
|
|
13
15
|
debounce?: number;
|
|
14
16
|
minQueryLength?: number;
|
|
15
17
|
maxSelected?: number;
|
|
@@ -487,10 +487,10 @@ declare const __VLS_export: import("vue").DefineComponent<{
|
|
|
487
487
|
"onUpdate:modelValue"?: ((value: number | null | undefined) => any) | undefined;
|
|
488
488
|
}>, {
|
|
489
489
|
size: FieldSize;
|
|
490
|
+
stepper: boolean;
|
|
490
491
|
invalid: boolean;
|
|
491
492
|
step: number;
|
|
492
493
|
align: "end" | "start";
|
|
493
|
-
stepper: boolean;
|
|
494
494
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
495
495
|
declare const _default: typeof __VLS_export;
|
|
496
496
|
export default _default;
|
|
@@ -243,8 +243,8 @@ declare const __VLS_export: import("vue").DefineComponent<{
|
|
|
243
243
|
clear: () => void;
|
|
244
244
|
el: Readonly<import("vue").ShallowRef<HTMLInputElement | null>>;
|
|
245
245
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
246
|
-
"update:modelValue": (value: string | null | undefined) => any;
|
|
247
246
|
complete: (value: string) => any;
|
|
247
|
+
"update:modelValue": (value: string | null | undefined) => any;
|
|
248
248
|
}, string, import("vue").PublicProps, Readonly<{
|
|
249
249
|
length?: number;
|
|
250
250
|
mode?: PinInputMode;
|
|
@@ -484,8 +484,8 @@ declare const __VLS_export: import("vue").DefineComponent<{
|
|
|
484
484
|
src?: string | undefined | undefined;
|
|
485
485
|
onCancel?: ((payload: Event) => void) | undefined;
|
|
486
486
|
}> & Readonly<{
|
|
487
|
-
"onUpdate:modelValue"?: ((value: string | null | undefined) => any) | undefined;
|
|
488
487
|
onComplete?: ((value: string) => any) | undefined;
|
|
488
|
+
"onUpdate:modelValue"?: ((value: string | null | undefined) => any) | undefined;
|
|
489
489
|
}>, {
|
|
490
490
|
length: number;
|
|
491
491
|
size: FieldSize;
|
|
@@ -31,6 +31,8 @@ export interface ComboboxOption<T = string | number> {
|
|
|
31
31
|
keywords?: string[];
|
|
32
32
|
}
|
|
33
33
|
export type ComboboxSearch<T = string | number> = (query: string, signal: AbortSignal) => Promise<ComboboxOption<T>[]> | ComboboxOption<T>[];
|
|
34
|
+
export type ComboboxCreateValidate = (input: string) => string | true | null | undefined | void;
|
|
35
|
+
export type ComboboxCreateTransform = (input: string) => string | null | undefined;
|
|
34
36
|
export type ComboboxCreate<T = string | number> = (query: string) => ComboboxOption<T> | Promise<ComboboxOption<T>>;
|
|
35
37
|
export interface CalendarRange {
|
|
36
38
|
start: string | null;
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
'is-nav-open': open,
|
|
9
9
|
'is-mobile': mobile,
|
|
10
10
|
'is-named': viewTransition,
|
|
11
|
-
'is-fill':
|
|
11
|
+
'is-fill': contentMode === 'bleed'
|
|
12
12
|
}"
|
|
13
|
+
:data-content="contentMode"
|
|
13
14
|
@keydown.esc="onEscape"
|
|
14
15
|
>
|
|
15
16
|
<a
|
|
@@ -62,7 +63,16 @@
|
|
|
62
63
|
<slot name="topbar" :open-nav="openNav" :mobile="mobile" :collapsed="collapsed" />
|
|
63
64
|
</header>
|
|
64
65
|
<main :id="mainId" class="k-shell-main" tabindex="-1">
|
|
65
|
-
<div
|
|
66
|
+
<div
|
|
67
|
+
class="k-shell-content"
|
|
68
|
+
:class="[
|
|
69
|
+
`k-shell-content-${contentMode}`,
|
|
70
|
+
{
|
|
71
|
+
'is-fluid': fluid && contentMode === 'page',
|
|
72
|
+
'is-fill': contentMode === 'bleed'
|
|
73
|
+
}
|
|
74
|
+
]"
|
|
75
|
+
>
|
|
66
76
|
<slot />
|
|
67
77
|
</div>
|
|
68
78
|
</main>
|
|
@@ -98,11 +108,13 @@ const props = defineProps({
|
|
|
98
108
|
skipLabel: { type: String, required: false },
|
|
99
109
|
fluid: { type: Boolean, required: false },
|
|
100
110
|
fill: { type: Boolean, required: false },
|
|
111
|
+
content: { type: String, required: false },
|
|
101
112
|
topbar: { type: Boolean, required: false, default: void 0 },
|
|
102
113
|
storageKey: { type: [String, Boolean], required: false },
|
|
103
114
|
shortcut: { type: [String, Boolean], required: false, default: false },
|
|
104
115
|
viewTransition: { type: Boolean, required: false }
|
|
105
116
|
});
|
|
117
|
+
const contentMode = computed(() => props.content ?? (props.fill ? "bleed" : "page"));
|
|
106
118
|
const { t } = useLocale();
|
|
107
119
|
const sidebarText = computed(() => props.sidebarLabel ?? t("shell.navigation"));
|
|
108
120
|
const closeText = computed(() => props.closeLabel ?? t("shell.closeNavigation"));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppShellVariant } from './types';
|
|
1
|
+
import type { AppShellVariant, ShellContent } from './types';
|
|
2
2
|
declare function toggleCollapsed(): void;
|
|
3
3
|
declare function openNav(): void;
|
|
4
4
|
declare function closeNav(options?: {
|
|
@@ -33,6 +33,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
33
33
|
skipLabel?: string;
|
|
34
34
|
fluid?: boolean;
|
|
35
35
|
fill?: boolean;
|
|
36
|
+
content?: ShellContent;
|
|
36
37
|
topbar?: boolean;
|
|
37
38
|
storageKey?: string | false;
|
|
38
39
|
shortcut?: string | false;
|
|
@@ -247,6 +248,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
247
248
|
skipLabel?: string;
|
|
248
249
|
fluid?: boolean;
|
|
249
250
|
fill?: boolean;
|
|
251
|
+
content?: ShellContent;
|
|
250
252
|
topbar?: boolean;
|
|
251
253
|
storageKey?: string | false;
|
|
252
254
|
shortcut?: string | false;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
class="k-stepper"
|
|
4
|
+
:class="[
|
|
5
|
+
`k-stepper-${orientation}`,
|
|
6
|
+
`k-stepper-${size}`,
|
|
7
|
+
{ 'is-compact': compact === true, 'is-auto-compact': compact === 'auto' }
|
|
8
|
+
]"
|
|
9
|
+
:aria-label="labelText"
|
|
10
|
+
>
|
|
11
|
+
<ol ref="list" class="k-stepper-list">
|
|
12
|
+
<li
|
|
13
|
+
v-for="(entry, index) in entries"
|
|
14
|
+
:key="entry.step.value"
|
|
15
|
+
class="k-stepper-item"
|
|
16
|
+
:class="{
|
|
17
|
+
'is-current': entry.current,
|
|
18
|
+
'is-complete': entry.status === 'complete',
|
|
19
|
+
'is-error': entry.status === 'error',
|
|
20
|
+
'is-upcoming': entry.status === 'upcoming',
|
|
21
|
+
'is-disabled': !entry.enabled
|
|
22
|
+
}"
|
|
23
|
+
:data-state="entry.status"
|
|
24
|
+
>
|
|
25
|
+
<component
|
|
26
|
+
:is="entry.enabled ? 'button' : 'span'"
|
|
27
|
+
:type="entry.enabled ? 'button' : void 0"
|
|
28
|
+
class="k-stepper-step"
|
|
29
|
+
:data-index="index"
|
|
30
|
+
:aria-current="entry.current ? 'step' : void 0"
|
|
31
|
+
:aria-disabled="!entry.enabled && !entry.current && !readonly ? 'true' : void 0"
|
|
32
|
+
:tabindex="entry.enabled ? index === focusIndex ? 0 : -1 : void 0"
|
|
33
|
+
@click="entry.enabled && select(entry.step)"
|
|
34
|
+
@focus="focusIndex = index"
|
|
35
|
+
@keydown="onKeydown($event, index)"
|
|
36
|
+
>
|
|
37
|
+
<span class="k-stepper-indicator" aria-hidden="true">
|
|
38
|
+
<Icon v-if="entry.status === 'error'" name="exclamation-circle" />
|
|
39
|
+
<Icon v-else-if="entry.status === 'complete'" name="check" />
|
|
40
|
+
<Icon v-else-if="entry.step.icon" :name="entry.step.icon" />
|
|
41
|
+
<span v-else class="k-stepper-number">{{ index + 1 }}</span>
|
|
42
|
+
</span>
|
|
43
|
+
<span class="k-stepper-text">
|
|
44
|
+
<span class="k-visually-hidden">{{ positionText(index) }}</span>
|
|
45
|
+
<span class="k-stepper-label">{{ entry.step.label }}</span>
|
|
46
|
+
<span v-if="entry.step.description" class="k-stepper-description">{{
|
|
47
|
+
entry.step.description
|
|
48
|
+
}}</span>
|
|
49
|
+
<span v-if="entry.status === 'complete'" class="k-visually-hidden">{{
|
|
50
|
+
completeText
|
|
51
|
+
}}</span>
|
|
52
|
+
<span v-else-if="entry.status === 'error'" class="k-visually-hidden">{{
|
|
53
|
+
errorText
|
|
54
|
+
}}</span>
|
|
55
|
+
</span>
|
|
56
|
+
</component>
|
|
57
|
+
<span
|
|
58
|
+
v-if="index < entries.length - 1"
|
|
59
|
+
class="k-stepper-connector"
|
|
60
|
+
aria-hidden="true"
|
|
61
|
+
/>
|
|
62
|
+
</li>
|
|
63
|
+
</ol>
|
|
64
|
+
<p v-if="compact !== false && currentEntry" class="k-stepper-summary" aria-hidden="true">
|
|
65
|
+
<span class="k-stepper-summary-position">{{ positionText(currentIndex) }}</span>
|
|
66
|
+
<span class="k-stepper-summary-label">{{ currentEntry.step.label }}</span>
|
|
67
|
+
</p>
|
|
68
|
+
</nav>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup>
|
|
72
|
+
import { computed, ref, useTemplateRef, watch } from "vue";
|
|
73
|
+
import { useLocale } from "../../composables/useLocale";
|
|
74
|
+
import { translateOr } from "../../locale";
|
|
75
|
+
import { rovingIndex } from "../../internal/core/roving";
|
|
76
|
+
import Icon from "../core/Icon.vue";
|
|
77
|
+
const props = defineProps({
|
|
78
|
+
steps: { type: Array, required: true },
|
|
79
|
+
label: { type: String, required: false },
|
|
80
|
+
orientation: { type: String, required: false, default: "horizontal" },
|
|
81
|
+
size: { type: String, required: false, default: "md" },
|
|
82
|
+
linear: { type: Boolean, required: false },
|
|
83
|
+
readonly: { type: Boolean, required: false },
|
|
84
|
+
compact: { type: [Boolean, String], required: false, default: "auto" },
|
|
85
|
+
positionLabel: { type: String, required: false },
|
|
86
|
+
completeLabel: { type: String, required: false },
|
|
87
|
+
errorLabel: { type: String, required: false }
|
|
88
|
+
});
|
|
89
|
+
const model = defineModel({ type: [String, Number] });
|
|
90
|
+
const emit = defineEmits(["select"]);
|
|
91
|
+
const { locale, t } = useLocale();
|
|
92
|
+
const list = useTemplateRef("list");
|
|
93
|
+
const labelText = computed(() => props.label ?? t("stepper.label"));
|
|
94
|
+
const completeText = computed(() => props.completeLabel ?? t("stepper.complete"));
|
|
95
|
+
const errorText = computed(() => props.errorLabel ?? t("stepper.error"));
|
|
96
|
+
function positionText(index) {
|
|
97
|
+
return translateOr(locale.value, props.positionLabel, "stepper.position", {
|
|
98
|
+
step: index + 1,
|
|
99
|
+
count: props.steps.length
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const currentIndex = computed(
|
|
103
|
+
() => Math.max(
|
|
104
|
+
0,
|
|
105
|
+
props.steps.findIndex((step) => step.value === model.value)
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
const entries = computed(
|
|
109
|
+
() => props.steps.map((step, index) => {
|
|
110
|
+
const current = index === currentIndex.value;
|
|
111
|
+
const status = step.state === "error" ? "error" : step.state === "complete" ? "complete" : current ? "current" : "upcoming";
|
|
112
|
+
const reachable = !props.linear || index <= currentIndex.value || step.state === "complete";
|
|
113
|
+
const enabled = !props.readonly && !step.disabled && reachable;
|
|
114
|
+
return { step, current, status, enabled };
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
const currentEntry = computed(() => entries.value[currentIndex.value]);
|
|
118
|
+
const focusIndex = ref(currentIndex.value);
|
|
119
|
+
watch(currentIndex, (index) => {
|
|
120
|
+
focusIndex.value = index;
|
|
121
|
+
});
|
|
122
|
+
watch(
|
|
123
|
+
entries,
|
|
124
|
+
(list2) => {
|
|
125
|
+
if (list2[focusIndex.value]?.enabled) return;
|
|
126
|
+
const fallback = list2.findIndex((entry) => entry.enabled);
|
|
127
|
+
if (fallback !== -1) focusIndex.value = fallback;
|
|
128
|
+
},
|
|
129
|
+
{ immediate: true }
|
|
130
|
+
);
|
|
131
|
+
function select(step) {
|
|
132
|
+
emit("select", step);
|
|
133
|
+
model.value = step.value;
|
|
134
|
+
}
|
|
135
|
+
function onKeydown(event, index) {
|
|
136
|
+
const target = rovingIndex(
|
|
137
|
+
event.key,
|
|
138
|
+
index,
|
|
139
|
+
entries.value.length,
|
|
140
|
+
props.orientation === "vertical" ? "vertical" : "horizontal",
|
|
141
|
+
(candidate) => !entries.value[candidate]?.enabled
|
|
142
|
+
);
|
|
143
|
+
if (target === null) return;
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
focusIndex.value = target;
|
|
146
|
+
list.value?.querySelector(`[data-index="${target}"]`)?.focus();
|
|
147
|
+
}
|
|
148
|
+
</script>
|