@ulu/frontend-vue 0.1.0-beta.25 → 0.1.0-beta.27

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.
@@ -4,9 +4,10 @@
4
4
  aria-label="Breadcrumb"
5
5
  v-if="items.length"
6
6
  >
7
- <ul :class="classes.list">
7
+ <ol :class="classes.list">
8
8
  <li v-for="(item, index) in items" :key="index" :class="classes.item">
9
9
  <router-link
10
+ v-if="!item.current"
10
11
  :to="item.to"
11
12
  :class="classes.link"
12
13
  :aria-current="item.current ? 'page' : null"
@@ -15,6 +16,11 @@
15
16
  {{ item.title }}
16
17
  </slot>
17
18
  </router-link>
19
+ <span v-else :class="item.current">
20
+ <slot :item="item">
21
+ {{ item.title }}
22
+ </slot>
23
+ </span>
18
24
  <template v-if="index < items.length - 1">
19
25
  <slot name="separator">
20
26
  <UluIcon
@@ -24,7 +30,7 @@
24
30
  </slot>
25
31
  </template>
26
32
  </li>
27
- </ul>
33
+ </ol>
28
34
  </nav>
29
35
  </template>
30
36
 
@@ -63,6 +69,7 @@
63
69
  list: "breadcrumb__list",
64
70
  item: "breadcrumb__item",
65
71
  link: "breadcrumb__link",
72
+ current: "breadcrumb__current",
66
73
  separator: "breadcrumb__separator"
67
74
  })
68
75
  }
@@ -10,5 +10,4 @@ export { useWindowResize } from './useWindowResize.js';
10
10
  export { useRequiredInject } from './useRequiredInject.js';
11
11
  export { useBreakpointManager } from './useBreakpointManager.js';
12
12
  export { usePagination } from './usePagination.js';
13
- export { useDocumentTitle } from './useDocumentTitle.js';
14
- export { usePageTitle } from './usePageTitle.js';
13
+ export { useDocumentTitle } from './useDocumentTitle.js';
@@ -1,46 +1,60 @@
1
- import { computed } from 'vue';
2
- import { useHead as defaultUseHead } from '@unhead/vue';
3
- import { useRoute as defaultUseRoute } from 'vue-router';
4
- import { pageTitles } from './usePageTitle.js';
1
+ import { reactive, watchEffect, onUnmounted, unref, computed } from "vue";
2
+ import { useHead as defaultUseHead } from "@unhead/vue";
3
+ import { useRoute as defaultUseRoute } from "vue-router";
4
+ import { getRouteTitle } from "../utils/router.js";
5
+
6
+ // A reactive map to store component-defined titles.
7
+ const componentTitles = reactive({});
5
8
 
6
9
  /**
7
- * Manages the document's <title> tag based on the current route's title.
8
- * It pulls titles from the `usePageTitle` system, falling back to `meta.title`,
9
- * and formats it with a template.
10
+ * A composable to manage the document title.
11
+ *
12
+ * When called with a `title` option, it sets a dynamic title for the current page.
13
+ * This is for use within specific components.
10
14
  *
11
- * This should be called once in the root App.vue component.
15
+ * When called without a `title` option (typically in App.vue), it manages the
16
+ * document title for the whole app, using titles from components or route meta.
12
17
  *
13
18
  * @param {object} options
14
- * @param {string} [options.titleTemplate='%s | My Awesome Site'] - The template for the title.
15
- * @param {Function} [options.useRoute=defaultUseRoute] - The `useRoute` function, injectable for testing.
16
- * @param {Function} [options.useHead=defaultUseHead] - The `useHead` function, injectable for testing.
19
+ * @param {import('vue').Ref<string> | string} [options.title] - The dynamic title to set for the current page.
20
+ * @param {string} [options.titleTemplate='%s'] - The template for the document title, e.g., '%s | My Site'.
21
+ * @param {Function} [options.useRoute=defaultUseRoute] - Injectable `useRoute` for testing.
22
+ * @param {Function} [options.useHead=defaultUseHead] - Injectable `useHead` for testing.
17
23
  */
