@ulu/frontend-vue 0.1.0-beta.9 → 0.1.1-beta.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.
Files changed (99) hide show
  1. package/dist/{breakpoints-BbkGNxxt.js → breakpoints-DM-CBTtb.js} +1 -1
  2. package/dist/frontend-vue.css +1 -1
  3. package/dist/frontend-vue.js +79 -68
  4. package/dist/index-BNRZ3Apw.js +7541 -0
  5. package/lib/components/collapsible/UluAccordion.vue +71 -53
  6. package/lib/components/collapsible/UluAccordionGroup.vue +54 -0
  7. package/lib/components/collapsible/UluCollapsible.vue +144 -0
  8. package/lib/components/collapsible/UluDropdown.vue +29 -29
  9. package/lib/components/collapsible/UluOverflowPopover.vue +1 -1
  10. package/lib/components/elements/UluBadge.vue +51 -28
  11. package/lib/components/elements/UluBadgeStack.vue +8 -13
  12. package/lib/components/elements/UluButtonVerbose.vue +119 -0
  13. package/lib/components/elements/UluCard.vue +1 -1
  14. package/lib/components/elements/UluDefinitionList.vue +14 -17
  15. package/lib/components/elements/UluExternalLink.vue +21 -27
  16. package/lib/components/elements/UluIcon.vue +11 -1
  17. package/lib/components/elements/UluList.vue +53 -55
  18. package/lib/components/elements/UluSpokeSpinner.vue +12 -18
  19. package/lib/components/elements/UluTag.vue +35 -35
  20. package/lib/components/forms/UluFileDisplay.vue +49 -31
  21. package/lib/components/forms/UluFormFile.vue +37 -24
  22. package/lib/components/forms/UluFormMessage.vue +13 -10
  23. package/lib/components/forms/UluFormSelect.vue +28 -16
  24. package/lib/components/forms/UluFormText.vue +24 -15
  25. package/lib/components/forms/UluSearchForm.vue +11 -10
  26. package/lib/components/forms/UluSelectableMenu.vue +99 -0
  27. package/lib/components/index.js +4 -3
  28. package/lib/components/layout/UluTitleRail.vue +18 -0
  29. package/lib/components/layout/UluWhenBreakpoint.vue +9 -0
  30. package/lib/components/navigation/UluBreadcrumb.vue +9 -2
  31. package/lib/components/navigation/UluMenu.vue +8 -3
  32. package/lib/components/navigation/UluMenuStack.vue +3 -1
  33. package/lib/components/navigation/UluPager.vue +102 -0
  34. package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
  35. package/lib/components/systems/facets/UluFacetsFilterLists.vue +91 -0
  36. package/lib/components/systems/facets/UluFacetsFilterPopovers.vue +125 -0
  37. package/lib/components/systems/facets/UluFacetsFilterSelects.vue +71 -0
  38. package/lib/components/systems/facets/UluFacetsHeaderLayout.vue +24 -0
  39. package/lib/components/systems/facets/UluFacetsList.vue +62 -34
  40. package/lib/components/systems/facets/UluFacetsResults.vue +63 -0
  41. package/lib/components/systems/facets/UluFacetsSearch.vue +27 -50
  42. package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +70 -0
  43. package/lib/components/systems/facets/UluFacetsSort.vue +45 -0
  44. package/lib/components/systems/facets/_facets.scss +2 -3
  45. package/lib/components/systems/facets/_mock-data.js +40 -0
  46. package/lib/components/systems/facets/useFacets.js +268 -0
  47. package/lib/components/systems/index.js +13 -2
  48. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +2 -1
  49. package/lib/components/systems/skeleton/UluShowSkeleton.vue +9 -8
  50. package/lib/components/systems/skeleton/UluSkeletonContent.vue +39 -43
  51. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +4 -6
  52. package/lib/components/systems/skeleton/UluSkeletonText.vue +27 -0
  53. package/lib/components/systems/slider/UluImageSlideShow.vue +1 -1
  54. package/lib/components/systems/slider/UluSlideShow.vue +8 -3
  55. package/lib/components/systems/table-sticky/UluTableSticky.vue +7 -7
  56. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +3 -3
  57. package/lib/components/visualizations/UluAnimateNumber.vue +7 -1
  58. package/lib/components/visualizations/UluProgressBar.vue +99 -68
  59. package/lib/components/visualizations/UluProgressCircle.vue +146 -0
  60. package/lib/components/visualizations/progress-bar-examples.html +175 -0
  61. package/lib/composables/index.js +3 -1
  62. package/lib/composables/useDocumentTitle.js +61 -0
  63. package/lib/composables/usePagination.js +122 -0
  64. package/lib/index.js +1 -0
  65. package/lib/plugins/core/index.js +6 -1
  66. package/lib/plugins/popovers/UluPopover.vue +8 -3
  67. package/lib/plugins/toast/UluToast.vue +1 -1
  68. package/lib/plugins/toast/UluToastDisplay.vue +19 -2
  69. package/lib/utils/dom.js +12 -0
  70. package/lib/utils/index.js +2 -0
  71. package/lib/utils/{vue-router.js → router.js} +114 -30
  72. package/package.json +17 -11
  73. package/types/components/systems/facets/_mock-data.d.ts +18 -0
  74. package/types/components/systems/facets/_mock-data.d.ts.map +1 -0
  75. package/types/components/systems/facets/useFacets.d.ts +39 -0
  76. package/types/components/systems/facets/useFacets.d.ts.map +1 -0
  77. package/types/components/systems/index.d.ts +1 -1
  78. package/types/composables/index.d.ts +2 -0
  79. package/types/composables/useDocumentTitle.d.ts +22 -0
  80. package/types/composables/useDocumentTitle.d.ts.map +1 -0
  81. package/types/composables/usePageTitle.d.ts +19 -0
  82. package/types/composables/usePageTitle.d.ts.map +1 -0
  83. package/types/composables/usePagination.d.ts +25 -0
  84. package/types/composables/usePagination.d.ts.map +1 -0
  85. package/types/index.d.ts +1 -0
  86. package/types/plugins/core/index.d.ts.map +1 -1
  87. package/types/utils/dom.d.ts +1 -0
  88. package/types/utils/dom.d.ts.map +1 -1
  89. package/types/utils/index.d.ts +3 -0
  90. package/types/utils/index.d.ts.map +1 -0
  91. package/types/utils/router.d.ts +144 -0
  92. package/types/utils/router.d.ts.map +1 -0
  93. package/dist/index-D3Uc6T5M.js +0 -6469
  94. package/lib/components/collapsible/UluCollapsibleRegion.vue +0 -278
  95. package/lib/components/forms/UluCheckboxMenu.vue +0 -36
  96. package/lib/components/systems/facets/UluFacets.vue +0 -380
  97. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +0 -9
  98. package/lib/components/visualizations/UluProgressDonut.vue +0 -97
  99. package/lib/utils/placeholder.js +0 -6
