@flyanra/vue-core 1.0.2 → 1.2.1
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/types/components/navigation/menu/fly-sidebar/FlySidebar.types.d.ts +14 -1
- package/dist/types/components/navigation/menu/fly-sidebar/FlySidebar.vue.d.ts +10 -4
- package/dist/types/composables/UseLocaleInjector.d.ts +7 -1
- package/dist/types/composables/index.d.ts +3 -1
- package/dist/types/composables/useSidebarActiveIndex.d.ts +66 -0
- package/package.json +3 -1
- package/src/components/basic/fly-button/FlyButton.types.ts +28 -28
- package/src/components/basic/fly-button/FlyButton.vue +178 -178
- package/src/components/basic/typography/FlyCollapseTitleRenderer.vue +46 -46
- package/src/components/feedback/tooltip/FlyInfoTooltip.vue +49 -49
- package/src/components/feedback/tooltip/FlyTooltip.element.scss +4 -4
- package/src/components/feedback/tooltip/FlyTooltip.vue +60 -60
- package/src/components/form/form.utils.ts +72 -72
- package/src/components/form/index.ts +6 -6
- package/src/components/form/input-renderers/boolean/FlyBoolean.vue +48 -48
- package/src/components/form/input-renderers/form-item-renderer/FlyFormItemRenderer.vue +48 -48
- package/src/components/form/input-renderers/form-item-renderer/index.ts +4 -4
- package/src/components/form/input-renderers/form-schema-renderer/FlyFormSchemaRenderer.types.ts +3 -3
- package/src/components/form/input-renderers/form-schema-renderer/FlyFormSchemaRenderer.utils.ts +221 -221
- package/src/components/form/input-renderers/form-schema-renderer/FlyFormSchemaRenderer.vue +75 -75
- package/src/components/form/input-renderers/form-schema-renderer/UseFormSchemaRenderer.ts +97 -97
- package/src/components/form/input-renderers/input-renderer/FlyInputRenderer.vue +49 -49
- package/src/components/form/input-renderers/input-renderer/index.ts +4 -4
- package/src/components/form/input-renderers/input-renderer.utils.ts +18 -18
- package/src/components/form/input-renderers/number/FlyNumber.vue +64 -64
- package/src/components/form/input-renderers/regional-tenant-form-renderer/FlyRegionalTenantFormRenderer.types.ts +15 -15
- package/src/components/form/input-renderers/regional-tenant-form-renderer/FlyRegionalTenantFormRenderer.vue +99 -99
- package/src/components/form/input-renderers/select/FlySelect.scss +1 -1
- package/src/components/form/input-renderers/select/FlySelect.utils.ts +37 -37
- package/src/components/form/input-renderers/text/FlyText.vue +70 -70
- package/src/components/form/input-renderers/value-unit/FlyValueUnit.vue +102 -102
- package/src/components/form/input-validators.ts +99 -99
- package/src/components/general/fly-icon-v2/FlyIconV2.types.ts +6 -6
- package/src/components/general/fly-icon-v2/FlyIconV2.vue +17 -17
- package/src/components/general/fly-icon-v2/index.ts +6 -6
- package/src/components/general/index.ts +5 -5
- package/src/components/index.ts +7 -7
- package/src/components/navigation/menu/fly-mini-sidebar/FlyMiniSidebar.types.ts +8 -8
- package/src/components/navigation/menu/fly-mini-sidebar/FlyMiniSidebarItem.vue +55 -55
- package/src/components/navigation/menu/fly-sidebar/FlySidebar.types.ts +49 -36
- package/src/components/navigation/menu/fly-sidebar/FlySidebar.vue +146 -151
- package/src/components/navigation/menu/fly-sidebar/FlySidebarNode.vue +47 -47
- package/src/composables/UseLocaleInjector.ts +37 -18
- package/src/composables/index.ts +4 -1
- package/src/composables/useSidebarActiveIndex.ts +103 -0
- package/src/styles/_scss-tokens.scss +71 -71
- package/src/styles/element/mixins/collapse.scss +61 -61
- package/src/styles/element/overrides/form.scss +121 -121
- package/src/styles/element/overrides/message-box.scss +2 -2
- package/src/styles/element/overrides/radio.scss +2 -2
- package/src/styles/element/overrides/select.scss +9 -9
- package/src/styles/index.scss +1 -1
- package/src/styles/tokens.scss +1 -0
- package/src/utils/index.ts +2 -2
- package/src/utils/pure-functions.ts +15 -15
- package/src/utils/validators.ts +81 -81
|
@@ -1,151 +1,146 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="fly-sidebar">
|
|
3
|
-
<el-scrollbar :height="height">
|
|
4
|
-
<el-menu
|
|
5
|
-
:default-active="defaultActive"
|
|
6
|
-
:collapse="isCollapsed"
|
|
7
|
-
:router="router"
|
|
8
|
-
popper-class="fly-sidebar-popper"
|
|
9
|
-
>
|
|
10
|
-
<FlySidebarNode v-for="node in menu" :key="node.index" :node="node" />
|
|
11
|
-
</el-menu>
|
|
12
|
-
</el-scrollbar>
|
|
13
|
-
|
|
14
|
-
<div class="fly_sidebar__collapse-trigger" role="button" @click="toggleCollapse">
|
|
15
|
-
<FlyIconV2 :icon="isCollapsed ? IconMdiChevronRight : IconMdiChevronLeft" />
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
</template>
|
|
19
|
-
|
|
20
|
-
<script lang="ts" setup>
|
|
21
|
-
import IconMdiChevronLeft from '~icons/mdi/chevron-left'
|
|
22
|
-
import IconMdiChevronRight from '~icons/mdi/chevron-right'
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
import './
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
.el-menu-item {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
margin-right: 10px;
|
|
148
|
-
font-size: 15px;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<div class="fly-sidebar">
|
|
3
|
+
<el-scrollbar :height="height">
|
|
4
|
+
<el-menu
|
|
5
|
+
:default-active="defaultActive"
|
|
6
|
+
:collapse="isCollapsed"
|
|
7
|
+
:router="router"
|
|
8
|
+
popper-class="fly-sidebar-popper"
|
|
9
|
+
>
|
|
10
|
+
<FlySidebarNode v-for="node in menu" :key="node.index" :node="node" />
|
|
11
|
+
</el-menu>
|
|
12
|
+
</el-scrollbar>
|
|
13
|
+
|
|
14
|
+
<div class="fly_sidebar__collapse-trigger" role="button" @click="toggleCollapse">
|
|
15
|
+
<FlyIconV2 :icon="isCollapsed ? IconMdiChevronRight : IconMdiChevronLeft" />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script lang="ts" setup>
|
|
21
|
+
import IconMdiChevronLeft from '~icons/mdi/chevron-left'
|
|
22
|
+
import IconMdiChevronRight from '~icons/mdi/chevron-right'
|
|
23
|
+
|
|
24
|
+
import { ElMenu, ElScrollbar } from 'element-plus'
|
|
25
|
+
|
|
26
|
+
import FlyIconV2 from '../../../general/fly-icon-v2/FlyIconV2.vue'
|
|
27
|
+
|
|
28
|
+
import './FlySidebar.element.scss'
|
|
29
|
+
import type { T_FlySidebarProps } from './FlySidebar.types'
|
|
30
|
+
import FlySidebarNode from './FlySidebarNode.vue'
|
|
31
|
+
|
|
32
|
+
const { router = true, defaultActive, menu, height } = defineProps<T_FlySidebarProps>()
|
|
33
|
+
|
|
34
|
+
const isCollapsed = defineModel<boolean>('collapsed', { default: false })
|
|
35
|
+
|
|
36
|
+
function toggleCollapse() {
|
|
37
|
+
isCollapsed.value = !isCollapsed.value
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<style lang="scss" scoped>
|
|
42
|
+
$radius: 9999px;
|
|
43
|
+
$height: 30px;
|
|
44
|
+
$trigger-height: 20px;
|
|
45
|
+
|
|
46
|
+
.fly-sidebar {
|
|
47
|
+
position: relative;
|
|
48
|
+
display: inline-block;
|
|
49
|
+
height: 100%;
|
|
50
|
+
background-color: var(--el-menu-bg-color);
|
|
51
|
+
border-inline-end: 1px solid var(--anra-border-color);
|
|
52
|
+
|
|
53
|
+
:deep(.el-menu) {
|
|
54
|
+
border-right: 0;
|
|
55
|
+
&.el-menu--vertical {
|
|
56
|
+
--el-menu-item-height: #{$height};
|
|
57
|
+
--el-menu-sub-item-height: calc(var(--el-menu-item-height) - 6px);
|
|
58
|
+
|
|
59
|
+
padding-inline-end: 1rem;
|
|
60
|
+
padding-top: calc(#{$trigger-height} + 10px);
|
|
61
|
+
padding-bottom: 1rem;
|
|
62
|
+
min-height: auto;
|
|
63
|
+
height: 100%;
|
|
64
|
+
|
|
65
|
+
&.el-menu--collapse {
|
|
66
|
+
padding-inline-end: 0.3rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&:not(.el-menu--collapse) {
|
|
70
|
+
width: 200px;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.fly_sidebar__collapse-trigger {
|
|
77
|
+
position: absolute;
|
|
78
|
+
top: 5px;
|
|
79
|
+
inset-inline-end: 0;
|
|
80
|
+
width: calc(#{$trigger-height} + 5px);
|
|
81
|
+
height: $trigger-height;
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
border-top: 1px solid var(--anra-border-color);
|
|
86
|
+
border-left: 1px solid var(--anra-border-color);
|
|
87
|
+
border-bottom: 1px solid var(--anra-border-color);
|
|
88
|
+
color: var(--dynamic-light);
|
|
89
|
+
border-start-start-radius: 5px;
|
|
90
|
+
border-end-start-radius: 5px;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: all 0.2s ease;
|
|
93
|
+
|
|
94
|
+
&:hover {
|
|
95
|
+
background-color: var(--anra-primary);
|
|
96
|
+
color: var(--anra-black);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
</style>
|
|
100
|
+
|
|
101
|
+
<style lang="scss">
|
|
102
|
+
$radius: 9999px;
|
|
103
|
+
$height: 30px;
|
|
104
|
+
|
|
105
|
+
.fly-sidebar .el-menu.el-menu--vertical,
|
|
106
|
+
.fly-sidebar-popper {
|
|
107
|
+
--el-menu-item-height: #{$height};
|
|
108
|
+
--el-menu-sub-item-height: calc(var(--el-menu-item-height) - 2px);
|
|
109
|
+
|
|
110
|
+
& > li:not(:last-child),
|
|
111
|
+
.el-sub-menu .el-menu .el-menu-item {
|
|
112
|
+
margin-bottom: 4px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.el-sub-menu {
|
|
116
|
+
--el-menu-item-height: #{$height};
|
|
117
|
+
|
|
118
|
+
.el-menu {
|
|
119
|
+
padding-top: 0.25rem;
|
|
120
|
+
padding-bottom: 0.25rem;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.el-sub-menu__title,
|
|
125
|
+
.el-menu-item {
|
|
126
|
+
border-top-right-radius: $radius;
|
|
127
|
+
border-bottom-right-radius: $radius;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.el-menu-item.is-active {
|
|
131
|
+
font-weight: 500;
|
|
132
|
+
background-color: var(--anra-primary);
|
|
133
|
+
color: var(--anra-text-on-primary);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.fly-sidebar {
|
|
138
|
+
--el-menu-base-level-padding: 10px;
|
|
139
|
+
|
|
140
|
+
.el-menu-item [class^='el-icon'],
|
|
141
|
+
.el-sub-menu__title [class^='el-icon']:not(.el-sub-menu__icon-arrow) {
|
|
142
|
+
margin-right: 10px;
|
|
143
|
+
font-size: 15px;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<el-menu-item v-if="node.type === 'item'" :index="node.index" :disabled="node.disabled">
|
|
3
|
-
<template v-if="node.icon">
|
|
4
|
-
<i class="el-icon">
|
|
5
|
-
<FlyIconV2 :icon="node.icon" />
|
|
6
|
-
</i>
|
|
7
|
-
</template>
|
|
8
|
-
|
|
9
|
-
<template #title>
|
|
10
|
-
{{ node.label }}
|
|
11
|
-
</template>
|
|
12
|
-
</el-menu-item>
|
|
13
|
-
|
|
14
|
-
<el-sub-menu v-else-if="node.type === 'submenu'" :index="node.index">
|
|
15
|
-
<template #title>
|
|
16
|
-
<template v-if="node.icon">
|
|
17
|
-
<i class="el-icon">
|
|
18
|
-
<FlyIconV2 :icon="node.icon" />
|
|
19
|
-
</i>
|
|
20
|
-
</template>
|
|
21
|
-
|
|
22
|
-
<span>{{ node.label }}</span>
|
|
23
|
-
</template>
|
|
24
|
-
|
|
25
|
-
<FlySidebarNode v-for="child in node.children" :key="child.index" :node="child" />
|
|
26
|
-
</el-sub-menu>
|
|
27
|
-
|
|
28
|
-
<el-menu-item-group v-else>
|
|
29
|
-
<template v-if="node.label" #title>
|
|
30
|
-
<span>{{ node.label }}</span>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<FlySidebarNode v-for="child in node.children" :key="child.index" :node="child" />
|
|
34
|
-
</el-menu-item-group>
|
|
35
|
-
</template>
|
|
36
|
-
|
|
37
|
-
<script lang="ts" setup>
|
|
38
|
-
import { ElMenuItem, ElMenuItemGroup, ElSubMenu } from 'element-plus'
|
|
39
|
-
|
|
40
|
-
import FlyIconV2 from '../../../general/fly-icon-v2/FlyIconV2.vue'
|
|
41
|
-
|
|
42
|
-
import type { T_FlySidebarNode } from './FlySidebar.types'
|
|
43
|
-
|
|
44
|
-
defineProps<{
|
|
45
|
-
node: T_FlySidebarNode
|
|
46
|
-
}>()
|
|
47
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<el-menu-item v-if="node.type === 'item'" :index="node.index" :disabled="node.disabled">
|
|
3
|
+
<template v-if="node.icon">
|
|
4
|
+
<i class="el-icon">
|
|
5
|
+
<FlyIconV2 :icon="node.icon" />
|
|
6
|
+
</i>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<template #title>
|
|
10
|
+
{{ node.label }}
|
|
11
|
+
</template>
|
|
12
|
+
</el-menu-item>
|
|
13
|
+
|
|
14
|
+
<el-sub-menu v-else-if="node.type === 'submenu'" :index="node.index">
|
|
15
|
+
<template #title>
|
|
16
|
+
<template v-if="node.icon">
|
|
17
|
+
<i class="el-icon">
|
|
18
|
+
<FlyIconV2 :icon="node.icon" />
|
|
19
|
+
</i>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<span>{{ node.label }}</span>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<FlySidebarNode v-for="child in node.children" :key="child.index" :node="child" />
|
|
26
|
+
</el-sub-menu>
|
|
27
|
+
|
|
28
|
+
<el-menu-item-group v-else>
|
|
29
|
+
<template v-if="node.label" #title>
|
|
30
|
+
<span>{{ node.label }}</span>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<FlySidebarNode v-for="child in node.children" :key="child.index" :node="child" />
|
|
34
|
+
</el-menu-item-group>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script lang="ts" setup>
|
|
38
|
+
import { ElMenuItem, ElMenuItemGroup, ElSubMenu } from 'element-plus'
|
|
39
|
+
|
|
40
|
+
import FlyIconV2 from '../../../general/fly-icon-v2/FlyIconV2.vue'
|
|
41
|
+
|
|
42
|
+
import type { T_FlySidebarNode } from './FlySidebar.types'
|
|
43
|
+
|
|
44
|
+
defineProps<{
|
|
45
|
+
node: T_FlySidebarNode
|
|
46
|
+
}>()
|
|
47
|
+
</script>
|
|
@@ -1,18 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { isRef } from 'vue'
|
|
2
|
+
import type { MaybeRef } from 'vue'
|
|
3
|
+
|
|
4
|
+
import { FallbackTranslateFn } from '@flyanra/js-utils/constants'
|
|
5
|
+
import type { Locale } from '@flyanra/js-utils/locales'
|
|
6
|
+
import type { ComposerTranslation } from 'vue-i18n'
|
|
7
|
+
|
|
8
|
+
export type T_LocaleInjector = {
|
|
9
|
+
t: ComposerTranslation
|
|
10
|
+
locale: Locale
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type I18nLike = {
|
|
14
|
+
t: ComposerTranslation
|
|
15
|
+
locale: MaybeRef<Locale>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let _t: ComposerTranslation = FallbackTranslateFn
|
|
19
|
+
let _locale: MaybeRef<Locale> = 'en'
|
|
20
|
+
let _initialized = false
|
|
21
|
+
|
|
22
|
+
export function initLocaleInjector(i18n: I18nLike): void {
|
|
23
|
+
_t = i18n.t
|
|
24
|
+
_locale = i18n.locale
|
|
25
|
+
_initialized = true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function UseLocaleInjector(): T_LocaleInjector {
|
|
29
|
+
if (!_initialized) {
|
|
30
|
+
console.warn('[vue-core] UseLocaleInjector: call initLocaleInjector() before use.')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
t: _t,
|
|
35
|
+
locale: isRef(_locale) ? _locale.value : _locale
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/composables/index.ts
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter, computed, toValue } from 'vue'
|
|
2
|
+
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
|
|
5
|
+
import type { T_FlySidebar } from '../components'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Minimal route shape required by `useSidebarActiveIndex`.
|
|
9
|
+
*
|
|
10
|
+
* You can pass your full route objects directly — only these fields are read.
|
|
11
|
+
* `meta.sidebarActiveIndex` is the route **name** of the sidebar item that
|
|
12
|
+
* should stay highlighted while on this route (see composable docs below).
|
|
13
|
+
*/
|
|
14
|
+
interface RouteItem {
|
|
15
|
+
name?: string
|
|
16
|
+
path: string
|
|
17
|
+
meta?: {
|
|
18
|
+
/** Route name of the sidebar item to keep active on this route. */
|
|
19
|
+
sidebarActiveIndex?: string
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the correct active sidebar index for Element Plus menus in router mode.
|
|
25
|
+
*
|
|
26
|
+
* **Resolution order** (first match wins):
|
|
27
|
+
* 1. **Exact path match** — `route.path` matches a menu item's `index` directly.
|
|
28
|
+
* 2. **`sidebarActiveIndex` meta** — the current route's `meta.sidebarActiveIndex`
|
|
29
|
+
* names which sidebar route should appear active. Use this for pages that are
|
|
30
|
+
* not listed in the sidebar (e.g. edit, create, or detail views).
|
|
31
|
+
* 3. **Longest static-prefix fallback** — strips dynamic segments (`:id`) and
|
|
32
|
+
* picks the menu item whose path is the longest prefix of the current path.
|
|
33
|
+
* This is a best-effort catch-all for any unhandled cases.
|
|
34
|
+
*
|
|
35
|
+
* > **Note:** Only one level of submenus is flattened. Deeply nested menu trees
|
|
36
|
+
* > (submenu inside a submenu) are not supported.
|
|
37
|
+
*
|
|
38
|
+
* @param menuItems - The sidebar menu tree passed to `FlySidebar`. Each leaf item
|
|
39
|
+
* must have `index` set to a route path (e.g. `/config/list`). Submenu items
|
|
40
|
+
* must have `type: 'submenu'` and a `children` array of leaf items.
|
|
41
|
+
* Accepts a plain value, `ref`, or `computed`.
|
|
42
|
+
*
|
|
43
|
+
* @param allRoutes - Flat list of all application routes. Only used to resolve a
|
|
44
|
+
* `sidebarActiveIndex` route name back to its path (resolution step 2).
|
|
45
|
+
* Each entry needs at minimum `{ name, path }`.
|
|
46
|
+
* Accepts a plain array, `ref`, or `computed`.
|
|
47
|
+
*
|
|
48
|
+
* @returns `ComputedRef<string>` — bind directly to `:default-active` on
|
|
49
|
+
* `FlySidebar` or `el-menu`.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* // In your sidebar component setup:
|
|
54
|
+
* const activeIndex = useSidebarActiveIndex(menuItems, router.getRoutes())
|
|
55
|
+
* ```
|
|
56
|
+
* ```html
|
|
57
|
+
* <!-- In your sidebar template: -->
|
|
58
|
+
* <FlySidebar :default-active="activeIndex" ... />
|
|
59
|
+
* ```
|
|
60
|
+
* ```ts
|
|
61
|
+
* // In a route that is NOT listed in the sidebar (e.g. an edit page),
|
|
62
|
+
* // tell the composable which sidebar item should stay highlighted:
|
|
63
|
+
* {
|
|
64
|
+
* name: 'ConfigEdit',
|
|
65
|
+
* path: '/config/:id/edit',
|
|
66
|
+
* meta: { sidebarActiveIndex: 'ConfigList' }
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function useSidebarActiveIndex(
|
|
71
|
+
menuItems: MaybeRefOrGetter<T_FlySidebar>,
|
|
72
|
+
allRoutes: MaybeRefOrGetter<RouteItem[]>
|
|
73
|
+
) {
|
|
74
|
+
const route = useRoute()
|
|
75
|
+
|
|
76
|
+
return computed(() => {
|
|
77
|
+
const currentPath = route.path
|
|
78
|
+
const items = toValue(menuItems)
|
|
79
|
+
const routes = toValue(allRoutes)
|
|
80
|
+
|
|
81
|
+
const flatItems = items.flatMap((item) =>
|
|
82
|
+
item.type === 'submenu' ? (item.children ?? []) : [item]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// 1. Exact match — handles direct navigation and redirect-resolved paths
|
|
86
|
+
if (flatItems.some((i) => i.index === currentPath)) return currentPath
|
|
87
|
+
|
|
88
|
+
// 2. sidebarActiveIndex — explicit route name pointing to the sidebar item
|
|
89
|
+
// that should stay active while on this route
|
|
90
|
+
const activeIndexName = route.meta?.sidebarActiveIndex as string | undefined
|
|
91
|
+
if (activeIndexName) {
|
|
92
|
+
const ownerRoute = routes.find((r) => r.name === activeIndexName)
|
|
93
|
+
if (ownerRoute) return ownerRoute.path
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 3. Longest static-prefix fallback (strips dynamic segments before matching)
|
|
97
|
+
return (
|
|
98
|
+
flatItems
|
|
99
|
+
.filter((i) => currentPath.startsWith(i.index.split(':')[0]))
|
|
100
|
+
.sort((a, b) => b.index.length - a.index.length)[0]?.index ?? currentPath
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
}
|