18
24
  export function useDocumentTitle(options = {}) {
19
25
  const {
20
- titleTemplate = '%s',
26
+ title,
27
+ titleTemplate = "%s",
21
28
  useRoute = defaultUseRoute,
22
- useHead = defaultUseHead
29
+ useHead = defaultUseHead,
23
30
  } = options;
24
31
 
25
32
  const route = useRoute();
33
+ const path = route.path;
26
34
 
27
- const documentTitle = computed(() => {
28
- // Get the title from our reactive state, or fall back to the route's meta.
29
- const titleFromState = pageTitles[route.path];
30
- const titleFromMeta = route.meta.title;
35
+ // --- Setter Mode ---
36
+ // If a title is provided, we're in "setter" mode, used within a component.
37
+ if (title !== undefined) {
38
+ watchEffect(() => {
39
+ componentTitles[path] = unref(title);
40
+ });
31
41
 
32
- let title = titleFromState || titleFromMeta;
42
+ onUnmounted(() => {
43
+ delete componentTitles[path];
44
+ });
45
+ return; // End execution for setter mode.
46
+ }
33
47
 
34
- // If the title from meta is a function, resolve it.
35
- if (typeof title === 'function') {
36
- title = title(route);
37
- }
48
+ // --- Manager Mode ---
49
+ // If no title is provided, we're in "manager" mode, used in App.vue.
50
+ const documentTitle = computed(() => {
51
+ const titleFromComponent = componentTitles[route.path];
52
+ const titleFromMeta = getRouteTitle(route, route);
53
+ const resolvedTitle = titleFromComponent || titleFromMeta;
38
54
 
39
- // Format the title with the template, or provide a default.
40
- return title ? titleTemplate.replace('%s', title) : 'App';
55
+ return resolvedTitle ? titleTemplate.replace("%s", resolvedTitle) : "App";
41
56
  });
42
57
 
43
- // useHead is reactive, so it will automatically update when documentTitle changes.
44
58
  useHead({
45
59
  title: documentTitle,
46
60
  });
@@ -1,10 +1,29 @@
1
- import { getPageTitle } from "../composables/usePageTitle.js";
2
1
  /**
3
2
  * This Module Creates Menus from route or router config
4
3
  * - Note: Functions prefixed with "$" work with $route objects (running application, provided by vue-router ie $router, useRoute, etc),
5
4
  * @module router-utils
6
5
  */
7
6
 
7
+ /**
8
+ * Resolves a route's title from its meta.
9
+ * - If `meta.title` is a function, it's called with the `currentRoute` (or the route itself).
10
+ * - Otherwise, `meta.title` is returned.
11
+ * This function is the single source of truth for resolving titles from route configuration.
12
+ * @param {object} route The route or route match object.
13
+ * @param {object} [currentRoute] The current route from `useRoute()`, passed to functional titles.
14
+ * @returns {string|undefined} The resolved title, or undefined if not found.
15
+ */
16
+ export function getRouteTitle(route, currentRoute) {
17
+ const meta = route?.meta || {};
18
+ let title = meta.title;
19
+
20
+ if (typeof title === "function") {
21
+ title = title(currentRoute || route);
22
+ }
23
+
24
+ return title;
25
+ }
26
+
8
27
  /**
9
28
  * Route Menu Item
10
29
  * @typedef {Object} RouteMenuItem
@@ -39,7 +58,6 @@ export function createBaseMenu(routes, options) {
39
58
  return currentRoutes
40
59
  .filter(r => opts.qualifier(r, parentPath))
41
60
  .map(r => {
42
- // Need to grab meta from child but use the parent path
43
61
  const menuRoute = r.children ? getChildIndexRoute(r.children) : r;
44
62
  const children = r.children ? r.children.filter(child => child.path !== "") : false;
45
63
  const item = createMenuItem(menuRoute, getItemPath(r, parentPath), opts.item);
@@ -89,7 +107,6 @@ export function createSectionMenu(routes, sectionPath, options) {
89
107
  };
90
108
  const opts = Object.assign({}, defaults, options);
91
109
  const base = routes.find(r => r.path !== "/" && sectionPath.includes(r.path));
92
- // Go through each item and
93
110
  const getSection = (current, previous, path) => {
94
111
  if (current.children) {
95
112
  const child = current.children.find(c => c.path.includes(sectionPath));
@@ -110,20 +127,6 @@ export function createSectionMenu(routes, sectionPath, options) {
110
127
  .sort(opts.sort);
111
128
  }
112
129
 
113
- /**
114
- * For a given route this will return the route that renders. For routes without
115
- * children this is the route itself for those with children (first child with empty path)
116
- * @param {Object} route Route object to resolve
117
- * @returns {Object} Resolved route
118
- */
119
- // export function resolveRouteIndex(route) {
120
- // if (route.children) {
121
- // return getChildIndexRoute(route.children);
122
- // } else {
123
- // return route;
124
- // }
125
- // }
126
-
127
130
  /**
128
131
  * For a given array of child routes return the index
129
132
  * @param {Array} children Children array of routes
@@ -132,6 +135,7 @@ export function createSectionMenu(routes, sectionPath, options) {
132
135
  export function getChildIndexRoute(children) {
133
136
  return children.find(r => r.path === "");
134
137
  }
138
+
135
139
  /**
136
140
  * Creates common menu item structure from route, pulls title and weight from meta (on route or index child)
137
141
  * @param {Object} route Route
@@ -151,9 +155,10 @@ export function createMenuItem(route, routePath = route.path, options) {
151
155
  if (opts.indexMeta && route.children) {
152
156
  meta = Object.assign({}, meta, getChildIndexRoute(route.children)?.meta);
153
157
  }
158
+ const routeWithMergedMeta = { ...route, meta };
154
159
  const item = {
155
160
  path: routePath,
156
- title: meta?.title || "Missing Title",
161
+ title: getRouteTitle(routeWithMergedMeta, route) || "Missing Title",
157
162
  weight: meta?.weight || 0,
158
163
  meta
159
164
  };
@@ -162,23 +167,26 @@ export function createMenuItem(route, routePath = route.path, options) {
162
167
  }
163
168
  return item;
164
169
  }
170
+
165
171
  /**
166
- * Test if route is static (doesn't incude parameters)
172
+ * Test if route is static (doesn't include parameters)
167
173
  * @param {Object} route Route object to test
168
174
  * @returns {Boolean} Whether or not this route is static (not dynamic)
169
175
  */
170
176
  export function isStaticRoute(route) {
171
177
  return !route.path.includes("/:");
172
178
  }
179
+
173
180
  /**
174
181
  *
175
182
  * @param {Object} route Route object to test
176
183
  * @returns {Boolean} Whether or not this route is a static base route
177
184
  */
178
185
  export function isStaticBaseRoute(route) {
179
- const matches = route.path.match(/\//) || [];
186
+ const matches = route.path.match(/\//g) || [];
180
187
  return isStaticRoute(route) && matches.length === 1;
181
188
  }
189
+
182
190
  /**
183
191
  * Function to make normal <a> behave as router links instread of page reload
184
192
  * @param {Object} router Router instance (ie src/router) to push routes to
@@ -195,6 +203,7 @@ export function nativeLinkRouter(router, event) {
195
203
  }
196
204
  }
197
205
  }
206
+
198
207
  /**
199
208
  * Returns the child routes for base route
200
209
  * @param {Object} route Route Object
@@ -203,6 +212,7 @@ export function nativeLinkRouter(router, event) {
203
212
  export function $getRouteChildren(route, parent = $getParentRoute(route)) {
204
213
  return parent?.children;
205
214
  }
215
+
206
216
  /**
207
217
  * Returns the route's parent
208
218
  * @param {Object} route Route Object
@@ -257,7 +267,6 @@ export function $createSectionMenu(route, options) {
257
267
  /**
258
268
  * For a given $route, this will generate a breadcrumb trail.
259
269
  * It iterates through `route.matched` to build the trail.
260
- * - Prioritizes titles set via the `usePageTitle` composable for the current page.
261
270
  * - Falls back to `meta.title` (string or function).
262
271
  * - Skips routes where `meta.breadcrumb` is set to `false`.
263
272
  * - Avoids duplicate crumbs for nested routes with empty paths.
@@ -269,36 +278,16 @@ export function $createBreadcrumb(route) {
269
278
  let prevPath;
270
279
 
271
280
  const crumbs = matched.reduce((arr, match, index) => {
272
- // Skip routes configured to be hidden from breadcrumbs
273
281
  if (match.meta?.breadcrumb === false) {
274
282
  return arr;
275
283
  }
276
284
 
277
- // Avoid duplicates from child routes with empty paths
278
285
  if (match.path === prevPath) {
279
286
  return arr;
280
287
  }
281
288
 
282
289
  const isLast = index === matched.length - 1;
283
- let title;
284
-
285
- // 1. Prioritize component-defined title for the current page
286
- if (isLast) {
287
- title = getPageTitle(currentPath);
288
- }
289
-
290
- // 2. Fallback to meta.title (function or string)
291
- if (!title) {
292
- const metaTitle = match.meta?.title;
293
- if (typeof metaTitle === 'function') {
294
- title = metaTitle(route);
295
- } else {
296
- title = metaTitle;
297
- }
298
- }
299
-
300
- // 3. Final fallback
301
- title = title || 'Missing Title';
290
+ const title = getRouteTitle(match, route) || "Missing Title";
302
291
 
303
292
  arr.push({
304
293
  title,
@@ -311,4 +300,4 @@ export function $createBreadcrumb(route) {
311
300
  }, []);
312
301
 
313
302
  return crumbs;
314
- }
303
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend-vue",
3
- "version": "0.1.0-beta.25",
3
+ "version": "0.1.0-beta.27",
4
4
  "description": "A modular and tree-shakeable Vue 3 component library for the Ulu frontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -59,7 +59,7 @@
59
59
  "homepage": "https://github.com/Jscherbe/frontend-vue#readme",
60
60
  "peerDependencies": {
61
61
  "@headlessui/vue": "^1.7.23",
62
- "@ulu/frontend": "^0.1.0-beta.102",
62
+ "@ulu/frontend": "^0.1.0-beta.104",
63
63
  "@unhead/vue": "^2.0.11",
64
64
  "vue": "^3.5.17",
65
65
  "vue-router": "^4.5.1"
@@ -82,7 +82,7 @@
82
82
  "@storybook/addon-essentials": "^9.0.0-alpha.12",
83
83
  "@storybook/addon-links": "^9.1.1",
84
84
  "@storybook/vue3-vite": "^9.1.1",
85
- "@ulu/frontend": "^0.1.0-beta.102",
85
+ "@ulu/frontend": "^0.1.0-beta.104",
86
86
  "@unhead/vue": "^2.0.11",
87
87
  "@vitejs/plugin-vue": "^6.0.0",
88
88
  "ollama": "^0.5.16",
@@ -5,5 +5,4 @@ export { useRequiredInject } from "./useRequiredInject.js";
5
5
  export { useBreakpointManager } from "./useBreakpointManager.js";
6
6
  export { usePagination } from "./usePagination.js";
7
7
  export { useDocumentTitle } from "./useDocumentTitle.js";
8
- export { usePageTitle } from "./usePageTitle.js";
9
8
  //# sourceMappingURL=index.d.ts.map
@@ -1,16 +1,20 @@
1
1
  /**
2
- * Manages the document's <title> tag based on the current route's title.
3
- * It pulls titles from the `usePageTitle` system, falling back to `meta.title`,
4
- * and formats it with a template.
2
+ * A composable to manage the document title.
5
3
  *
6
- * This should be called once in the root App.vue component.
4
+ * When called with a `title` option, it sets a dynamic title for the current page.
5
+ * This is for use within specific components.
6
+ *
7
+ * When called without a `title` option (typically in App.vue), it manages the
8
+ * document title for the whole app, using titles from components or route meta.
7
9
  *
8
10
  * @param {object} options
9
- * @param {string} [options.titleTemplate='%s | My Awesome Site'] - The template for the title.
10
- * @param {Function} [options.useRoute=defaultUseRoute] - The `useRoute` function, injectable for testing.
11
- * @param {Function} [options.useHead=defaultUseHead] - The `useHead` function, injectable for testing.
11
+ * @param {import('vue').Ref<string> | string} [options.title] - The dynamic title to set for the current page.
12
+ * @param {string} [options.titleTemplate='%s'] - The template for the document title, e.g., '%s | My Site'.
13
+ * @param {Function} [options.useRoute=defaultUseRoute] - Injectable `useRoute` for testing.
14
+ * @param {Function} [options.useHead=defaultUseHead] - Injectable `useHead` for testing.
12
15
  */
13
16
  export function useDocumentTitle(options?: {
17
+ title?: import("vue").Ref<string> | string;
14
18
  titleTemplate?: string;
15
19
  useRoute?: Function;
16
20
  useHead?: Function;
@@ -1 +1 @@
1
- {"version":3,"file":"useDocumentTitle.d.ts","sourceRoot":"","sources":["../../lib/composables/useDocumentTitle.js"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AACH,2CAJG;IAAyB,aAAa,GAA9B,MAAM;IACa,QAAQ;IACR,OAAO;CACpC,QA8BA"}
1
+ {"version":3,"file":"useDocumentTitle.d.ts","sourceRoot":"","sources":["../../lib/composables/useDocumentTitle.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;GAcG;AACH,2CALG;IAAqD,KAAK,GAAlD,OAAO,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM;IACjB,aAAa,GAA9B,MAAM;IACa,QAAQ;IACR,OAAO;CACpC,QAsCA"}
@@ -3,6 +3,16 @@
3
3
  * - Note: Functions prefixed with "$" work with $route objects (running application, provided by vue-router ie $router, useRoute, etc),
4
4
  * @module router-utils
5
5
  */
6
+ /**
7
+ * Resolves a route's title from its meta.
8
+ * - If `meta.title` is a function, it's called with the `currentRoute` (or the route itself).
9
+ * - Otherwise, `meta.title` is returned.
10
+ * This function is the single source of truth for resolving titles from route configuration.
11
+ * @param {object} route The route or route match object.
12
+ * @param {object} [currentRoute] The current route from `useRoute()`, passed to functional titles.
13
+ * @returns {string|undefined} The resolved title, or undefined if not found.
14
+ */
15
+ export function getRouteTitle(route: object, currentRoute?: object): string | undefined;
6
16
  /**
7
17
  * Route Menu Item
8
18
  * @typedef {Object} RouteMenuItem
@@ -38,12 +48,6 @@ export function createSectionMenu(routes: any, sectionPath: any, options: {
38
48
  includeIndex: boolean;
39
49
  item: any;
40
50
  }): Array<RouteMenuItem>;
41
- /**
42
- * For a given route this will return the route that renders. For routes without
43
- * children this is the route itself for those with children (first child with empty path)
44
- * @param {Object} route Route object to resolve
45
- * @returns {Object} Resolved route
46
- */
47
51
  /**
48
52
  * For a given array of child routes return the index
49
53
  * @param {Array} children Children array of routes
@@ -64,7 +68,7 @@ export function createMenuItem(route: any, routePath: any, options: {
64
68
  indexMeta: Function;
65
69
  }): RouteMenuItem;
66
70
  /**
67
- * Test if route is static (doesn't incude parameters)
71
+ * Test if route is static (doesn't include parameters)
68
72
  * @param {Object} route Route object to test
69
73
  * @returns {Boolean} Whether or not this route is static (not dynamic)
70
74
  */
@@ -113,7 +117,6 @@ export function $createSectionMenu(route: any, options: {
113
117
  /**
114
118
  * For a given $route, this will generate a breadcrumb trail.
115
119
  * It iterates through `route.matched` to build the trail.
116
- * - Prioritizes titles set via the `usePageTitle` composable for the current page.
117
120
  * - Falls back to `meta.title` (string or function).
118
121
  * - Skips routes where `meta.breadcrumb` is set to `false`.
119
122
  * - Avoids duplicate crumbs for nested routes with empty paths.
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../lib/utils/router.js"],"names":[],"mappings":"AACA;;;;GAIG;AAEH;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,uCANW,GAAC,WAET;IAAwB,SAAS;IACT,IAAI;CAC5B,GAAU,KAAK,CAAE,aAAa,CAAC,CAiCjC;AAED;;GAEG;AACH,4CAcC;AAED;;;;;;;;GAQG;AACH,0CAPW,GAAC,eACD,GAAC,WAET;IAAyB,YAAY;IACb,IAAI;CAC5B,GAAU,KAAK,CAAE,aAAa,CAAC,CA6BjC;AAED;;;;;GAKG;AASH;;;;GAIG;AACH,yDAEC;AACD;;;;;;;;GAQG;AACH,oEAJG;IAA0B,MAAM;IACN,SAAS;CACnC,GAAU,aAAa,CAsBzB;AACD;;;;GAIG;AACH,mDAEC;AACD;;;;GAIG;AACH,uDAGC;AACD;;;;GAIG;AACH,gEAUC;AACD;;;;GAIG;AACH,iEAEC;AACD;;;;;GAKG;AACH,2DAFY,UAAW,CAUtB;AAUD;;;;;;;;;;GAUG;AACH,wDALG;IAAwB,MAAM;IACL,YAAY;IACb,IAAI;CAC5B,GAAU,KAAK,CAAE,aAAa,CAAC,CAgBjC;AAED;;;;;;;;;GASG;AACH,+CAFa,KAAK,CAAE;IAAC,KAAK,SAAS;IAAC,EAAE,MAAS;IAAC,OAAO,UAAS;CAAC,CAAC,CAiDjE"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../lib/utils/router.js"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;GAQG;AACH,qCAJW,MAAM,iBACN,MAAM,GACJ,MAAM,GAAC,SAAS,CAW5B;AAED;;;;;GAKG;AAEH;;;;;;;GAOG;AACH,uCANW,GAAC,WAET;IAAwB,SAAS;IACT,IAAI;CAC5B,GAAU,KAAK,CAAE,aAAa,CAAC,CAgCjC;AAED;;GAEG;AACH,4CAcC;AAED;;;;;;;;GAQG;AACH,0CAPW,GAAC,eACD,GAAC,WAET;IAAyB,YAAY;IACb,IAAI;CAC5B,GAAU,KAAK,CAAE,aAAa,CAAC,CA4BjC;AAED;;;;GAIG;AACH,yDAEC;AAED;;;;;;;;GAQG;AACH,oEAJG;IAA0B,MAAM;IACN,SAAS;CACnC,GAAU,aAAa,CAuBzB;AAED;;;;GAIG;AACH,mDAEC;AAED;;;;GAIG;AACH,uDAGC;AAED;;;;GAIG;AACH,gEAUC;AAED;;;;GAIG;AACH,iEAEC;AAED;;;;;GAKG;AACH,2DAFY,UAAW,CAUtB;AAUD;;;;;;;;;;GAUG;AACH,wDALG;IAAwB,MAAM;IACL,YAAY;IACb,IAAI;CAC5B,GAAU,KAAK,CAAE,aAAa,CAAC,CAgBjC;AAED;;;;;;;;GAQG;AACH,+CAFa,KAAK,CAAE;IAAC,KAAK,SAAS;IAAC,EAAE,MAAS;IAAC,OAAO,UAAS;CAAC,CAAC,CA6BjE"}
@@ -1,37 +0,0 @@
1
- import { reactive, watchEffect, onUnmounted, unref } from "vue";
2
- import { useRoute as defaultUseRoute } from "vue-router";
3
-
4
- // A reactive map to store component-defined titles for the current route.
5
- // Key: route.path, Value: title string
6
- export const pageTitles = reactive({});
7
-
8
- /**
9
- * A composable to set the title for the current page/route from within its component.
10
- * This provides a single source of truth for a page's title, which can be
11
- * consumed by various parts of the application (e.g., breadcrumbs, document title).
12
- * @param {import('vue').Ref<string> | string} title The title to set for the current page. Can be a ref, computed, or a plain string.
13
- * @param {{ useRoute: Function }} options For dependency injection in tests/stories.
14
- */
15
- export function usePageTitle(title, { useRoute = defaultUseRoute } = {}) {
16
- const route = useRoute();
17
- const path = route.path;
18
-
19
- watchEffect(() => {
20
- pageTitles[path] = unref(title);
21
- });
22
-
23
- // Clean up when the component is unmounted to prevent memory leaks
24
- onUnmounted(() => {
25
- delete pageTitles[path];
26
- });
27
- }
28
-
29
- /**
30
- * Gets the dynamically set page title for a given path.
31
- * For internal use by consumers like breadcrumb or document title utilities.
32
- * @param {string} path The route path to look up.
33
- * @returns {string | undefined}
34
- */
35
- export function getPageTitle(path) {
36
- return pageTitles[path];
37
- }