@@ -0,0 +1,122 @@
1
+ import { computed, watch } from "vue";
2
+ import { useRoute, useRouter } from "vue-router";
3
+
4
+ /**
5
+ * A Vue composable for handling pagination logic.
6
+ * It interacts with vue-router to keep the current page in the URL query string.
7
+ *
8
+ * @param {import('vue').Ref<Array<any>>} items - A ref containing the full list of items to be paginated.
9
+ * @param {number} itemsPerPage - The number of items to display per page.
10
+ * @returns {{
11
+ * currentPage: import('vue').ComputedRef<number>,
12
+ * totalPages: import('vue').ComputedRef<number>,
13
+ * paginatedItems: import('vue').ComputedRef<Array<any>>,
14
+ * pagerItems: import('vue').ComputedRef<object|null>,
15
+ * pagerEllipses: import('vue').ComputedRef<{previous: boolean, next: boolean}>
16
+ * }} - An object containing reactive properties for pagination.
17
+ */
18
+ export function usePagination(items, itemsPerPage) {
19
+ const route = useRoute();
20
+ const router = useRouter();
21
+
22
+ const currentPage = computed(() => {
23
+ const page = parseInt(route.query.page || '1', 10);
24
+ return isNaN(page) || page < 1 ? 1 : page;
25
+ });
26
+
27
+ const totalPages = computed(() => {
28
+ if (!items.value || items.value.length === 0) return 1;
29
+ return Math.ceil(items.value.length / itemsPerPage);
30
+ });
31
+
32
+ watch(totalPages, (newTotalPages) => {
33
+ if (currentPage.value > newTotalPages) {
34
+ router.push({ query: { ...route.query, page: newTotalPages } });
35
+ }
36
+ });
37
+
38
+ const paginatedItems = computed(() => {
39
+ const start = (currentPage.value - 1) * itemsPerPage;
40
+ const end = start + itemsPerPage;
41
+ return items.value.slice(start, end);
42
+ });
43
+
44
+ const pagerItems = computed(() => {
45
+ if (totalPages.value <= 1) {
46
+ return null;
47
+ }
48
+
49
+ const items = {
50
+ pages: {}
51
+ };
52
+ const page = currentPage.value;
53
+ const total = totalPages.value;
54
+ const maxPagesToShow = 5;
55
+
56
+ const createRoute = (p) => {
57
+ return { query: { ...route.query, page: p } };
58
+ };
59
+
60
+ if (page > 1) {
61
+ items.first = { href: createRoute(1) };
62
+ items.previous = { href: createRoute(page - 1) };
63
+ }
64
+
65
+ if (page < total) {
66
+ items.next = { href: createRoute(page + 1) };
67
+ items.last = { href: createRoute(total) };
68
+ }
69
+
70
+ let startPage, endPage;
71
+ if (total <= maxPagesToShow) {
72
+ startPage = 1;
73
+ endPage = total;
74
+ } else {
75
+ const maxPagesBeforeCurrent = Math.floor(maxPagesToShow / 2);
76
+ const maxPagesAfterCurrent = Math.ceil(maxPagesToShow / 2) - 1;
77
+ if (page <= maxPagesBeforeCurrent) {
78
+ startPage = 1;
79
+ endPage = maxPagesToShow;
80
+ } else if (page + maxPagesAfterCurrent >= total) {
81
+ startPage = total - maxPagesToShow + 1;
82
+ endPage = total;
83
+ } else {
84
+ startPage = page - maxPagesBeforeCurrent;
85
+ endPage = page + maxPagesAfterCurrent;
86
+ }
87
+ }
88
+
89
+ for (let i = startPage; i <= endPage; i++) {
90
+ items.pages[i] = { href: createRoute(i) };
91
+ }
92
+
93
+ return items;
94
+ });
95
+
96
+ const pagerEllipses = computed(() => {
97
+ const ellipses = { previous: false, next: false };
98
+ if (!pagerItems.value || !pagerItems.value.pages) return ellipses;
99
+
100
+ const pageKeys = Object.keys(pagerItems.value.pages).map(Number);
101
+ if (pageKeys.length === 0) return ellipses;
102
+
103
+ const firstPageInPager = Math.min(...pageKeys);
104
+ const lastPageInPager = Math.max(...pageKeys);
105
+
106
+ if (firstPageInPager > 1) {
107
+ ellipses.previous = true;
108
+ }
109
+ if (lastPageInPager < totalPages.value) {
110
+ ellipses.next = true;
111
+ }
112
+ return ellipses;
113
+ });
114
+
115
+ return {
116
+ currentPage,
117
+ totalPages,
118
+ paginatedItems,
119
+ pagerItems,
120
+ pagerEllipses
121
+ };
122
+ }
package/lib/index.js CHANGED
@@ -7,3 +7,4 @@
7
7
  export * from './plugins/index.js';
