@flyanra/vue-core 1.0.2 → 1.2.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.
Files changed (53) hide show
  1. package/dist/types/components/navigation/menu/fly-sidebar/FlySidebar.types.d.ts +14 -1
  2. package/dist/types/components/navigation/menu/fly-sidebar/FlySidebar.vue.d.ts +10 -4
  3. package/dist/types/composables/UseLocaleInjector.d.ts +7 -1
  4. package/dist/types/composables/index.d.ts +3 -1
  5. package/dist/types/composables/useSidebarActiveIndex.d.ts +66 -0
  6. package/package.json +158 -156
  7. package/src/components/basic/fly-button/FlyButton.types.ts +28 -28
  8. package/src/components/basic/fly-button/FlyButton.vue +178 -178
  9. package/src/components/basic/typography/FlyCollapseTitleRenderer.vue +46 -46
  10. package/src/components/feedback/tooltip/FlyInfoTooltip.vue +49 -49
  11. package/src/components/feedback/tooltip/FlyTooltip.element.scss +4 -4
  12. package/src/components/feedback/tooltip/FlyTooltip.vue +60 -60
  13. package/src/components/form/form.utils.ts +72 -72
  14. package/src/components/form/index.ts +6 -6
  15. package/src/components/form/input-renderers/boolean/FlyBoolean.vue +48 -48
  16. package/src/components/form/input-renderers/form-item-renderer/FlyFormItemRenderer.vue +48 -48
  17. package/src/components/form/input-renderers/form-item-renderer/index.ts +4 -4
  18. package/src/components/form/input-renderers/form-schema-renderer/FlyFormSchemaRenderer.types.ts +3 -3
  19. package/src/components/form/input-renderers/form-schema-renderer/FlyFormSchemaRenderer.utils.ts +221 -221
  20. package/src/components/form/input-renderers/form-schema-renderer/FlyFormSchemaRenderer.vue +75 -75
  21. package/src/components/form/input-renderers/form-schema-renderer/UseFormSchemaRenderer.ts +97 -97
  22. package/src/components/form/input-renderers/input-renderer/FlyInputRenderer.vue +49 -49
  23. package/src/components/form/input-renderers/input-renderer/index.ts +4 -4
  24. package/src/components/form/input-renderers/input-renderer.utils.ts +18 -18
  25. package/src/components/form/input-renderers/number/FlyNumber.vue +64 -64
  26. package/src/components/form/input-renderers/regional-tenant-form-renderer/FlyRegionalTenantFormRenderer.types.ts +15 -15
  27. package/src/components/form/input-renderers/regional-tenant-form-renderer/FlyRegionalTenantFormRenderer.vue +99 -99
  28. package/src/components/form/input-renderers/select/FlySelect.scss +1 -1
  29. package/src/components/form/input-renderers/select/FlySelect.utils.ts +37 -37
  30. package/src/components/form/input-renderers/text/FlyText.vue +70 -70
  31. package/src/components/form/input-renderers/value-unit/FlyValueUnit.vue +102 -102
  32. package/src/components/form/input-validators.ts +99 -99
  33. package/src/components/general/fly-icon-v2/FlyIconV2.types.ts +6 -6
  34. package/src/components/general/fly-icon-v2/FlyIconV2.vue +17 -17
  35. package/src/components/general/fly-icon-v2/index.ts +6 -6
  36. package/src/components/general/index.ts +5 -5
  37. package/src/components/index.ts +7 -7
  38. package/src/components/navigation/menu/fly-mini-sidebar/FlyMiniSidebar.types.ts +8 -8
  39. package/src/components/navigation/menu/fly-mini-sidebar/FlyMiniSidebarItem.vue +55 -55
  40. package/src/components/navigation/menu/fly-sidebar/FlySidebar.types.ts +49 -36
  41. package/src/components/navigation/menu/fly-sidebar/FlySidebar.vue +146 -151
  42. package/src/components/navigation/menu/fly-sidebar/FlySidebarNode.vue +47 -47
  43. package/src/composables/UseLocaleInjector.ts +37 -18
  44. package/src/composables/index.ts +4 -1
  45. package/src/composables/useSidebarActiveIndex.ts +103 -0
  46. package/src/styles/element/mixins/collapse.scss +61 -61
  47. package/src/styles/element/overrides/form.scss +121 -121
  48. package/src/styles/element/overrides/message-box.scss +2 -2
  49. package/src/styles/element/overrides/radio.scss +2 -2
  50. package/src/styles/element/overrides/select.scss +9 -9
  51. package/src/utils/index.ts +2 -2
  52. package/src/utils/pure-functions.ts +15 -15
  53. 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 { ref } from 'vue'
