@shwfed/nuxt 0.10.9 → 0.10.11
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/module.json +1 -1
- package/dist/runtime/components/menu-tabs.d.vue.ts +29 -0
- package/dist/runtime/components/menu-tabs.vue +40 -0
- package/dist/runtime/components/menu-tabs.vue.d.ts +29 -0
- package/dist/runtime/components/ui/buttons/Buttons.vue +14 -1
- package/dist/runtime/components/ui/icon-picker/IconPicker.vue +61 -15
- package/dist/runtime/components/ui/menu-tabs/MenuTabs.d.vue.ts +29 -0
- package/dist/runtime/components/ui/menu-tabs/MenuTabs.vue +275 -0
- package/dist/runtime/components/ui/menu-tabs/MenuTabs.vue.d.ts +29 -0
- package/dist/runtime/components/ui/menu-tabs/schema.d.ts +58 -0
- package/dist/runtime/components/ui/menu-tabs/schema.js +24 -0
- package/dist/runtime/components/ui/menu-tabs-configurator/MenuTabsConfiguratorDialog.d.vue.ts +30 -0
- package/dist/runtime/components/ui/menu-tabs-configurator/MenuTabsConfiguratorDialog.vue +482 -0
- package/dist/runtime/components/ui/menu-tabs-configurator/MenuTabsConfiguratorDialog.vue.d.ts +30 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { MenuTabsConfigInput } from './ui/menu-tabs/schema.js';
|
|
3
|
+
export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from './ui/menu-tabs/schema.js';
|
|
4
|
+
export type { MenuTabsConfig, MenuTabsConfigInput, MenuTabsItem } from './ui/menu-tabs/schema.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
menus: readonly Readonly<{
|
|
13
|
+
id: string;
|
|
14
|
+
title: import("../utils/coders.js").LocaleValue;
|
|
15
|
+
to: string;
|
|
16
|
+
}>[];
|
|
17
|
+
}>) => any;
|
|
18
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
19
|
+
config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
20
|
+
context?: Record<string, unknown>;
|
|
21
|
+
}> & Readonly<{
|
|
22
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
23
|
+
menus: readonly Readonly<{
|
|
24
|
+
id: string;
|
|
25
|
+
title: import("../utils/coders.js").LocaleValue;
|
|
26
|
+
to: string;
|
|
27
|
+
}>[];
|
|
28
|
+
}>) => any) | undefined;
|
|
29
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import UiMenuTabs from "./ui/menu-tabs/MenuTabs.vue";
|
|
4
|
+
defineOptions({
|
|
5
|
+
inheritAttrs: false
|
|
6
|
+
});
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
config: { type: null, required: false },
|
|
9
|
+
context: { type: Object, required: false }
|
|
10
|
+
});
|
|
11
|
+
const emit = defineEmits(["update:config"]);
|
|
12
|
+
const defaultConfig = {
|
|
13
|
+
menus: []
|
|
14
|
+
};
|
|
15
|
+
function isEffectConfig(value) {
|
|
16
|
+
return typeof value === "object" && value !== null && "pipe" in value && typeof Reflect.get(value, "pipe") === "function";
|
|
17
|
+
}
|
|
18
|
+
function resolveConfig() {
|
|
19
|
+
if (isEffectConfig(props.config)) {
|
|
20
|
+
return props.config.pipe(Effect.map((value) => value ?? defaultConfig));
|
|
21
|
+
}
|
|
22
|
+
return Effect.succeed(props.config ?? defaultConfig);
|
|
23
|
+
}
|
|
24
|
+
function handleConfigUpdate(config) {
|
|
25
|
+
emit("update:config", config);
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from "./ui/menu-tabs/schema";
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<UiMenuTabs
|
|
35
|
+
v-bind="$attrs"
|
|
36
|
+
:config="resolveConfig()"
|
|
37
|
+
:context="props.context"
|
|
38
|
+
@update:config="handleConfigUpdate"
|
|
39
|
+
/>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import type { MenuTabsConfigInput } from './ui/menu-tabs/schema.js';
|
|
3
|
+
export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from './ui/menu-tabs/schema.js';
|
|
4
|
+
export type { MenuTabsConfig, MenuTabsConfigInput, MenuTabsItem } from './ui/menu-tabs/schema.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
menus: readonly Readonly<{
|
|
13
|
+
id: string;
|
|
14
|
+
title: import("../utils/coders.js").LocaleValue;
|
|
15
|
+
to: string;
|
|
16
|
+
}>[];
|
|
17
|
+
}>) => any;
|
|
18
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
19
|
+
config?: MenuTabsConfigInput | Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
20
|
+
context?: Record<string, unknown>;
|
|
21
|
+
}> & Readonly<{
|
|
22
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
23
|
+
menus: readonly Readonly<{
|
|
24
|
+
id: string;
|
|
25
|
+
title: import("../utils/coders.js").LocaleValue;
|
|
26
|
+
to: string;
|
|
27
|
+
}>[];
|
|
28
|
+
}>) => any) | undefined;
|
|
29
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -132,6 +132,17 @@ function handleConfiguratorConfirm(nextConfig) {
|
|
|
132
132
|
function isDropdownItem(item) {
|
|
133
133
|
return "items" in item;
|
|
134
134
|
}
|
|
135
|
+
function hasLeadingPrimaryGap(items, itemIndex) {
|
|
136
|
+
if (itemIndex === 0) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const item = items[itemIndex];
|
|
140
|
+
const previousItem = items[itemIndex - 1];
|
|
141
|
+
if (!item || !previousItem || isDropdownItem(item) || isDropdownItem(previousItem)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return item.variant === "primary" && previousItem.variant === "primary";
|
|
145
|
+
}
|
|
135
146
|
</script>
|
|
136
147
|
|
|
137
148
|
<script>
|
|
@@ -177,7 +188,7 @@ export { ButtonActionC, ButtonConfigC, ButtonConfigInputC, ButtonDropdownC, Butt
|
|
|
177
188
|
orientation="horizontal"
|
|
178
189
|
>
|
|
179
190
|
<template
|
|
180
|
-
v-for="item in group.items"
|
|
191
|
+
v-for="(item, itemIndex) in group.items"
|
|
181
192
|
:key="item.id"
|
|
182
193
|
>
|
|
183
194
|
<Button
|
|
@@ -185,6 +196,7 @@ export { ButtonActionC, ButtonConfigC, ButtonConfigInputC, ButtonDropdownC, Butt
|
|
|
185
196
|
data-slot="buttons-item"
|
|
186
197
|
:variant="item.variant"
|
|
187
198
|
:disabled="isButtonDisabled(item.id)"
|
|
199
|
+
:class="hasLeadingPrimaryGap(group.items, itemIndex) ? 'ml-px' : void 0"
|
|
188
200
|
:title="item.hideTitle ? getButtonLabel(item) : void 0"
|
|
189
201
|
@click="void runButton(item.id)"
|
|
190
202
|
>
|
|
@@ -208,6 +220,7 @@ export { ButtonActionC, ButtonConfigC, ButtonConfigInputC, ButtonDropdownC, Butt
|
|
|
208
220
|
data-slot="buttons-item"
|
|
209
221
|
:variant="item.variant"
|
|
210
222
|
:disabled="isButtonDisabled(item.id)"
|
|
223
|
+
:class="hasLeadingPrimaryGap(group.items, itemIndex) ? 'ml-px' : void 0"
|
|
211
224
|
:title="item.hideTitle ? getButtonLabel(item) : void 0"
|
|
212
225
|
@click="void runButton(item.id)"
|
|
213
226
|
>
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
import { icons } from "@iconify-json/fluent";
|
|
3
3
|
import { Icon } from "@iconify/vue";
|
|
4
4
|
import { useVirtualizer } from "@tanstack/vue-virtual";
|
|
5
|
-
import { computed, shallowRef, ref, watch } from "vue";
|
|
5
|
+
import { computed, onBeforeUnmount, onMounted, shallowRef, ref, watch } from "vue";
|
|
6
6
|
import { cn } from "../../../utils/cn";
|
|
7
7
|
import { InputGroup, InputGroupAddon, InputGroupInput } from "../input-group";
|
|
8
8
|
defineOptions({
|
|
9
9
|
inheritAttrs: false
|
|
10
10
|
});
|
|
11
|
-
const
|
|
12
|
-
const
|
|
11
|
+
const ICON_BUTTON_SIZE = 40;
|
|
12
|
+
const ICON_GRID_GAP = 8;
|
|
13
|
+
const ICON_GRID_MIN_COLUMN_WIDTH = 52;
|
|
13
14
|
const props = defineProps({
|
|
14
15
|
modelValue: { type: String, required: false },
|
|
15
16
|
placeholder: { type: String, required: false },
|
|
@@ -25,6 +26,8 @@ const availableIcons = Object.entries(icons.icons).filter(([name]) => name.endsW
|
|
|
25
26
|
const availableIconIds = new Set(availableIcons.map((icon) => icon.id));
|
|
26
27
|
const searchQuery = ref("");
|
|
27
28
|
const galleryElement = shallowRef(null);
|
|
29
|
+
const galleryWidth = ref(0);
|
|
30
|
+
let galleryResizeObserver;
|
|
28
31
|
const selectedIcon = computed(() => props.modelValue && availableIconIds.has(props.modelValue) ? availableIcons.find((icon) => icon.id === props.modelValue) : void 0);
|
|
29
32
|
const filteredIcons = computed(() => {
|
|
30
33
|
const term = searchQuery.value.trim().toLowerCase();
|
|
@@ -33,11 +36,21 @@ const filteredIcons = computed(() => {
|
|
|
33
36
|
}
|
|
34
37
|
return availableIcons.filter((icon) => icon.id.includes(term));
|
|
35
38
|
});
|
|
36
|
-
const
|
|
39
|
+
const iconColumns = computed(() => {
|
|
40
|
+
if (galleryWidth.value <= 0) {
|
|
41
|
+
return 8;
|
|
42
|
+
}
|
|
43
|
+
return Math.max(
|
|
44
|
+
1,
|
|
45
|
+
Math.floor((galleryWidth.value + ICON_GRID_GAP) / (ICON_GRID_MIN_COLUMN_WIDTH + ICON_GRID_GAP))
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
const iconRowHeight = computed(() => ICON_BUTTON_SIZE + ICON_GRID_GAP);
|
|
49
|
+
const rowCount = computed(() => Math.ceil(filteredIcons.value.length / iconColumns.value));
|
|
37
50
|
const rowVirtualizer = useVirtualizer(computed(() => ({
|
|
38
51
|
count: rowCount.value,
|
|
39
52
|
getScrollElement: () => galleryElement.value,
|
|
40
|
-
estimateSize: () =>
|
|
53
|
+
estimateSize: () => iconRowHeight.value,
|
|
41
54
|
overscan: 4
|
|
42
55
|
})));
|
|
43
56
|
watch(() => props.modelValue, (value) => {
|
|
@@ -45,9 +58,39 @@ watch(() => props.modelValue, (value) => {
|
|
|
45
58
|
searchQuery.value = "";
|
|
46
59
|
}
|
|
47
60
|
});
|
|
61
|
+
watch(galleryElement, (element) => {
|
|
62
|
+
if (galleryResizeObserver) {
|
|
63
|
+
galleryResizeObserver.disconnect();
|
|
64
|
+
galleryResizeObserver = void 0;
|
|
65
|
+
}
|
|
66
|
+
if (!element || typeof ResizeObserver === "undefined") {
|
|
67
|
+
galleryWidth.value = 0;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
galleryWidth.value = element.clientWidth;
|
|
71
|
+
galleryResizeObserver = new ResizeObserver((entries) => {
|
|
72
|
+
const entry = entries[0];
|
|
73
|
+
if (!entry) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
galleryWidth.value = entry.contentRect.width;
|
|
77
|
+
});
|
|
78
|
+
galleryResizeObserver.observe(element);
|
|
79
|
+
}, {
|
|
80
|
+
flush: "post"
|
|
81
|
+
});
|
|
82
|
+
onMounted(() => {
|
|
83
|
+
if (!galleryElement.value) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
galleryWidth.value = galleryElement.value.clientWidth;
|
|
87
|
+
});
|
|
88
|
+
onBeforeUnmount(() => {
|
|
89
|
+
galleryResizeObserver?.disconnect();
|
|
90
|
+
});
|
|
48
91
|
function iconsForRow(index) {
|
|
49
|
-
const start = index *
|
|
50
|
-
return filteredIcons.value.slice(start, start +
|
|
92
|
+
const start = index * iconColumns.value;
|
|
93
|
+
return filteredIcons.value.slice(start, start + iconColumns.value);
|
|
51
94
|
}
|
|
52
95
|
function selectIcon(iconId) {
|
|
53
96
|
if (props.disabled) {
|
|
@@ -88,26 +131,28 @@ function handleSearchUpdate(value) {
|
|
|
88
131
|
<div
|
|
89
132
|
data-slot="icon-picker-hero"
|
|
90
133
|
:data-icon-id="selectedIcon.id"
|
|
91
|
-
class="relative flex size-
|
|
134
|
+
class="relative flex size-28 items-center justify-center rounded-2xl border border-zinc-200 text-zinc-700 shadow-xs"
|
|
92
135
|
>
|
|
93
136
|
<button
|
|
94
137
|
type="button"
|
|
95
138
|
data-slot="icon-picker-clear"
|
|
96
|
-
class="absolute right-
|
|
139
|
+
class="absolute right-2 top-2 flex size-7 items-center justify-center rounded-full border border-zinc-200 bg-white text-zinc-500 shadow-xs transition-colors hover:border-red-200 hover:bg-red-50 hover:text-red-600 disabled:pointer-events-none disabled:opacity-60"
|
|
97
140
|
:disabled="props.disabled"
|
|
98
141
|
@click="clearIcon"
|
|
99
142
|
>
|
|
100
|
-
<Icon
|
|
143
|
+
<Icon
|
|
144
|
+
icon="fluent:dismiss-20-regular"
|
|
145
|
+
class="size-4"
|
|
146
|
+
/>
|
|
101
147
|
</button>
|
|
102
148
|
<Icon
|
|
103
149
|
:icon="selectedIcon.icon"
|
|
104
|
-
class="size-
|
|
150
|
+
class="size-14 shrink-0"
|
|
105
151
|
/>
|
|
106
152
|
</div>
|
|
107
|
-
|
|
108
153
|
<p
|
|
109
154
|
data-slot="icon-picker-name"
|
|
110
|
-
class="font-mono text-sm text-zinc-500"
|
|
155
|
+
class="max-w-full break-all font-mono text-sm text-zinc-500"
|
|
111
156
|
>
|
|
112
157
|
{{ selectedIcon.id }}
|
|
113
158
|
</p>
|
|
@@ -144,9 +189,10 @@ function handleSearchUpdate(value) {
|
|
|
144
189
|
<div
|
|
145
190
|
v-for="row in rowVirtualizer.getVirtualItems()"
|
|
146
191
|
:key="String(row.key)"
|
|
147
|
-
class="absolute left-0 top-0 grid w-full
|
|
192
|
+
class="absolute left-0 top-0 grid w-full gap-2"
|
|
148
193
|
:style="{
|
|
149
194
|
height: `${row.size}px`,
|
|
195
|
+
gridTemplateColumns: `repeat(${iconColumns}, minmax(0, 1fr))`,
|
|
150
196
|
transform: `translateY(${row.start}px)`
|
|
151
197
|
}"
|
|
152
198
|
>
|
|
@@ -156,7 +202,7 @@ function handleSearchUpdate(value) {
|
|
|
156
202
|
type="button"
|
|
157
203
|
data-slot="icon-picker-item"
|
|
158
204
|
:data-icon-id="item.id"
|
|
159
|
-
class="flex
|
|
205
|
+
class="mx-auto flex size-10 items-center justify-center rounded-md text-zinc-600 transition-colors hover:bg-zinc-100 hover:text-zinc-800 disabled:pointer-events-none disabled:opacity-60"
|
|
160
206
|
:disabled="props.disabled"
|
|
161
207
|
@click="selectIcon(item.id)"
|
|
162
208
|
>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type MenuTabsConfigInput } from './schema.js';
|
|
3
|
+
export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from './schema.js';
|
|
4
|
+
export type { MenuTabsConfig, MenuTabsConfigInput, MenuTabsItem } from './schema.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config: Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
menus: readonly Readonly<{
|
|
13
|
+
id: string;
|
|
14
|
+
title: import("../../../utils/coders.js").LocaleValue;
|
|
15
|
+
to: string;
|
|
16
|
+
}>[];
|
|
17
|
+
}>) => any;
|
|
18
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
19
|
+
config: Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
20
|
+
context?: Record<string, unknown>;
|
|
21
|
+
}> & Readonly<{
|
|
22
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
23
|
+
menus: readonly Readonly<{
|
|
24
|
+
id: string;
|
|
25
|
+
title: import("../../../utils/coders.js").LocaleValue;
|
|
26
|
+
to: string;
|
|
27
|
+
}>[];
|
|
28
|
+
}>) => any) | undefined;
|
|
29
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useNuxtApp, useRoute, useRouter } from "#app";
|
|
3
|
+
import { useCheating } from "#imports";
|
|
4
|
+
import { Icon } from "@iconify/vue";
|
|
5
|
+
import { computedAsync } from "@vueuse/core";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue";
|
|
8
|
+
import { useI18n } from "vue-i18n";
|
|
9
|
+
import { getLocalizedText } from "../../../utils/coders";
|
|
10
|
+
import { Button } from "../button";
|
|
11
|
+
import MenuTabsConfiguratorDialog from "../menu-tabs-configurator/MenuTabsConfiguratorDialog.vue";
|
|
12
|
+
import { Skeleton } from "../skeleton";
|
|
13
|
+
import { MenuTabsConfigC, normalizeMenuTabsConfigInput } from "./schema";
|
|
14
|
+
defineOptions({
|
|
15
|
+
inheritAttrs: false
|
|
16
|
+
});
|
|
17
|
+
const defaultConfig = {
|
|
18
|
+
menus: []
|
|
19
|
+
};
|
|
20
|
+
const props = defineProps({
|
|
21
|
+
config: { type: null, required: true },
|
|
22
|
+
context: { type: Object, required: false }
|
|
23
|
+
});
|
|
24
|
+
const emit = defineEmits(["update:config"]);
|
|
25
|
+
const { $dsl } = useNuxtApp();
|
|
26
|
+
const route = useRoute();
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
const { locale, t } = useI18n();
|
|
29
|
+
const isCheating = useCheating();
|
|
30
|
+
const resolvedConfig = computedAsync(
|
|
31
|
+
async () => MenuTabsConfigC.parse(normalizeMenuTabsConfigInput(await props.config.pipe(Effect.runPromise)))
|
|
32
|
+
);
|
|
33
|
+
const displayConfig = ref(defaultConfig);
|
|
34
|
+
const isConfiguratorOpen = ref(false);
|
|
35
|
+
const listRef = ref(null);
|
|
36
|
+
const buttonElements = /* @__PURE__ */ new Map();
|
|
37
|
+
const indicatorMetrics = ref();
|
|
38
|
+
let resizeObserver;
|
|
39
|
+
let observedElements = /* @__PURE__ */ new Set();
|
|
40
|
+
let frameId = 0;
|
|
41
|
+
function cleanPath(path) {
|
|
42
|
+
const normalizedPath = path.trim().replace(/\/+$/g, "");
|
|
43
|
+
return normalizedPath.length > 0 ? normalizedPath : "/";
|
|
44
|
+
}
|
|
45
|
+
function getEvaluationContext() {
|
|
46
|
+
return {
|
|
47
|
+
...props.context ?? {},
|
|
48
|
+
context: props.context ?? {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const resolvedMenus = computed(
|
|
52
|
+
() => displayConfig.value.menus.map((menu) => {
|
|
53
|
+
let path;
|
|
54
|
+
try {
|
|
55
|
+
const result = $dsl.evaluate`${menu.to}`(getEvaluationContext());
|
|
56
|
+
if (typeof result === "string" && result.trim().length > 0) {
|
|
57
|
+
path = cleanPath(result);
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
path = void 0;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
id: menu.id,
|
|
64
|
+
path,
|
|
65
|
+
title: getLocalizedText(menu.title, locale.value) ?? t("untitled-menu")
|
|
66
|
+
};
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
const activeMenuId = computed(() => {
|
|
70
|
+
const currentPath = cleanPath(route.path);
|
|
71
|
+
return resolvedMenus.value.find((menu) => menu.path === currentPath)?.id;
|
|
72
|
+
});
|
|
73
|
+
const indicatorStyle = computed(() => {
|
|
74
|
+
if (!indicatorMetrics.value) {
|
|
75
|
+
return {
|
|
76
|
+
opacity: 0,
|
|
77
|
+
transform: "translateX(0px)",
|
|
78
|
+
width: "0px"
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
opacity: 1,
|
|
83
|
+
transform: `translateX(${indicatorMetrics.value.left}px)`,
|
|
84
|
+
width: `${indicatorMetrics.value.width}px`
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
function setMenuButtonRef(id, value) {
|
|
88
|
+
if (value instanceof HTMLButtonElement) {
|
|
89
|
+
if (buttonElements.get(id) === value) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
buttonElements.set(id, value);
|
|
93
|
+
} else {
|
|
94
|
+
if (!buttonElements.has(id)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
buttonElements.delete(id);
|
|
98
|
+
}
|
|
99
|
+
syncObservedElements();
|
|
100
|
+
void scheduleIndicator();
|
|
101
|
+
}
|
|
102
|
+
function updateIndicator() {
|
|
103
|
+
const activeId = activeMenuId.value;
|
|
104
|
+
const activeButton = activeId ? buttonElements.get(activeId) : void 0;
|
|
105
|
+
if (!activeButton) {
|
|
106
|
+
indicatorMetrics.value = void 0;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
indicatorMetrics.value = {
|
|
110
|
+
left: activeButton.offsetLeft,
|
|
111
|
+
width: activeButton.offsetWidth
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function scheduleIndicator() {
|
|
115
|
+
if (typeof window !== "undefined") {
|
|
116
|
+
cancelAnimationFrame(frameId);
|
|
117
|
+
frameId = window.requestAnimationFrame(() => {
|
|
118
|
+
updateIndicator();
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
await nextTick();
|
|
123
|
+
updateIndicator();
|
|
124
|
+
}
|
|
125
|
+
function syncObservedElements() {
|
|
126
|
+
if (!resizeObserver) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const nextObservedElements = /* @__PURE__ */ new Set();
|
|
130
|
+
if (listRef.value) {
|
|
131
|
+
nextObservedElements.add(listRef.value);
|
|
132
|
+
}
|
|
133
|
+
for (const button of buttonElements.values()) {
|
|
134
|
+
nextObservedElements.add(button);
|
|
135
|
+
}
|
|
136
|
+
for (const element of observedElements) {
|
|
137
|
+
if (!nextObservedElements.has(element)) {
|
|
138
|
+
resizeObserver.unobserve(element);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const element of nextObservedElements) {
|
|
142
|
+
if (!observedElements.has(element)) {
|
|
143
|
+
resizeObserver.observe(element);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
observedElements = nextObservedElements;
|
|
147
|
+
}
|
|
148
|
+
function handleConfiguratorConfirm(nextConfig) {
|
|
149
|
+
displayConfig.value = nextConfig;
|
|
150
|
+
emit("update:config", nextConfig);
|
|
151
|
+
}
|
|
152
|
+
function handleMenuClick(menu) {
|
|
153
|
+
if (!menu.path) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
void router.replace(menu.path);
|
|
157
|
+
}
|
|
158
|
+
watch(resolvedConfig, (value) => {
|
|
159
|
+
if (!value) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
displayConfig.value = value;
|
|
163
|
+
}, { immediate: true });
|
|
164
|
+
watch(() => `${route.path}|${resolvedMenus.value.map((menu) => `${menu.id}:${menu.path ?? ""}`).join("|")}`, () => {
|
|
165
|
+
void scheduleIndicator();
|
|
166
|
+
}, { immediate: true });
|
|
167
|
+
onMounted(() => {
|
|
168
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
169
|
+
resizeObserver = new ResizeObserver(() => {
|
|
170
|
+
void scheduleIndicator();
|
|
171
|
+
});
|
|
172
|
+
syncObservedElements();
|
|
173
|
+
}
|
|
174
|
+
void scheduleIndicator();
|
|
175
|
+
});
|
|
176
|
+
onUnmounted(() => {
|
|
177
|
+
cancelAnimationFrame(frameId);
|
|
178
|
+
resizeObserver?.disconnect();
|
|
179
|
+
observedElements = /* @__PURE__ */ new Set();
|
|
180
|
+
});
|
|
181
|
+
</script>
|
|
182
|
+
|
|
183
|
+
<script>
|
|
184
|
+
export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from "./schema";
|
|
185
|
+
</script>
|
|
186
|
+
|
|
187
|
+
<template>
|
|
188
|
+
<div
|
|
189
|
+
v-bind="$attrs"
|
|
190
|
+
data-slot="menu-tabs-root"
|
|
191
|
+
class="relative"
|
|
192
|
+
>
|
|
193
|
+
<Button
|
|
194
|
+
v-if="isCheating"
|
|
195
|
+
data-slot="menu-tabs-configurator-trigger"
|
|
196
|
+
variant="ghost"
|
|
197
|
+
size="sm"
|
|
198
|
+
type="button"
|
|
199
|
+
class="absolute right-2 top-2 z-20 bg-white/90 shadow-xs backdrop-blur-sm hover:bg-white"
|
|
200
|
+
:aria-label="t('menu-tabs-open-configurator')"
|
|
201
|
+
:title="t('menu-tabs-open-configurator')"
|
|
202
|
+
@click="isConfiguratorOpen = true"
|
|
203
|
+
>
|
|
204
|
+
<Icon icon="fluent:settings-20-regular" />
|
|
205
|
+
</Button>
|
|
206
|
+
|
|
207
|
+
<MenuTabsConfiguratorDialog
|
|
208
|
+
v-if="resolvedConfig !== void 0"
|
|
209
|
+
v-model:open="isConfiguratorOpen"
|
|
210
|
+
:config="displayConfig"
|
|
211
|
+
@confirm="handleConfiguratorConfirm"
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
<Skeleton
|
|
215
|
+
v-if="resolvedConfig === void 0"
|
|
216
|
+
data-slot="menu-tabs-skeleton"
|
|
217
|
+
class="absolute inset-0 z-10 h-full w-full"
|
|
218
|
+
/>
|
|
219
|
+
|
|
220
|
+
<div
|
|
221
|
+
data-slot="menu-tabs-container"
|
|
222
|
+
class="overflow-x-auto"
|
|
223
|
+
>
|
|
224
|
+
<div
|
|
225
|
+
ref="listRef"
|
|
226
|
+
data-slot="menu-tabs-list"
|
|
227
|
+
class="relative flex min-w-full w-max items-stretch gap-6 border-b-2 border-zinc-200 bg-white"
|
|
228
|
+
>
|
|
229
|
+
<button
|
|
230
|
+
v-for="menu in resolvedMenus"
|
|
231
|
+
:ref="(value) => setMenuButtonRef(menu.id, value)"
|
|
232
|
+
:key="menu.id"
|
|
233
|
+
type="button"
|
|
234
|
+
data-slot="menu-tabs-item"
|
|
235
|
+
:data-menu-id="menu.id"
|
|
236
|
+
:data-active="activeMenuId === menu.id ? 'true' : void 0"
|
|
237
|
+
:data-navigable="menu.path ? 'true' : 'false'"
|
|
238
|
+
:aria-current="activeMenuId === menu.id ? 'page' : void 0"
|
|
239
|
+
:aria-disabled="menu.path ? void 0 : 'true'"
|
|
240
|
+
:class="[
|
|
241
|
+
'text-sm relative shrink-0 border-b-2 border-transparent py-2 font-semibold transition-colors duration-180',
|
|
242
|
+
activeMenuId === menu.id ? 'text-(--primary)' : 'text-zinc-700',
|
|
243
|
+
menu.path ? 'cursor-pointer hover:text-(--primary)' : 'cursor-default opacity-60'
|
|
244
|
+
]"
|
|
245
|
+
@click="handleMenuClick(menu)"
|
|
246
|
+
>
|
|
247
|
+
{{ menu.title }}
|
|
248
|
+
</button>
|
|
249
|
+
|
|
250
|
+
<div
|
|
251
|
+
data-slot="menu-tabs-indicator"
|
|
252
|
+
class="pointer-events-none absolute -bottom-0.5 left-0 h-0.5 rounded bg-(--primary) transition-[transform,width,opacity] duration-200 ease-out"
|
|
253
|
+
:style="indicatorStyle"
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</template>
|
|
259
|
+
|
|
260
|
+
<i18n lang="json">
|
|
261
|
+
{
|
|
262
|
+
"zh": {
|
|
263
|
+
"menu-tabs-open-configurator": "打开菜单标签配置",
|
|
264
|
+
"untitled-menu": "未命名菜单"
|
|
265
|
+
},
|
|
266
|
+
"ja": {
|
|
267
|
+
"menu-tabs-open-configurator": "メニュータブ設定を開く",
|
|
268
|
+
"untitled-menu": "名称未設定メニュー"
|
|
269
|
+
},
|
|
270
|
+
"en": {
|
|
271
|
+
"menu-tabs-open-configurator": "Open menu tabs configurator",
|
|
272
|
+
"untitled-menu": "Untitled menu"
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
</i18n>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { type MenuTabsConfigInput } from './schema.js';
|
|
3
|
+
export { MenuTabsConfigC, MenuTabsConfigInputC, MenuTabsItemC, MenuTabsItemToC } from './schema.js';
|
|
4
|
+
export type { MenuTabsConfig, MenuTabsConfigInput, MenuTabsItem } from './schema.js';
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<{
|
|
8
|
+
config: Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
9
|
+
context?: Record<string, unknown>;
|
|
10
|
+
}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:config": (args_0: Readonly<{
|
|
12
|
+
menus: readonly Readonly<{
|
|
13
|
+
id: string;
|
|
14
|
+
title: import("../../../utils/coders.js").LocaleValue;
|
|
15
|
+
to: string;
|
|
16
|
+
}>[];
|
|
17
|
+
}>) => any;
|
|
18
|
+
}, string, import("vue").PublicProps, Readonly<{
|
|
19
|
+
config: Effect.Effect<MenuTabsConfigInput | undefined>;
|
|
20
|
+
context?: Record<string, unknown>;
|
|
21
|
+
}> & Readonly<{
|
|
22
|
+
"onUpdate:config"?: ((args_0: Readonly<{
|
|
23
|
+
menus: readonly Readonly<{
|
|
24
|
+
id: string;
|
|
25
|
+
title: import("../../../utils/coders.js").LocaleValue;
|
|
26
|
+
to: string;
|
|
27
|
+
}>[];
|
|
28
|
+
}>) => any) | undefined;
|
|
29
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import type { LocaleValue } from '../../../utils/coders.js';
|
|
3
|
+
export declare const MenuTabsItemToC: z.ZodString;
|
|
4
|
+
export declare const MenuTabsItemC: z.ZodReadonly<z.ZodObject<{
|
|
5
|
+
id: z.ZodUUID;
|
|
6
|
+
title: z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
7
|
+
locale: z.ZodEnum<{
|
|
8
|
+
zh: "zh";
|
|
9
|
+
ja: "ja";
|
|
10
|
+
en: "en";
|
|
11
|
+
ko: "ko";
|
|
12
|
+
}>;
|
|
13
|
+
message: z.ZodString;
|
|
14
|
+
}, z.core.$strip>>>;
|
|
15
|
+
to: z.ZodString;
|
|
16
|
+
}, z.core.$strict>>;
|
|
17
|
+
export declare const MenuTabsConfigC: z.ZodReadonly<z.ZodObject<{
|
|
18
|
+
menus: z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
|
|
19
|
+
id: z.ZodUUID;
|
|
20
|
+
title: z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
21
|
+
locale: z.ZodEnum<{
|
|
22
|
+
zh: "zh";
|
|
23
|
+
ja: "ja";
|
|
24
|
+
en: "en";
|
|
25
|
+
ko: "ko";
|
|
26
|
+
}>;
|
|
27
|
+
message: z.ZodString;
|
|
28
|
+
}, z.core.$strip>>>;
|
|
29
|
+
to: z.ZodString;
|
|
30
|
+
}, z.core.$strict>>>>;
|
|
31
|
+
}, z.core.$strict>>;
|
|
32
|
+
export declare const MenuTabsConfigInputC: z.ZodReadonly<z.ZodObject<{
|
|
33
|
+
menus: z.ZodReadonly<z.ZodArray<z.ZodReadonly<z.ZodObject<{
|
|
34
|
+
id: z.ZodUUID;
|
|
35
|
+
title: z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
36
|
+
locale: z.ZodEnum<{
|
|
37
|
+
zh: "zh";
|
|
38
|
+
ja: "ja";
|
|
39
|
+
en: "en";
|
|
40
|
+
ko: "ko";
|
|
41
|
+
}>;
|
|
42
|
+
message: z.ZodString;
|
|
43
|
+
}, z.core.$strip>>>;
|
|
44
|
+
to: z.ZodString;
|
|
45
|
+
}, z.core.$strict>>>>;
|
|
46
|
+
}, z.core.$strict>>;
|
|
47
|
+
export type MenuTabsItem = Readonly<{
|
|
48
|
+
id: string;
|
|
49
|
+
title: LocaleValue;
|
|
50
|
+
to: string;
|
|
51
|
+
}>;
|
|
52
|
+
export type MenuTabsConfig = Readonly<{
|
|
53
|
+
menus: ReadonlyArray<MenuTabsItem>;
|
|
54
|
+
}>;
|
|
55
|
+
export type MenuTabsConfigInput = Readonly<{
|
|
56
|
+
menus?: ReadonlyArray<MenuTabsItem>;
|
|
57
|
+
}>;
|
|
58
|
+
export declare function normalizeMenuTabsConfigInput(value: unknown): unknown;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { expressionC, localeC } from "../../../utils/coders.js";
|
|
3
|
+
const menuTabsItemIdC = z.uuid().describe("\u83DC\u5355\u9879\u552F\u4E00\u6807\u8BC6\uFF0C\u5FC5\u987B\u662F UUID");
|
|
4
|
+
export const MenuTabsItemToC = expressionC("string", { context: "dyn" }).describe("\u8FD4\u56DE\u76EE\u6807\u8DEF\u5F84\u7684 CEL \u8868\u8FBE\u5F0F\u3002\u53EF\u7528\u53D8\u91CF\uFF1Acontext\u3002\u5FC5\u987B\u8FD4\u56DE string\u3002");
|
|
5
|
+
export const MenuTabsItemC = z.strictObject({
|
|
6
|
+
id: menuTabsItemIdC,
|
|
7
|
+
title: localeC.describe("\u83DC\u5355\u540D\u79F0\u7684\u672C\u5730\u5316\u663E\u793A\u6587\u672C"),
|
|
8
|
+
to: MenuTabsItemToC
|
|
9
|
+
}).readonly();
|
|
10
|
+
export const MenuTabsConfigC = z.strictObject({
|
|
11
|
+
menus: z.array(MenuTabsItemC).readonly().describe("\u9876\u90E8\u83DC\u5355\u5217\u8868")
|
|
12
|
+
}).readonly();
|
|
13
|
+
export const MenuTabsConfigInputC = MenuTabsConfigC;
|
|
14
|
+
export function normalizeMenuTabsConfigInput(value) {
|
|
15
|
+
if (typeof value !== "object" || value === null) {
|
|
16
|
+
return {
|
|
17
|
+
menus: []
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const menus = Reflect.get(value, "menus");
|
|
21
|
+
return {
|
|
22
|
+
menus: Array.isArray(menus) ? menus : []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LocaleValue } from '../../../utils/coders.js';
|
|
2
|
+
import { type MenuTabsConfig } from '../menu-tabs/schema.js';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
config: MenuTabsConfig;
|
|
5
|
+
};
|
|
6
|
+
type __VLS_ModelProps = {
|
|
7
|
+
'open'?: boolean;
|
|
8
|
+
};
|
|
9
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:open": (value: boolean) => any;
|
|
12
|
+
confirm: (args_0: Readonly<{
|
|
13
|
+
menus: readonly Readonly<{
|
|
14
|
+
id: string;
|
|
15
|
+
title: LocaleValue;
|
|
16
|
+
to: string;
|
|
17
|
+
}>[];
|
|
18
|
+
}>) => any;
|
|
19
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
20
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
21
|
+
onConfirm?: ((args_0: Readonly<{
|
|
22
|
+
menus: readonly Readonly<{
|
|
23
|
+
id: string;
|
|
24
|
+
title: LocaleValue;
|
|
25
|
+
to: string;
|
|
26
|
+
}>[];
|
|
27
|
+
}>) => any) | undefined;
|
|
28
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
|
+
declare const _default: typeof __VLS_export;
|
|
30
|
+
export default _default;
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useSortable } from "@vueuse/integrations/useSortable";
|
|
3
|
+
import { Icon } from "@iconify/vue";
|
|
4
|
+
import { computed, nextTick, ref, watch } from "vue";
|
|
5
|
+
import { useI18n } from "vue-i18n";
|
|
6
|
+
import { cn } from "../../../utils/cn";
|
|
7
|
+
import { Button } from "../button";
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogFooter,
|
|
13
|
+
DialogHeader,
|
|
14
|
+
DialogTitle
|
|
15
|
+
} from "../dialog";
|
|
16
|
+
import { ExpressionEditor } from "../expression-editor";
|
|
17
|
+
import { Input } from "../input";
|
|
18
|
+
import Locale from "../locale/Locale.vue";
|
|
19
|
+
import { MenuTabsConfigC, MenuTabsItemToC } from "../menu-tabs/schema";
|
|
20
|
+
defineOptions({
|
|
21
|
+
inheritAttrs: false
|
|
22
|
+
});
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
config: { type: Object, required: true }
|
|
25
|
+
});
|
|
26
|
+
const emit = defineEmits(["confirm"]);
|
|
27
|
+
const open = defineModel("open", { type: Boolean, ...{
|
|
28
|
+
default: false
|
|
29
|
+
} });
|
|
30
|
+
const { t } = useI18n();
|
|
31
|
+
const search = ref("");
|
|
32
|
+
const selectedItemId = ref("");
|
|
33
|
+
const draftMenus = ref([]);
|
|
34
|
+
const sortableListRef = ref(null);
|
|
35
|
+
const sortableItemIds = ref([]);
|
|
36
|
+
const toEditor = ref(null);
|
|
37
|
+
function createDraftId() {
|
|
38
|
+
return crypto.randomUUID();
|
|
39
|
+
}
|
|
40
|
+
function cloneLocaleValue(value) {
|
|
41
|
+
return value.map((item) => ({
|
|
42
|
+
locale: item.locale,
|
|
43
|
+
message: item.message
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
function cloneMenu(menu) {
|
|
47
|
+
return {
|
|
48
|
+
id: menu.id,
|
|
49
|
+
title: cloneLocaleValue(menu.title),
|
|
50
|
+
to: menu.to
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function createDraftMenu(menu) {
|
|
54
|
+
return {
|
|
55
|
+
draftId: createDraftId(),
|
|
56
|
+
menu: cloneMenu(menu)
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function cloneMenus(menus) {
|
|
60
|
+
return menus.map(createDraftMenu);
|
|
61
|
+
}
|
|
62
|
+
function createDefaultMenu() {
|
|
63
|
+
return {
|
|
64
|
+
id: crypto.randomUUID(),
|
|
65
|
+
title: [{ locale: "zh", message: "" }],
|
|
66
|
+
to: '"/"'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function getMenuChineseTitle(menu) {
|
|
70
|
+
const zhTitle = menu.title.find((item) => item.locale === "zh");
|
|
71
|
+
if (!zhTitle) {
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
const message = zhTitle.message.trim();
|
|
75
|
+
return message.length > 0 ? message : void 0;
|
|
76
|
+
}
|
|
77
|
+
function getMenuListLabel(menu) {
|
|
78
|
+
return getMenuChineseTitle(menu) ?? t("menu-tabs-configurator-untitled");
|
|
79
|
+
}
|
|
80
|
+
function applyDraftConfig(config) {
|
|
81
|
+
search.value = "";
|
|
82
|
+
draftMenus.value = cloneMenus(config.menus);
|
|
83
|
+
selectedItemId.value = draftMenus.value[0]?.draftId ?? "";
|
|
84
|
+
}
|
|
85
|
+
function resetDraftConfig() {
|
|
86
|
+
applyDraftConfig(props.config);
|
|
87
|
+
}
|
|
88
|
+
const normalizedSearch = computed(() => search.value.trim().toLocaleLowerCase());
|
|
89
|
+
const menuItems = computed(() => draftMenus.value.map((item) => ({
|
|
90
|
+
itemId: item.draftId,
|
|
91
|
+
label: getMenuListLabel(item.menu),
|
|
92
|
+
id: item.menu.id
|
|
93
|
+
})));
|
|
94
|
+
const filteredMenuItems = computed(() => {
|
|
95
|
+
if (!normalizedSearch.value) {
|
|
96
|
+
return menuItems.value;
|
|
97
|
+
}
|
|
98
|
+
return menuItems.value.filter((item) => {
|
|
99
|
+
const haystack = [item.label, item.id].join(" ").toLocaleLowerCase();
|
|
100
|
+
return haystack.includes(normalizedSearch.value);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
const selectedMenu = computed(() => draftMenus.value.find((item) => item.draftId === selectedItemId.value));
|
|
104
|
+
function syncSortableItemIds() {
|
|
105
|
+
sortableItemIds.value = draftMenus.value.map((item) => item.draftId);
|
|
106
|
+
}
|
|
107
|
+
function moveDraftMenus(menus, oldIndex, newIndex) {
|
|
108
|
+
if (oldIndex < 0 || newIndex < 0 || oldIndex >= menus.length || newIndex >= menus.length) {
|
|
109
|
+
return menus.slice();
|
|
110
|
+
}
|
|
111
|
+
const nextMenus = menus.slice();
|
|
112
|
+
const movedMenu = nextMenus[oldIndex];
|
|
113
|
+
if (!movedMenu) {
|
|
114
|
+
return menus.slice();
|
|
115
|
+
}
|
|
116
|
+
nextMenus.splice(oldIndex, 1);
|
|
117
|
+
nextMenus.splice(newIndex, 0, movedMenu);
|
|
118
|
+
return nextMenus;
|
|
119
|
+
}
|
|
120
|
+
function handleSortableUpdate(event) {
|
|
121
|
+
const oldIndex = event.oldIndex;
|
|
122
|
+
const newIndex = event.newIndex;
|
|
123
|
+
if (oldIndex === void 0 || newIndex === void 0 || oldIndex === newIndex) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
draftMenus.value = moveDraftMenus(draftMenus.value, oldIndex, newIndex);
|
|
127
|
+
}
|
|
128
|
+
const sortable = useSortable(sortableListRef, sortableItemIds);
|
|
129
|
+
function configureSortable() {
|
|
130
|
+
sortable.option("animation", 150);
|
|
131
|
+
sortable.option("handle", '[data-slot="menu-tabs-configurator-drag-handle"]');
|
|
132
|
+
sortable.option("onUpdate", handleSortableUpdate);
|
|
133
|
+
}
|
|
134
|
+
async function refreshSortable() {
|
|
135
|
+
sortable.stop();
|
|
136
|
+
if (!open.value || draftMenus.value.length === 0 || normalizedSearch.value.length > 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await nextTick();
|
|
140
|
+
sortable.start();
|
|
141
|
+
configureSortable();
|
|
142
|
+
}
|
|
143
|
+
function selectItem(itemId) {
|
|
144
|
+
selectedItemId.value = itemId;
|
|
145
|
+
}
|
|
146
|
+
function updateMenu(draftId, update) {
|
|
147
|
+
draftMenus.value = draftMenus.value.map((item) => item.draftId === draftId ? {
|
|
148
|
+
draftId: item.draftId,
|
|
149
|
+
menu: update(item.menu)
|
|
150
|
+
} : item);
|
|
151
|
+
}
|
|
152
|
+
function updateSelectedTitle(value) {
|
|
153
|
+
if (!selectedMenu.value) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
updateMenu(selectedMenu.value.draftId, (menu) => ({
|
|
157
|
+
id: menu.id,
|
|
158
|
+
title: cloneLocaleValue(value),
|
|
159
|
+
to: menu.to
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
function updateSelectedTo(value) {
|
|
163
|
+
if (!selectedMenu.value) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
updateMenu(selectedMenu.value.draftId, (menu) => ({
|
|
167
|
+
id: menu.id,
|
|
168
|
+
title: cloneLocaleValue(menu.title),
|
|
169
|
+
to: value ?? ""
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
async function addMenu() {
|
|
173
|
+
const nextMenu = {
|
|
174
|
+
draftId: createDraftId(),
|
|
175
|
+
menu: createDefaultMenu()
|
|
176
|
+
};
|
|
177
|
+
draftMenus.value = [...draftMenus.value, nextMenu];
|
|
178
|
+
selectedItemId.value = nextMenu.draftId;
|
|
179
|
+
await refreshSortable();
|
|
180
|
+
}
|
|
181
|
+
function deleteMenu(draftId) {
|
|
182
|
+
const removedIndex = draftMenus.value.findIndex((item) => item.draftId === draftId);
|
|
183
|
+
draftMenus.value = draftMenus.value.filter((item) => item.draftId !== draftId);
|
|
184
|
+
if (selectedItemId.value !== draftId) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const fallbackMenu = draftMenus.value[removedIndex] ?? draftMenus.value[removedIndex - 1];
|
|
188
|
+
selectedItemId.value = fallbackMenu?.draftId ?? "";
|
|
189
|
+
}
|
|
190
|
+
function buildDraftConfig() {
|
|
191
|
+
const result = MenuTabsConfigC.safeParse({
|
|
192
|
+
menus: draftMenus.value.map((item) => item.menu)
|
|
193
|
+
});
|
|
194
|
+
if (!result.success) {
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
return result.data;
|
|
198
|
+
}
|
|
199
|
+
function handleConfirm() {
|
|
200
|
+
if (selectedMenu.value && !(toEditor.value?.validate() ?? true)) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const nextConfig = buildDraftConfig();
|
|
204
|
+
if (!nextConfig) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
emit("confirm", nextConfig);
|
|
208
|
+
open.value = false;
|
|
209
|
+
}
|
|
210
|
+
watch(() => props.config, () => {
|
|
211
|
+
if (!open.value) {
|
|
212
|
+
resetDraftConfig();
|
|
213
|
+
}
|
|
214
|
+
}, { immediate: true });
|
|
215
|
+
watch(draftMenus, () => {
|
|
216
|
+
syncSortableItemIds();
|
|
217
|
+
}, { immediate: true });
|
|
218
|
+
watch(open, async (value) => {
|
|
219
|
+
if (value) {
|
|
220
|
+
resetDraftConfig();
|
|
221
|
+
await refreshSortable();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
sortable.stop();
|
|
225
|
+
}, { immediate: true });
|
|
226
|
+
watch(menuItems, async (items) => {
|
|
227
|
+
if (!items.some((item) => item.itemId === selectedItemId.value)) {
|
|
228
|
+
selectedItemId.value = items[0]?.itemId ?? "";
|
|
229
|
+
}
|
|
230
|
+
if (open.value) {
|
|
231
|
+
await refreshSortable();
|
|
232
|
+
}
|
|
233
|
+
}, { immediate: true });
|
|
234
|
+
watch(filteredMenuItems, async (items) => {
|
|
235
|
+
if (!normalizedSearch.value) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (items.some((item) => item.itemId === selectedItemId.value)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
selectedItemId.value = items[0]?.itemId ?? "";
|
|
242
|
+
await refreshSortable();
|
|
243
|
+
}, { immediate: true });
|
|
244
|
+
watch(normalizedSearch, async () => {
|
|
245
|
+
if (!open.value) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
await refreshSortable();
|
|
249
|
+
});
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<template>
|
|
253
|
+
<Dialog
|
|
254
|
+
:open="open"
|
|
255
|
+
@update:open="open = $event"
|
|
256
|
+
>
|
|
257
|
+
<DialogContent
|
|
258
|
+
data-slot="menu-tabs-configurator-dialog"
|
|
259
|
+
class="flex h-[min(42rem,calc(100vh-4rem))] w-[calc(100%-2rem)] max-h-[calc(100vh-4rem)] max-w-[calc(100%-2rem)] flex-col overflow-hidden p-0 sm:w-[80vw] sm:max-w-[80vw]"
|
|
260
|
+
>
|
|
261
|
+
<DialogHeader class="gap-1 border-b border-zinc-200 px-6 py-5">
|
|
262
|
+
<DialogTitle class="text-xl font-semibold text-zinc-800">
|
|
263
|
+
{{ t("menu-tabs-configurator-title") }}
|
|
264
|
+
</DialogTitle>
|
|
265
|
+
<DialogDescription class="text-sm text-zinc-500">
|
|
266
|
+
{{ t("menu-tabs-configurator-description") }}
|
|
267
|
+
</DialogDescription>
|
|
268
|
+
</DialogHeader>
|
|
269
|
+
|
|
270
|
+
<div class="grid min-h-0 flex-1 grid-cols-[20rem_minmax(0,1fr)]">
|
|
271
|
+
<section class="flex min-h-0 flex-col border-r border-zinc-200 px-4 py-4">
|
|
272
|
+
<Input
|
|
273
|
+
v-model="search"
|
|
274
|
+
data-slot="menu-tabs-configurator-search"
|
|
275
|
+
:placeholder="t('menu-tabs-configurator-search-placeholder')"
|
|
276
|
+
/>
|
|
277
|
+
|
|
278
|
+
<Button
|
|
279
|
+
data-slot="menu-tabs-configurator-add"
|
|
280
|
+
type="button"
|
|
281
|
+
class="mt-3 w-full justify-center"
|
|
282
|
+
@click="void addMenu()"
|
|
283
|
+
>
|
|
284
|
+
<Icon icon="fluent:add-20-regular" />
|
|
285
|
+
{{ t("menu-tabs-configurator-add-menu") }}
|
|
286
|
+
</Button>
|
|
287
|
+
|
|
288
|
+
<div class="mt-4 flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
289
|
+
<div class="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto pr-1">
|
|
290
|
+
<div
|
|
291
|
+
v-if="filteredMenuItems.length > 0"
|
|
292
|
+
ref="sortableListRef"
|
|
293
|
+
data-slot="menu-tabs-configurator-list"
|
|
294
|
+
class="flex flex-col gap-1"
|
|
295
|
+
>
|
|
296
|
+
<div
|
|
297
|
+
v-for="item in filteredMenuItems"
|
|
298
|
+
:key="item.itemId"
|
|
299
|
+
data-slot="menu-tabs-configurator-item"
|
|
300
|
+
:data-item-id="item.itemId"
|
|
301
|
+
:data-menu-id="item.id"
|
|
302
|
+
:data-selected="selectedItemId === item.itemId ? 'true' : 'false'"
|
|
303
|
+
:class="cn(
|
|
304
|
+
'flex w-full items-center gap-2 rounded-md border p-1 transition-colors',
|
|
305
|
+
selectedItemId === item.itemId ? 'border-(--primary)/25 bg-[color-mix(in_srgb,var(--primary)_10%,white)]' : 'border-transparent hover:border-zinc-200 hover:bg-zinc-50'
|
|
306
|
+
)"
|
|
307
|
+
>
|
|
308
|
+
<button
|
|
309
|
+
type="button"
|
|
310
|
+
data-slot="menu-tabs-configurator-drag-handle"
|
|
311
|
+
class="flex size-8 shrink-0 cursor-grab items-center justify-center rounded-sm text-zinc-400 active:cursor-grabbing"
|
|
312
|
+
:aria-label="t('menu-tabs-configurator-drag-menu', { menu: item.label })"
|
|
313
|
+
@click.stop
|
|
314
|
+
>
|
|
315
|
+
<Icon icon="fluent:re-order-dots-vertical-20-regular" />
|
|
316
|
+
</button>
|
|
317
|
+
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
data-slot="menu-tabs-configurator-item-select"
|
|
321
|
+
class="min-w-0 flex-1 px-2 py-2 text-left"
|
|
322
|
+
@click="selectItem(item.itemId)"
|
|
323
|
+
>
|
|
324
|
+
<span class="block truncate text-sm font-medium text-zinc-800">
|
|
325
|
+
{{ item.label }}
|
|
326
|
+
</span>
|
|
327
|
+
</button>
|
|
328
|
+
|
|
329
|
+
<button
|
|
330
|
+
type="button"
|
|
331
|
+
data-slot="menu-tabs-configurator-delete"
|
|
332
|
+
class="flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-sm text-zinc-400 transition-colors hover:bg-red-50 hover:text-red-600"
|
|
333
|
+
:aria-label="t('menu-tabs-configurator-delete-menu', { menu: item.label })"
|
|
334
|
+
@click.stop="deleteMenu(item.itemId)"
|
|
335
|
+
>
|
|
336
|
+
<Icon icon="fluent:delete-20-regular" />
|
|
337
|
+
</button>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
<p
|
|
342
|
+
v-else-if="normalizedSearch"
|
|
343
|
+
data-slot="menu-tabs-configurator-empty"
|
|
344
|
+
class="px-1 pt-2 text-xs text-zinc-400"
|
|
345
|
+
>
|
|
346
|
+
{{ t("menu-tabs-configurator-no-matches") }}
|
|
347
|
+
</p>
|
|
348
|
+
|
|
349
|
+
<p
|
|
350
|
+
v-else
|
|
351
|
+
data-slot="menu-tabs-configurator-empty"
|
|
352
|
+
class="px-1 pt-2 text-xs text-zinc-400"
|
|
353
|
+
>
|
|
354
|
+
{{ t("menu-tabs-configurator-empty") }}
|
|
355
|
+
</p>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
</section>
|
|
359
|
+
|
|
360
|
+
<section class="flex min-h-0 min-w-0 flex-col overflow-y-auto px-6 py-6">
|
|
361
|
+
<div
|
|
362
|
+
v-if="selectedMenu"
|
|
363
|
+
class="flex min-h-0 flex-1 flex-col gap-5"
|
|
364
|
+
>
|
|
365
|
+
<div>
|
|
366
|
+
<h3 class="text-lg font-semibold text-zinc-800">
|
|
367
|
+
{{ t("menu-tabs-configurator-menu-detail") }}
|
|
368
|
+
</h3>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<div class="flex flex-col gap-5">
|
|
372
|
+
<Locale
|
|
373
|
+
data-slot="menu-tabs-configurator-title"
|
|
374
|
+
:model-value="selectedMenu.menu.title"
|
|
375
|
+
@update:model-value="updateSelectedTitle"
|
|
376
|
+
/>
|
|
377
|
+
|
|
378
|
+
<ExpressionEditor
|
|
379
|
+
ref="toEditor"
|
|
380
|
+
data-slot="menu-tabs-configurator-to"
|
|
381
|
+
:model-value="selectedMenu.menu.to"
|
|
382
|
+
:label="t('menu-tabs-configurator-to-label')"
|
|
383
|
+
:description="t('menu-tabs-configurator-to-description')"
|
|
384
|
+
:placeholder="t('menu-tabs-configurator-to-placeholder')"
|
|
385
|
+
:schema="MenuTabsItemToC"
|
|
386
|
+
@update:model-value="updateSelectedTo"
|
|
387
|
+
/>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div
|
|
392
|
+
v-else
|
|
393
|
+
data-slot="menu-tabs-configurator-detail-empty"
|
|
394
|
+
class="flex min-h-0 flex-1 items-center justify-center text-sm text-zinc-400"
|
|
395
|
+
>
|
|
396
|
+
{{ t("menu-tabs-configurator-select-menu") }}
|
|
397
|
+
</div>
|
|
398
|
+
</section>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
<DialogFooter class="border-t border-zinc-200 px-6 py-4">
|
|
402
|
+
<Button
|
|
403
|
+
data-slot="menu-tabs-configurator-cancel"
|
|
404
|
+
type="button"
|
|
405
|
+
variant="ghost"
|
|
406
|
+
@click="open = false"
|
|
407
|
+
>
|
|
408
|
+
{{ t("cancel") }}
|
|
409
|
+
</Button>
|
|
410
|
+
<Button
|
|
411
|
+
data-slot="menu-tabs-configurator-confirm"
|
|
412
|
+
type="button"
|
|
413
|
+
@click="handleConfirm"
|
|
414
|
+
>
|
|
415
|
+
{{ t("confirm") }}
|
|
416
|
+
</Button>
|
|
417
|
+
</DialogFooter>
|
|
418
|
+
</DialogContent>
|
|
419
|
+
</Dialog>
|
|
420
|
+
</template>
|
|
421
|
+
|
|
422
|
+
<i18n lang="json">
|
|
423
|
+
{
|
|
424
|
+
"zh": {
|
|
425
|
+
"menu-tabs-configurator-title": "菜单标签配置",
|
|
426
|
+
"menu-tabs-configurator-description": "配置顶部菜单标签,并使用拖拽调整顺序。",
|
|
427
|
+
"menu-tabs-configurator-menus": "菜单",
|
|
428
|
+
"menu-tabs-configurator-add-menu": "新增菜单",
|
|
429
|
+
"menu-tabs-configurator-search-placeholder": "搜索菜单名称、路径或 ID",
|
|
430
|
+
"menu-tabs-configurator-empty": "暂无菜单",
|
|
431
|
+
"menu-tabs-configurator-no-matches": "没有匹配的菜单",
|
|
432
|
+
"menu-tabs-configurator-select-menu": "请选择左侧菜单以编辑详情",
|
|
433
|
+
"menu-tabs-configurator-menu-detail": "菜单详情",
|
|
434
|
+
"menu-tabs-configurator-untitled": "未命名菜单",
|
|
435
|
+
"menu-tabs-configurator-drag-menu": "拖拽菜单 {menu}",
|
|
436
|
+
"menu-tabs-configurator-delete-menu": "删除菜单 {menu}",
|
|
437
|
+
"menu-tabs-configurator-to-label": "目标路径表达式",
|
|
438
|
+
"menu-tabs-configurator-to-description": "填写返回路径字符串的 CEL 表达式。",
|
|
439
|
+
"menu-tabs-configurator-to-placeholder": "\"/locale\"",
|
|
440
|
+
"cancel": "取消",
|
|
441
|
+
"confirm": "确认"
|
|
442
|
+
},
|
|
443
|
+
"ja": {
|
|
444
|
+
"menu-tabs-configurator-title": "メニュータブ設定",
|
|
445
|
+
"menu-tabs-configurator-description": "上部メニュータブを設定し、ドラッグで並び順を変更します。",
|
|
446
|
+
"menu-tabs-configurator-menus": "メニュー",
|
|
447
|
+
"menu-tabs-configurator-add-menu": "メニューを追加",
|
|
448
|
+
"menu-tabs-configurator-search-placeholder": "メニュー名、パス、ID を検索",
|
|
449
|
+
"menu-tabs-configurator-empty": "メニューがありません",
|
|
450
|
+
"menu-tabs-configurator-no-matches": "一致するメニューがありません",
|
|
451
|
+
"menu-tabs-configurator-select-menu": "左側のメニューを選択して詳細を編集してください",
|
|
452
|
+
"menu-tabs-configurator-menu-detail": "メニュー詳細",
|
|
453
|
+
"menu-tabs-configurator-untitled": "名称未設定メニュー",
|
|
454
|
+
"menu-tabs-configurator-drag-menu": "{menu} をドラッグ",
|
|
455
|
+
"menu-tabs-configurator-delete-menu": "{menu} を削除",
|
|
456
|
+
"menu-tabs-configurator-to-label": "遷移先パス式",
|
|
457
|
+
"menu-tabs-configurator-to-description": "パス文字列を返す CEL 式を入力してください。",
|
|
458
|
+
"menu-tabs-configurator-to-placeholder": "\"/locale\"",
|
|
459
|
+
"cancel": "キャンセル",
|
|
460
|
+
"confirm": "確認"
|
|
461
|
+
},
|
|
462
|
+
"en": {
|
|
463
|
+
"menu-tabs-configurator-title": "Menu tabs configurator",
|
|
464
|
+
"menu-tabs-configurator-description": "Configure the top menu tabs and drag to reorder them.",
|
|
465
|
+
"menu-tabs-configurator-menus": "Menus",
|
|
466
|
+
"menu-tabs-configurator-add-menu": "Add menu",
|
|
467
|
+
"menu-tabs-configurator-search-placeholder": "Search menu name, path, or ID",
|
|
468
|
+
"menu-tabs-configurator-empty": "No menus yet",
|
|
469
|
+
"menu-tabs-configurator-no-matches": "No matching menus",
|
|
470
|
+
"menu-tabs-configurator-select-menu": "Select a menu on the left to edit its details",
|
|
471
|
+
"menu-tabs-configurator-menu-detail": "Menu details",
|
|
472
|
+
"menu-tabs-configurator-untitled": "Untitled menu",
|
|
473
|
+
"menu-tabs-configurator-drag-menu": "Drag menu {menu}",
|
|
474
|
+
"menu-tabs-configurator-delete-menu": "Delete menu {menu}",
|
|
475
|
+
"menu-tabs-configurator-to-label": "Target path expression",
|
|
476
|
+
"menu-tabs-configurator-to-description": "Enter a CEL expression that returns a path string.",
|
|
477
|
+
"menu-tabs-configurator-to-placeholder": "\"/locale\"",
|
|
478
|
+
"cancel": "Cancel",
|
|
479
|
+
"confirm": "Confirm"
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
</i18n>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { LocaleValue } from '../../../utils/coders.js';
|
|
2
|
+
import { type MenuTabsConfig } from '../menu-tabs/schema.js';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
config: MenuTabsConfig;
|
|
5
|
+
};
|
|
6
|
+
type __VLS_ModelProps = {
|
|
7
|
+
'open'?: boolean;
|
|
8
|
+
};
|
|
9
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
|
+
"update:open": (value: boolean) => any;
|
|
12
|
+
confirm: (args_0: Readonly<{
|
|
13
|
+
menus: readonly Readonly<{
|
|
14
|
+
id: string;
|
|
15
|
+
title: LocaleValue;
|
|
16
|
+
to: string;
|
|
17
|
+
}>[];
|
|
18
|
+
}>) => any;
|
|
19
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
20
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
21
|
+
onConfirm?: ((args_0: Readonly<{
|
|
22
|
+
menus: readonly Readonly<{
|
|
23
|
+
id: string;
|
|
24
|
+
title: LocaleValue;
|
|
25
|
+
to: string;
|
|
26
|
+
}>[];
|
|
27
|
+
}>) => any) | undefined;
|
|
28
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
|
+
declare const _default: typeof __VLS_export;
|
|
30
|
+
export default _default;
|