8
8
  export * from './components/index.js';
9
9
  export * from './composables/index.js';
10
+ export * as utils from './utils/index.js';
@@ -21,7 +21,12 @@ const defaults = {
21
21
  resizeVertical: "fas fa-grip-lines",
22
22
  resizeBoth: "fas fa-grip",
23
23
  ellipsis: "fas fa-ellipsis",
24
- pathSeparator: "fas fa-chevron-right"
24
+ pathSeparator: "fas fa-chevron-right",
25
+ image: "fas fa-image",
26
+ file: "fas fa-file",
27
+ next: "fas fa-chevron-left",
28
+ previous: "fas fa-chevron-right",
29
+ dropdownExpand: "fas fa-caret-down"
25
30
  }
26
31
  };
27
32
 
@@ -4,6 +4,7 @@
4
4
  type="button"
5
5
  ref="trigger"
6
6
  @click="toggle"
7
+ :id="triggerId"
7
8
  :disabled="disabled"
8
9
  :class="[
9
10
  { [activeClass] : isOpen },
@@ -14,7 +15,9 @@
14
15
  :aria-label="triggerAlt"
15
16
  v-ulu-tooltip="tooltip ? tooltip : null"
16
17
  >
17
- <slot name="trigger" :isOpen="isOpen"/>
18
+ <slot name="trigger" :isOpen="isOpen" :toggle="toggle" :close="close">
19
+ {{ triggerText }}
20
+ </slot>
18
21
  </button>