25
-
26
- import { ElMenu, ElScrollbar } from 'element-plus'
27
-
28
- import FlyIconV2 from '../../../general/fly-icon-v2/FlyIconV2.vue'
29
-
30
- import './FlySidebar.element.scss'
31
- import type { T_FlySidebarProps } from './FlySidebar.types'
32
- import FlySidebarNode from './FlySidebarNode.vue'
33
-
34
- const props = withDefaults(defineProps<T_FlySidebarProps>(), {
35
- localStorageKey: 'fly-sidebar-collapsed',
36
- router: true,
37
- defaultActive: undefined
38
- })
39
-
40
- const isCollapsed = ref(props.isCollapsed ?? false)
41
-
42
- function toggleCollapse() {
43
- isCollapsed.value = !isCollapsed.value
44
- }
45
- </script>
46
-
47
- <style lang="scss" scoped>
48
- $radius: 9999px;
49
- $height: 30px;
50
- $trigger-height: 20px;
51
-
52
- .fly-sidebar {
53
- position: relative;
54
- display: inline-block;
55
- height: 100%;
56
- background-color: var(--el-menu-bg-color);
57
- border-inline-end: 1px solid var(--anra-border-color);
58
-
59
- :deep(.el-menu) {
60
- border-right: 0;
61
- &.el-menu--vertical {
62
- --el-menu-item-height: #{$height};
63
- --el-menu-sub-item-height: calc(var(--el-menu-item-height) - 6px);
64
-
65
- padding-inline-end: 1rem;
66
- padding-top: calc(#{$trigger-height} + 10px);
67
- padding-bottom: 1rem;
68
- min-height: auto;
69
- height: 100%;
70
-
71
- &.el-menu--collapse {
72
- padding-inline-end: 0.3rem;
73
- }
74
-
75
- &:not(.el-menu--collapse) {
76
- width: 200px;
77
- }
78
- }
79
- }
80
- }
81
-
82
- .fly_sidebar__collapse-trigger {
83
- position: absolute;
84
- top: 5px;
85
- inset-inline-end: 0;
86
- width: calc(#{$trigger-height} + 5px);
87
- height: $trigger-height;
88
- display: flex;
89
- align-items: center;
90
- justify-content: center;
91
- border-top: 1px solid var(--anra-border-color);
92
- border-left: 1px solid var(--anra-border-color);
93
- border-bottom: 1px solid var(--anra-border-color);
94
- color: var(--dynamic-light);
95
- border-start-start-radius: 5px;
96
- border-end-start-radius: 5px;
97
- cursor: pointer;
98
- transition: all 0.2s ease;
99
-
100
- &:hover {
101
- background-color: var(--anra-primary);
102
- }
103
- }
104
- </style>
105
-
106
- <style lang="scss">
107
- $radius: 9999px;
108
- $height: 30px;
109
-
110
- .fly-sidebar .el-menu.el-menu--vertical,
111
- .fly-sidebar-popper {
112
- --el-menu-item-height: #{$height};
113
- --el-menu-sub-item-height: calc(var(--el-menu-item-height) - 2px);
114
-
115
- & > li:not(:last-child),
116
- .el-sub-menu .el-menu .el-menu-item {
117
- margin-bottom: 4px;
118
- }
119
-
120
- .el-sub-menu {
121
- --el-menu-item-height: #{$height};
122
-
123
- .el-menu {
124
- padding-top: 0.25rem;
125
- padding-bottom: 0.25rem;
126
- }
127
- }
128
-
129
- .el-sub-menu__title,
130
- .el-menu-item {
131
- border-top-right-radius: $radius;
132
- border-bottom-right-radius: $radius;
133
- }
134
-
135
- .el-menu-item.is-active {
136
- font-weight: 500;
137
- background-color: var(--anra-primary);
138
- color: var(--anra-text-on-primary);
139
- }
140
- }
141
-
142
- .fly-sidebar {
143
- --el-menu-base-level-padding: 10px;
144
-
145
- .el-menu-item [class^='el-icon'],
146
- .el-sub-menu__title [class^='el-icon']:not(.el-sub-menu__icon-arrow) {
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 { inject } from 'vue'
2
-
3
- import { FallbackTranslateFn, I18N_KEY } from '@flyanra/js-utils/constants'
4
- import type { Locale } from '@flyanra/js-utils/locales'
5
- import type { ComposerTranslation } from 'vue-i18n'
6
-
7
- type T_LocaleInjector = {
8
- t: ComposerTranslation
9
- locale: Locale
10
- }
11
- export function UseLocaleInjector(): T_LocaleInjector {
12
- const i18n = inject(I18N_KEY)
13
-
14
- return {
15
- t: i18n?.t || FallbackTranslateFn,
16
- locale: (i18n?.locale as Locale | undefined) || 'en'
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
+ }
@@ -1 +1,4 @@
1
- export { UseLocaleInjector } from './UseLocaleInjector'
1
+ export { initLocaleInjector, UseLocaleInjector } from './UseLocaleInjector'
2
+ export type { T_LocaleInjector } from './UseLocaleInjector'
3
+
4
+ export { useSidebarActiveIndex } from './useSidebarActiveIndex'
@@ -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
+ }