19
22
  <span
20
23
  class="popover"
@@ -27,7 +30,7 @@
27
30
  },
28
31
  classes.content,
29
32
  ]"
30
- :aria-hidden="isOpen ? 'false' : 'true'"
33
+ :aria-labelledby="triggerId"
31
34
  :id="id"
32
35
  :style="floatingStyles"
33
36
  :data-placement="placement"
@@ -35,7 +38,7 @@
35
38
  tabindex="-1"
36
39
  >
37
40
  <span class="popover__inner">
38
- <slot name="content" :close="close"/>
41
+ <slot :isOpen="isOpen" :toggle="toggle" :close="close"/>
39
42
  </span>
40
43
  <span v-if="$slots.footer" class="popover__footer">
41
44
  <slot name="footer" :close="close"/>
@@ -65,6 +68,7 @@
65
68
 
66
69
  const emit = defineEmits(["toggle"]);
67
70
  const props = defineProps({
71
+ triggerText: String,
68
72
  triggerAlt: String,
69
73
  disabled: Boolean,
70
74
  tooltip: String,
@@ -95,6 +99,7 @@
95
99
  });
96
100
 
97
101
  const id = newUid();
102
+ const triggerId = newUid();
98
103
  const config = Object.assign({}, defaults.popover, props.config);
99
104
  const isOpen = ref(props.startOpen || false);
100
105
  const trigger = ref(null);
@@ -13,7 +13,7 @@
13
13
  >
14
14
  <div v-if="toast.icon || $slots.icon" class="toast__icon" :class="classes.icon">
15
15
  <slot name="icon" :toast="toast">
16
- <FaIcon v-if="toast.icon" :icon="toast.icon"/>
16
+ <UluIcon v-if="toast.icon" :icon="toast.icon"/>
17
17
  </slot>
18
18
  </div>
19
19
  <div class="toast__content" :class="classes.content">
@@ -3,7 +3,7 @@
3
3
  <TransitionGroup
4
4
  class="toast-container"
5
5
  :class="classes"
6
- name="toast"
6
+ name="toast-animation"
7
7
  tag="div"
8
8
  >
9
9
  <component
@@ -32,4 +32,21 @@
32
32
  }
33
33
  }
34
34
  }
35
- </script>
35
+ </script>
36
+
37
+ <style lang="css">
38
+ .toast-animation-move,
39
+ .toast-animation-enter-active,
40
+ .toast-animation-leave-active {
41
+ transition: all 0.3s ease;
42
+ }
43
+ .toast-animation-enter-from,
44
+ .toast-animation-leave-to {
45
+ opacity: 0;
46
+ transform: translateX(30px);
47
+ }
48
+ .toast-animation-leave-active {
49
+ position: absolute;
50
+ width: 100%;
51
+ }
52
+ </style>
package/lib/utils/dom.js CHANGED
@@ -11,4 +11,16 @@ export function refToElement(value) {
11
11
  } else if (typeof value === 'object' && '$el' in value) {
12
12
  return value.$el;
13
13
  }
14
+ }
15
+
16
+
17
+ let idCounter = 0;
18
+
19
+ export function newId(prefix = "ulu-id") {
20
+ const id = `${ prefix }-${ ++idCounter }`;
21
+ if (typeof document !== 'undefined' && document.getElementById(id)) {
22
+ // In the unlikely event of a collision (e.g., SSR), recurse.
23
+ return generateUid(prefix);
24
+ }
25
+ return id;
14
26
  }
@@ -0,0 +1,2 @@
1
+ export * as dom from './dom.js';
2
+ export * as router from './router.js';
@@ -1,9 +1,29 @@
1
1
  /**
2
2
  * This Module Creates Menus from route or router config
3
3
  * - Note: Functions prefixed with "$" work with $route objects (running application, provided by vue-router ie $router, useRoute, etc),
4
- * @module utils/router-utils
4
+ * @module router-utils
5
5
  */
6
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
+
7
27
  /**
8
28
  * Route Menu Item
9
29
  * @typedef {Object} RouteMenuItem
@@ -21,21 +41,55 @@
21
41
  */
22
42
  export function createBaseMenu(routes, options) {
23
43
  const defaults = {
24
- qualifier: isStaticBaseRoute,
44
+ qualifier(route, parentPath) {
45
+ if (!parentPath) {
46
+ return isStaticBaseRoute(route);
47
+ } else {
48
+ return isStaticRoute(route);
49
+ }
50
+ },
25
51
  sort: sortByWeight,
26
- item: {}
52
+ item: {},
53
+ includeChildren: false
27
54
  };
28
55
  const opts = Object.assign({}, defaults, options);
29
- return routes
30
- .filter(opts.qualifier)
31
- .map(r => {
32
- // Need to grab meta from child but use the parent path
33
- const menuRoute = r.children ? getChildIndexRoute(r.children) : r;
34
- return createMenuItem(menuRoute, r.path, opts.item);
35
- })
36
- .sort(opts.sort);
56
+ const getItemPath = (r, parentPath) => parentPath ? `${ parentPath }/${ r.path }` : r.path;
57
+ const toMenuItems = (currentRoutes, parentPath = null) => {
58
+ return currentRoutes
59
+ .filter(r => opts.qualifier(r, parentPath))
60
+ .map(r => {
61
+ const menuRoute = r.children ? getChildIndexRoute(r.children) : r;
62
+ const children = r.children ? r.children.filter(child => child.path !== "") : false;
63
+ const item = createMenuItem(menuRoute, getItemPath(r, parentPath), opts.item);
64
+ if (opts.includeChildren && children.length) {
65
+ item.children = toMenuItems(children, item.path);
66
+ }
67
+ return item;
68
+ })
69
+ .sort(opts.sort);
70
+ };
71
+ return toMenuItems(routes);
37
72
  }
38
73
 
74
+ /**
75
+ * Returns menu flat (no parent children)
76
+ */
77
+ export function flattenMenu(menu) {
78
+ function flatten(items) {
79
+ const result = [];
80
+ for (const item of items) {
81
+ const newItem = { ...item };
82
+ delete newItem.children;
83
+ result.push(newItem);
84
+ if (item.children) {
85
+ result.push(...flatten(item.children));
86
+ }
87
+ }
88
+ return result;
89
+ }
90
+ return flatten(menu);
91
+ }
92
+
39
93
  /**
40
94
  * Print out a section's menu based on path
41
95
  * @param {*} routes All routes
@@ -53,7 +107,6 @@ export function createSectionMenu(routes, sectionPath, options) {
53
107
  };
54
108
  const opts = Object.assign({}, defaults, options);
55
109
  const base = routes.find(r => r.path !== "/" && sectionPath.includes(r.path));
56
- // Go through each item and
57
110
  const getSection = (current, previous, path) => {
58
111
  if (current.children) {
59
112
  const child = current.children.find(c => c.path.includes(sectionPath));
@@ -74,20 +127,6 @@ export function createSectionMenu(routes, sectionPath, options) {
74
127
  .sort(opts.sort);
75
128
  }
76
129
 
77
- /**
78
- * For a given route this will return the route that renders. For routes without
79
- * children this is the route itself for those with children (first child with empty path)
80
- * @param {Object} route Route object to resolve
81
- * @returns {Object} Resolved route
82
- */
83
- // export function resolveRouteIndex(route) {
84
- // if (route.children) {
85
- // return getChildIndexRoute(route.children);
86
- // } else {
87
- // return route;
88
- // }
89
- // }
90
-
91
130
  /**
92
131
  * For a given array of child routes return the index
93
132
  * @param {Array} children Children array of routes
@@ -96,6 +135,7 @@ export function createSectionMenu(routes, sectionPath, options) {
96
135
  export function getChildIndexRoute(children) {
97
136
  return children.find(r => r.path === "");
98
137
  }
138
+
99
139
  /**
100
140
  * Creates common menu item structure from route, pulls title and weight from meta (on route or index child)
101
141
  * @param {Object} route Route
@@ -115,9 +155,10 @@ export function createMenuItem(route, routePath = route.path, options) {
115
155
  if (opts.indexMeta && route.children) {
116
156
  meta = Object.assign({}, meta, getChildIndexRoute(route.children)?.meta);
117
157
  }
158
+ const routeWithMergedMeta = { ...route, meta };
118
159
  const item = {
119
160
  path: routePath,
120
- title: meta?.title || "Missing Title",
161
+ title: getRouteTitle(routeWithMergedMeta, route) || "Missing Title",
121
162
  weight: meta?.weight || 0,
122
163
  meta
123
164
  };
@@ -126,23 +167,26 @@ export function createMenuItem(route, routePath = route.path, options) {
126
167
  }
127
168
  return item;
128
169
  }
170
+
129
171
  /**
130
- * Test if route is static (doesn't incude parameters)
172
+ * Test if route is static (doesn't include parameters)
131
173
  * @param {Object} route Route object to test
132
174
  * @returns {Boolean} Whether or not this route is static (not dynamic)
133
175
  */
134
176
  export function isStaticRoute(route) {
135
177
  return !route.path.includes("/:");
136
178
  }
179
+
137
180
  /**
138
181
  *
139
182
  * @param {Object} route Route object to test
140
183
  * @returns {Boolean} Whether or not this route is a static base route
141
184
  */
142
185
  export function isStaticBaseRoute(route) {
143
- const matches = route.path.match(/\//) || [];
186
+ const matches = route.path.match(/\//g) || [];
144
187
  return isStaticRoute(route) && matches.length === 1;
145
188
  }
189
+
146
190
  /**
147
191
  * Function to make normal <a> behave as router links instread of page reload
148
192
  * @param {Object} router Router instance (ie src/router) to push routes to
@@ -159,6 +203,7 @@ export function nativeLinkRouter(router, event) {
159
203
  }
160
204
  }
161
205
  }
206
+
162
207
  /**
163
208
  * Returns the child routes for base route
164
209
  * @param {Object} route Route Object
@@ -167,6 +212,7 @@ export function nativeLinkRouter(router, event) {
167
212
  export function $getRouteChildren(route, parent = $getParentRoute(route)) {
168
213
  return parent?.children;
169
214
  }
215
+
170
216
  /**
171
217
  * Returns the route's parent
172
218
  * @param {Object} route Route Object
@@ -216,4 +262,42 @@ export function $createSectionMenu(route, options) {
216
262
  .filter(includeIndex(opts.includeIndex))
217
263
  .map(r => createMenuItem(r, `${ parent.path }/${ r.path }`, opts.item))
218
264
  .sort(opts.sort);
219
- }
265
+ }
266
+
267
+ /**
268
+ * For a given $route, this will generate a breadcrumb trail.
269
+ * It iterates through `route.matched` to build the trail.
270
+ * - Falls back to `meta.title` (string or function).
271
+ * - Skips routes where `meta.breadcrumb` is set to `false`.
272
+ * - Avoids duplicate crumbs for nested routes with empty paths.
273
+ * @param {Object} route The Vue Router `$route` object.
274
+ * @returns {Array.<{title: String, to: Object, current: Boolean}>} An array of breadcrumb items.
275
+ */
276
+ export function $createBreadcrumb(route) {
277
+ const { matched, path: currentPath } = route;
278
+ let prevPath;
279
+
280
+ const crumbs = matched.reduce((arr, match, index) => {
281
+ if (match.meta?.breadcrumb === false) {
282
+ return arr;
283
+ }
284
+
285
+ if (match.path === prevPath) {
286
+ return arr;
287
+ }
288
+
289
+ const isLast = index === matched.length - 1;
290
+ const title = getRouteTitle(match, route) || "Missing Title";
291
+
292
+ arr.push({
293
+ title,
294
+ to: { path: isLast ? currentPath : match.path },
295
+ current: isLast,
296
+ });
297
+
298
+ prevPath = match.path;
299
+ return arr;
300
+ }, []);
301
+
302
+ return crumbs;
303
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend-vue",
3
- "version": "0.1.0-beta.9",
3
+ "version": "0.1.1-beta.1",
4
4
  "description": "A modular and tree-shakeable Vue 3 component library for the Ulu frontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -11,7 +11,8 @@
11
11
  "module": "./lib/index.js",
12
12
  "exports": {
13
13
  ".": {
14
- "import": "./lib/index.js"
14
+ "import": "./lib/index.js",
15
+ "types": "./types/index.d.ts"
15
16
  },
16
17
  "./dist": {
17
18
  "import": "./dist/frontend-vue.js"
@@ -19,9 +20,10 @@
19
20
  "./*": "./lib/*",
20
21
  "./scss": "./lib/_index.scss"
21
22
  },
23
+ "types": "./types/index.d.ts",
22
24
  "typesVersions": {
23
25
  "*": {
24
- "lib/*": [
26
+ "*": [
25
27
  "./types/*"
26
28
  ]
27
29
  }
@@ -35,7 +37,8 @@
35
37
  "docs:build": "storybook build -o docs",
36
38
  "build": "vite build",
37
39
  "types": "npx tsc",
38
- "deploy": "npm run types && npm run build && npm run docs:build"
40
+ "deploy": "npm run types && npm run build && npm run docs:build",
41
+ "update-contexts": "rm -rf .ctx && mkdir -p .ctx/frontend && cp -R node_modules/@ulu/frontend/scss node_modules/@ulu/frontend/js .ctx/frontend/"
39
42
  },
40
43
  "keywords": [
41
44
  "vue",
@@ -56,13 +59,15 @@
56
59
  "homepage": "https://github.com/Jscherbe/frontend-vue#readme",
57
60
  "peerDependencies": {
58
61
  "@headlessui/vue": "^1.7.23",
59
- "@ulu/frontend": "^0.1.0-beta.94",
62
+ "@ulu/frontend": "^0.1.0-beta.111",
63
+ "@unhead/vue": "^2.0.11",
60
64
  "vue": "^3.5.17",
61
- "vue-router": "^4.5.1"
65
+ "vue-router": "^4.5.1",
66
+ "@formkit/auto-animate" : "^0.9.0"
62
67
  },
63
68
  "optionalDependencies": {
64
- "gsap": "^3.13.0",
65
69
  "fuse.js": "^6.6.2",
70
+ "gsap": "^3.13.0",
66
71
  "vue3-dropzone": "^2.2.1"
67
72
  },
68
73
  "dependencies": {
@@ -75,10 +80,11 @@
75
80
  "@fortawesome/free-solid-svg-icons": "^6.7.2",
76
81
  "@fortawesome/vue-fontawesome": "^3.0.8",
77
82
  "@storybook/addon-docs": "^9.1.1",
78
- "@storybook/addon-links": "^9.1.1",
79
83
  "@storybook/addon-essentials": "^9.0.0-alpha.12",
84
+ "@storybook/addon-links": "^9.1.1",
80
85
  "@storybook/vue3-vite": "^9.1.1",
81
- "@ulu/frontend": "^0.1.0-beta.94",
86
+ "@ulu/frontend": "^0.1.0-beta.111",
87
+ "@unhead/vue": "^2.0.11",
82
88
  "@vitejs/plugin-vue": "^6.0.0",
83
89
  "ollama": "^0.5.16",
84
90
  "react": "^19.1.1",
@@ -86,12 +92,12 @@
86
92
  "sass-embedded": "^1.89.2",
87
93
  "storybook": "^9.1.1",
88
94
  "storybook-addon-vue-mdx": "^2.0.2",
95
+ "typescript": "^5.3.3",
89
96
  "vite": "^7.0.0",
90
97
  "vue-router": "^4.5.1",
91
- "typescript": "^5.3.3"
98
+ "@formkit/auto-animate" : "^0.9.0"
92
99
  },
93
100
  "volta": {
94
101
  "node": "22.17.0"
95
102
  }
96
103
  }
97
-
@@ -0,0 +1,18 @@
1
+ export const initialMockFacets: {
2
+ name: string;
3
+ uid: string;
4
+ open: boolean;
5
+ children: {
6
+ uid: string;
7
+ label: string;
8
+ }[];
9
+ }[];
10
+ export const mockItems: {
11
+ id: number;
12
+ title: string;
13
+ description: string;
14
+ category: string[];
15
+ author: string[];
16
+ date: Date;
17
+ }[];
18
+ //# sourceMappingURL=_mock-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_mock-data.d.ts","sourceRoot":"","sources":["../../../../lib/components/systems/facets/_mock-data.js"],"names":[],"mappings":"AAAA;;;;;;;;IAwBE;AAEF;;;;;;;IAaE"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * A composable for handling client-side faceted search, filtering, and sorting.
3
+ * @param {import('vue').Ref<Array<Object>>} allItems - A Vue ref containing the full list of items to be processed.
4
+ * @param {Object} options - Configuration options for the composable.
5
+ * @param {Array} [options.initialFacets] - The initial configuration for the facets. Can be generated automatically if `facetFields` is provided.
6
+ * @param {Array} [options.facetFields] - A simpler configuration to automatically generate facets from items. Each item can have `uid`, `name`, `open`, `getValue` and `getLabel`.
7
+ * @param {String} [options.initialSearchValue=''] - The initial value for the search input.
8
+ * @param {String} [options.initialSortType='az'] - The initial sort type.
9
+ * @param {Boolean} [options.noDefaultSorts=false] - If true, the default 'A-Z' and 'Z-A' sorts will not be included.
10
+ * @param {Object} [options.extraSortTypes={}] - Additional sort types to be merged with the default ones.
11
+ * @param {Object} [options.searchOptions={}] - Configuration options for Fuse.js.
12
+ * @param {Function} [options.getItemFacet] - A function to retrieve facet information from an item. Should always return an array of values.
13
+ * @param {Function} [options.getSortValue] - A function to get the value to sort by from an item.
14
+ */
15
+ export function useFacets(allItems: import("vue").Ref<Array<any>>, options?: {
16
+ initialFacets?: any[];
17
+ facetFields?: any[];
18
+ initialSearchValue?: string;
19
+ initialSortType?: string;
20
+ noDefaultSorts?: boolean;
21
+ extraSortTypes?: any;
22
+ searchOptions?: any;
23
+ getItemFacet?: Function;
24
+ getSortValue?: Function;
25
+ }): {
26
+ facets: import("vue").Ref<any, any>;
27
+ searchValue: import("vue").Ref<string, string>;
28
+ selectedSort: import("vue").Ref<string, string>;
29
+ sortTypes: import("vue").ComputedRef<any>;
30
+ displayItems: import("vue").ComputedRef<any>;
31
+ selectedFacets: import("vue").ComputedRef<any[]>;
32
+ clearFilters: () => void;
33
+ handleFacetChange: ({ groupUid, facetUid, selected }: {
34
+ groupUid: any;
35
+ facetUid: any;
36
+ selected: any;
37
+ }) => void;
38
+ };
39
+ //# sourceMappingURL=useFacets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFacets.d.ts","sourceRoot":"","sources":["../../../../lib/components/systems/facets/useFacets.js"],"names":[],"mappings":"AA2CA;;;;;;;;;;;;;GAaG;AACH,oCAZW,OAAO,KAAK,EAAE,GAAG,CAAC,KAAK,KAAQ,CAAC,YAExC;IAAwB,aAAa;IACb,WAAW;IACV,kBAAkB;IAClB,eAAe;IACd,cAAc;IACf,cAAc;IACd,aAAa;IACX,YAAY;IACZ,YAAY;CACzC;;;;;;;;;;;;;EAmNA"}
@@ -1,2 +1,2 @@
1
- export {};
1
+ export { useFacets } from "./facets/useFacets.js";
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -3,4 +3,6 @@ export { useModifiers } from "./useModifiers.js";
3
3
  export { useWindowResize } from "./useWindowResize.js";
4
4
  export { useRequiredInject } from "./useRequiredInject.js";
5
5
  export { useBreakpointManager } from "./useBreakpointManager.js";
6
+ export { usePagination } from "./usePagination.js";
7
+ export { useDocumentTitle } from "./useDocumentTitle.js";
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A composable to manage the document title.
3
+ *
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.
9
+ *
10
+ * @param {object} options
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.
15
+ */
16
+ export function useDocumentTitle(options?: {
17
+ title?: import("vue").Ref<string> | string;
18
+ titleTemplate?: string;
19
+ useRoute?: Function;
20
+ useHead?: Function;
21
+ }): void;
22
+ //# sourceMappingURL=useDocumentTitle.d.ts.map