@ulu/frontend-vue 0.1.0-beta.2 → 0.1.0-beta.21

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 (148) hide show
  1. package/README.md +113 -2
  2. package/dist/{breakpoints-ClT9bfZm.js → breakpoints-DOXmgVoG.js} +1 -1
  3. package/dist/frontend-vue.css +1 -1
  4. package/dist/frontend-vue.js +75 -73
  5. package/dist/index-BpmkfeZb.js +6671 -0
  6. package/lib/components/collapsible/UluAccordion.vue +1 -1
  7. package/lib/components/collapsible/UluModal.vue +4 -5
  8. package/lib/components/collapsible/UluOverflowPopover.vue +1 -1
  9. package/lib/components/elements/UluAlert.vue +1 -2
  10. package/lib/components/elements/UluBadge.vue +27 -28
  11. package/lib/components/elements/UluBadgeStack.vue +8 -13
  12. package/lib/components/elements/UluButton.vue +2 -2
  13. package/lib/components/elements/UluButtonVerbose.vue +119 -0
  14. package/lib/components/elements/UluCard.vue +1 -1
  15. package/lib/components/elements/UluDefinitionList.vue +14 -17
  16. package/lib/components/elements/UluExternalLink.vue +22 -29
  17. package/lib/components/elements/UluIcon.vue +22 -17
  18. package/lib/components/elements/UluList.vue +53 -55
  19. package/lib/components/elements/UluSpokeSpinner.vue +12 -18
  20. package/lib/components/elements/UluTag.vue +35 -35
  21. package/lib/components/forms/UluCheckboxMenu.vue +32 -31
  22. package/lib/components/forms/UluFileDisplay.vue +40 -31
  23. package/lib/components/forms/UluFormFile.vue +22 -24
  24. package/lib/components/forms/UluFormMessage.vue +7 -10
  25. package/lib/components/forms/UluFormSelect.vue +16 -16
  26. package/lib/components/forms/UluFormText.vue +15 -15
  27. package/lib/components/forms/UluSearchForm.vue +8 -10
  28. package/lib/components/index.js +1 -1
  29. package/lib/components/layout/UluAdaptiveLayout.vue +3 -5
  30. package/lib/components/layout/UluTitleRail.vue +9 -5
  31. package/lib/components/layout/UluWhenBreakpoint.vue +71 -77
  32. package/lib/components/navigation/UluBreadcrumb.vue +1 -2
  33. package/lib/components/navigation/UluMenu.vue +3 -3
  34. package/lib/components/navigation/UluPager.vue +102 -0
  35. package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
  36. package/lib/components/systems/facets/UluFacetsFilters.vue +73 -0
  37. package/lib/components/systems/facets/UluFacetsList.vue +13 -14
  38. package/lib/components/systems/facets/UluFacetsResults.vue +57 -0
  39. package/lib/components/systems/facets/UluFacetsSearch.vue +26 -49
  40. package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +31 -0
  41. package/lib/components/systems/facets/UluFacetsSort.vue +45 -0
  42. package/lib/components/systems/facets/_facets.scss +2 -3
  43. package/lib/components/systems/facets/_mock-data.js +40 -0
  44. package/lib/components/systems/facets/useFacets.js +221 -0
  45. package/lib/components/systems/index.js +10 -2
  46. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +2 -1
  47. package/lib/components/systems/skeleton/UluShowSkeleton.vue +9 -8
  48. package/lib/components/systems/skeleton/UluSkeletonContent.vue +39 -43
  49. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +4 -6
  50. package/lib/components/systems/skeleton/UluSkeletonText.vue +27 -0
  51. package/lib/components/systems/slider/UluImageSlideShow.vue +1 -1
  52. package/lib/components/systems/slider/UluSlideShow.vue +8 -3
  53. package/lib/components/systems/table-sticky/UluTableSticky.vue +8 -8
  54. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +3 -3
  55. package/lib/composables/index.js +3 -1
  56. package/lib/composables/useDocumentTitle.js +47 -0
  57. package/lib/composables/usePageTitle.js +37 -0
  58. package/lib/composables/usePagination.js +122 -0
  59. package/lib/composables/useRequiredInject.js +26 -0
  60. package/lib/index.js +1 -1
  61. package/lib/meta.js +14 -0
  62. package/lib/plugins/core/index.js +91 -0
  63. package/lib/plugins/index.js +1 -0
  64. package/lib/plugins/toast/UluToast.vue +2 -2
  65. package/lib/utils/index.js +2 -0
  66. package/lib/utils/{vue-router.js → router.js} +106 -11
  67. package/package.json +37 -14
  68. package/types/components/index.d.ts +2 -0
  69. package/types/components/index.d.ts.map +1 -0
  70. package/types/components/systems/facets/_mock-data.d.ts +18 -0
  71. package/types/components/systems/facets/_mock-data.d.ts.map +1 -0
  72. package/types/components/systems/facets/useFacets.d.ts +39 -0
  73. package/types/components/systems/facets/useFacets.d.ts.map +1 -0
  74. package/types/components/systems/index.d.ts +2 -0
  75. package/types/components/systems/index.d.ts.map +1 -0
  76. package/types/components/systems/scroll-anchors/symbols.d.ts +7 -0
  77. package/types/components/systems/scroll-anchors/symbols.d.ts.map +1 -0
  78. package/types/composables/index.d.ts +7 -0
  79. package/types/composables/index.d.ts.map +1 -0
  80. package/types/composables/useBreakpointManager.d.ts +8 -0
  81. package/types/composables/useBreakpointManager.d.ts.map +1 -0
  82. package/types/composables/useDocumentTitle.d.ts +18 -0
  83. package/types/composables/useDocumentTitle.d.ts.map +1 -0
  84. package/types/composables/useIcon.d.ts +6 -0
  85. package/types/composables/useIcon.d.ts.map +1 -0
  86. package/types/composables/useModifiers.d.ts +69 -0
  87. package/types/composables/useModifiers.d.ts.map +1 -0
  88. package/types/composables/usePageTitle.d.ts +19 -0
  89. package/types/composables/usePageTitle.d.ts.map +1 -0
  90. package/types/composables/usePagination.d.ts +25 -0
  91. package/types/composables/usePagination.d.ts.map +1 -0
  92. package/types/composables/useRequiredInject.d.ts +8 -0
  93. package/types/composables/useRequiredInject.d.ts.map +1 -0
  94. package/types/composables/useWindowResize.d.ts +6 -0
  95. package/types/composables/useWindowResize.d.ts.map +1 -0
  96. package/types/index.d.ts +5 -0
  97. package/types/index.d.ts.map +1 -0
  98. package/types/meta.d.ts +10 -0
  99. package/types/meta.d.ts.map +1 -0
  100. package/types/plugins/breakpoints/index.d.ts +2 -0
  101. package/types/plugins/breakpoints/index.d.ts.map +1 -0
  102. package/types/plugins/core/index.d.ts +3 -0
  103. package/types/plugins/core/index.d.ts.map +1 -0
  104. package/types/plugins/index.d.ts +6 -0
  105. package/types/plugins/index.d.ts.map +1 -0
  106. package/types/plugins/modals/api.d.ts +34 -0
  107. package/types/plugins/modals/api.d.ts.map +1 -0
  108. package/types/plugins/modals/index.d.ts +28 -0
  109. package/types/plugins/modals/index.d.ts.map +1 -0
  110. package/types/plugins/modals/useModals.d.ts +2 -0
  111. package/types/plugins/modals/useModals.d.ts.map +1 -0
  112. package/types/plugins/popovers/defaults.d.ts +14 -0
  113. package/types/plugins/popovers/defaults.d.ts.map +1 -0
  114. package/types/plugins/popovers/directive.d.ts +8 -0
  115. package/types/plugins/popovers/directive.d.ts.map +1 -0
  116. package/types/plugins/popovers/index.d.ts +7 -0
  117. package/types/plugins/popovers/index.d.ts.map +1 -0
  118. package/types/plugins/popovers/manager.d.ts +52 -0
  119. package/types/plugins/popovers/manager.d.ts.map +1 -0
  120. package/types/plugins/popovers/useFollow.d.ts +31 -0
  121. package/types/plugins/popovers/useFollow.d.ts.map +1 -0
  122. package/types/plugins/popovers/utils.d.ts +2 -0
  123. package/types/plugins/popovers/utils.d.ts.map +1 -0
  124. package/types/plugins/toast/defaults.d.ts +15 -0
  125. package/types/plugins/toast/defaults.d.ts.map +1 -0
  126. package/types/plugins/toast/index.d.ts +5 -0
  127. package/types/plugins/toast/index.d.ts.map +1 -0
  128. package/types/plugins/toast/store.d.ts +22 -0
  129. package/types/plugins/toast/store.d.ts.map +1 -0
  130. package/types/plugins/toast/useToast.d.ts +2 -0
  131. package/types/plugins/toast/useToast.d.ts.map +1 -0
  132. package/types/utils/dom.d.ts +8 -0
  133. package/types/utils/dom.d.ts.map +1 -0
  134. package/types/utils/index.d.ts +3 -0
  135. package/types/utils/index.d.ts.map +1 -0
  136. package/types/utils/placeholder.d.ts +8 -0
  137. package/types/utils/placeholder.d.ts.map +1 -0
  138. package/types/utils/router.d.ts +141 -0
  139. package/types/utils/router.d.ts.map +1 -0
  140. package/types/utils/vue-router.d.ts +122 -0
  141. package/types/utils/vue-router.d.ts.map +1 -0
  142. package/dist/frontend-vue.umd.cjs +0 -561
  143. package/dist/index-P5Rwl_Dl.js +0 -7263
  144. package/lib/components/forms/UluFormDropzone.vue +0 -62
  145. package/lib/components/systems/facets/UluFacets.vue +0 -380
  146. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +0 -9
  147. package/lib/settings.js +0 -119
  148. 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
+ }
@@ -0,0 +1,26 @@
1
+ import { inject } from 'vue';
2
+ import { injectRegistry } from "../meta.js";
3
+
4
+ // A unique sentinel object to detect if a value was provided.
5
+ // This is used to differentiate between a provider not existing vs.
6
+ // a provider explicitly giving a `null` value.
7
+ const NOT_FOUND = {};
8
+
9
+ /**
10
+ * Injects a dependency from a plugin (or other required inject) and throws an error if it's not available.
11
+ *
12
+ * @param {string} key - The injection key (e.g., 'uluBreakpointManager').
13
+ * @returns The injected value.
14
+ */
15
+ export function useRequiredInject(key) {
16
+ const dependency = inject(key, NOT_FOUND);
17
+
18
+ if (dependency === NOT_FOUND) {
19
+ const plugin = injectRegistry[key] || '';
20
+ const pluginInfo = plugin ? ` from the '${ plugin }' plugin` : "";
21
+ const action = plugin ? "Please install missing plugin." : "";
22
+ throw new Error(`Required inject: '${ key }'${ pluginInfo } was not provided. ${ action }`);
23
+ }
24
+
25
+ return dependency;
26
+ }
package/lib/index.js CHANGED
@@ -7,4 +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 * from './settings.js';
10
+ export * as utils from './utils/index.js';
package/lib/meta.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module lib/meta
3
+ * @description A central lookup for meta like info in the system
4
+ */
5
+
6
+ export const injectRegistry = {
7
+ 'uluCore': 'Core',
8
+ 'uluIsMobile': 'Breakpoints',
9
+ 'uluBreakpointActive': 'Breakpoints',
10
+ 'uluBreakpointDirection': 'Breakpoints',
11
+ 'uluBreakpointManager': 'Breakpoints',
12
+ 'uluModals': 'Modals',
13
+ 'uluToast': 'Toast',
14
+ };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @module plugins/core/index.js
3
+ * @description Core plugin for managing shared configuration for the library.
4
+ */
5
+ import { reactive } from 'vue';
6
+
7
+ const defaults = {
8
+ fontAwesomeStatic: false,
9
+ iconComponent: null,
10
+ iconPropResolver: (definition) => ({ icon: definition }),
11
+ iconsByType: {
12
+ danger: "fas fa-triangle-exclamation",
13
+ warning: "fas fa-circle-exclamation",
14
+ info: "fas fa-circle-info",
15
+ success: "fas fa-circle-check",
16
+ externalLink: "fas fa-arrow-up-right-from-square",
17
+ close: "fas fa-xmark",
18
+ expand: "fas fa-plus",
19
+ collapse: "fas fa-minus",
20
+ resizeHorizontal: "fas fa-grip-lines-vertical",
21
+ resizeVertical: "fas fa-grip-lines",
22
+ resizeBoth: "fas fa-grip",
23
+ ellipsis: "fas fa-ellipsis",
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
+ }
30
+ };
31
+
32
+ export const iconKeys = Object.keys(defaults.iconsByType);
33
+
34
+ export default function install(app, userSettings = {}) {
35
+ // A single reactive object for all settings
36
+ const settings = reactive({ ...defaults });
37
+
38
+ // Separate icon overrides from other options to handle them safely
39
+ const { iconsByType: iconOverrides, ...otherOptions } = userSettings || {};
40
+
41
+ // Merge any user-provided options during installation
42
+ if (otherOptions) {
43
+ Object.assign(settings, otherOptions);
44
+ }
45
+
46
+ const api = {
47
+ // Methods to interact with settings
48
+ getSettings() {
49
+ return settings;
50
+ },
51
+ getDefaultSettings() {
52
+ return { ...defaults };
53
+ },
54
+ updateSettings(changes) {
55
+ return Object.assign(settings, changes);
56
+ },
57
+ getSetting(key) {
58
+ if (!settings.hasOwnProperty(key)) {
59
+ console.warn(`Attempted to access non-existent setting: ${key}`);
60
+ return;
61
+ }
62
+ return settings[key];
63
+ },
64
+ updateSetting(key, value) {
65
+ if (typeof key !== "string") {
66
+ throw new Error("Expected key to be string");
67
+ }
68
+ settings[key] = value;
69
+ },
70
+ getIcon(type) {
71
+ const icons = settings.iconsByType;
72
+ if (!icons[type]) {
73
+ throw new Error(`Icon type "${type}" not found!`);
74
+ }
75
+ return icons[type];
76
+ },
77
+ setIcon(type, definition) {
78
+ settings.iconsByType[type] = definition;
79
+ }
80
+ };
81
+
82
+ // Apply any individual icon overrides passed during installation
83
+ if (iconOverrides) {
84
+ for (const [type, definition] of Object.entries(iconOverrides)) {
85
+ api.setIcon(type, definition);
86
+ }
87
+ }
88
+
89
+ app.provide('uluCore', api);
90
+ app.config.globalProperties.$uluCore = api;
91
+ }
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
 
8
+ export { default as corePlugin } from './core/index.js';
8
9
  export { default as popoversPlugin } from './popovers/index.js';
9
10
  export { default as modalsPlugin } from './modals/index.js';
10
11
  export { default as toastPlugin } from './toast/index.js';
@@ -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">
@@ -43,7 +43,7 @@
43
43
  </button>
44
44
  </div>
45
45
  <button class="toast__close" :class="classes.closeButton" @click="toast.close">
46
- <UluIcon type="close"/>
46
+ <UluIcon :icon="'type:close'"/>
47
47
  </button>
48
48
  </div>
49
49
  </template>
@@ -0,0 +1,2 @@
1
+ export * as dom from './dom.js';
2
+ export * as router from './router.js';
@@ -1,7 +1,8 @@
1
+ import { getPageTitle } from "../composables/usePageTitle.js";
1
2
  /**
2
3
  * This Module Creates Menus from route or router config
3
4
  * - Note: Functions prefixed with "$" work with $route objects (running application, provided by vue-router ie $router, useRoute, etc),
4
- * @module utils/router-utils
5
+ * @module router-utils
5
6
  */
6
7
 
7
8
  /**
@@ -21,21 +22,56 @@
21
22
  */
22
23
  export function createBaseMenu(routes, options) {
23
24
  const defaults = {
24
- qualifier: isStaticBaseRoute,
25
+ qualifier(route, parentPath) {
26
+ if (!parentPath) {
27
+ return isStaticBaseRoute(route);
28
+ } else {
29
+ return isStaticRoute(route);
30
+ }
31
+ },
25
32
  sort: sortByWeight,
26
- item: {}
33
+ item: {},
34
+ includeChildren: false
27
35
  };
28
36
  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);
37
+ const getItemPath = (r, parentPath) => parentPath ? `${ parentPath }/${ r.path }` : r.path;
38
+ const toMenuItems = (currentRoutes, parentPath = null) => {
39
+ return currentRoutes
40
+ .filter(r => opts.qualifier(r, parentPath))
41
+ .map(r => {
42
+ // Need to grab meta from child but use the parent path
43
+ const menuRoute = r.children ? getChildIndexRoute(r.children) : r;
44
+ const children = r.children ? r.children.filter(child => child.path !== "") : false;
45
+ const item = createMenuItem(menuRoute, getItemPath(r, parentPath), opts.item);
46
+ if (opts.includeChildren && children.length) {
47
+ item.children = toMenuItems(children, item.path);
48
+ }
49
+ return item;
50
+ })
51
+ .sort(opts.sort);
52
+ };
53
+ return toMenuItems(routes);
37
54
  }
38
55
 
56
+ /**
57
+ * Returns menu flat (no parent children)
58
+ */
59
+ export function flattenMenu(menu) {
60
+ function flatten(items) {
61
+ const result = [];
62
+ for (const item of items) {
63
+ const newItem = { ...item };
64
+ delete newItem.children;
65
+ result.push(newItem);
66
+ if (item.children) {
67
+ result.push(...flatten(item.children));
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+ return flatten(menu);
73
+ }
74
+
39
75
  /**
40
76
  * Print out a section's menu based on path
41
77
  * @param {*} routes All routes
@@ -216,4 +252,63 @@ export function $createSectionMenu(route, options) {
216
252
  .filter(includeIndex(opts.includeIndex))
217
253
  .map(r => createMenuItem(r, `${ parent.path }/${ r.path }`, opts.item))
218
254
  .sort(opts.sort);
255
+ }
256
+
257
+ /**
258
+ * For a given $route, this will generate a breadcrumb trail.
259
+ * It iterates through `route.matched` to build the trail.
260
+ * - Prioritizes titles set via the `usePageTitle` composable for the current page.
261
+ * - Falls back to `meta.title` (string or function).
262
+ * - Skips routes where `meta.breadcrumb` is set to `false`.
263
+ * - Avoids duplicate crumbs for nested routes with empty paths.
264
+ * @param {Object} route The Vue Router `$route` object.
265
+ * @returns {Array.<{title: String, to: Object, current: Boolean}>} An array of breadcrumb items.
266
+ */
267
+ export function $createBreadcrumb(route) {
268
+ const { matched, path: currentPath } = route;
269
+ let prevPath;
270
+
271
+ const crumbs = matched.reduce((arr, match, index) => {
272
+ // Skip routes configured to be hidden from breadcrumbs
273
+ if (match.meta?.breadcrumb === false) {
274
+ return arr;
275
+ }
276
+
277
+ // Avoid duplicates from child routes with empty paths
278
+ if (match.path === prevPath) {
279
+ return arr;
280
+ }
281
+
282
+ 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';
302
+
303
+ arr.push({
304
+ title,
305
+ to: { path: isLast ? currentPath : match.path },
306
+ current: isLast,
307
+ });
308
+
309
+ prevPath = match.path;
310
+ return arr;
311
+ }, []);
312
+
313
+ return crumbs;
219
314
  }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@ulu/frontend-vue",
3
- "version": "0.1.0-beta.2",
4
- "description": "Frontend theming library (Vue)",
3
+ "version": "0.1.0-beta.21",
4
+ "description": "A modular and tree-shakeable Vue 3 component library for the Ulu frontend",
5
5
  "type": "module",
6
- "files" : [
6
+ "files": [
7
7
  "lib",
8
- "dist"
8
+ "dist",
9
+ "types"
9
10
  ],
10
11
  "module": "./lib/index.js",
11
12
  "exports": {
@@ -18,17 +19,36 @@
18
19
  "./*": "./lib/*",
19
20
  "./scss": "./lib/_index.scss"
20
21
  },
22
+ "typesVersions": {
23
+ "*": {
24
+ "lib/*": [
25
+ "./types/*"
26
+ ]
27
+ }
28
+ },
21
29
  "repository": {
22
30
  "type": "git",
23
31
  "url": "git+https://github.com/Jscherbe/frontend-vue.git"
24
32
  },
25
33
  "scripts": {
26
34
  "dev": "storybook dev -p 6006",
27
- "docs:build": "storybook build -o docs --docs",
35
+ "docs:build": "storybook build -o docs",
28
36
  "build": "vite build",
29
- "deploy": "npm run build && npm run docs:build"
37
+ "types": "npx tsc",
38
+ "deploy": "npm run types && npm run build && npm run docs:build",
39
+ "update-contexts": "rm -rf .contexts && mkdir -p .contexts/frontend && cp -R node_modules/@ulu/frontend/scss node_modules/@ulu/frontend/js .contexts/frontend/"
30
40
  },
31
- "keywords": [],
41
+ "keywords": [
42
+ "vue",
43
+ "vue3",
44
+ "ui",
45
+ "components",
46
+ "component-library",
47
+ "design-system",
48
+ "ulu",
49
+ "frontend",
50
+ "scss"
51
+ ],
32
52
  "author": "Joseph Scherben <jscherbe@gmail.com>",
33
53
  "license": "MIT",
34
54
  "bugs": {
@@ -37,29 +57,30 @@
37
57
  "homepage": "https://github.com/Jscherbe/frontend-vue#readme",
38
58
  "peerDependencies": {
39
59
  "@headlessui/vue": "^1.7.23",
40
- "@ulu/frontend": "^0.1.0-beta.94",
60
+ "@ulu/frontend": "^0.1.0-beta.102",
41
61
  "vue": "^3.5.17",
42
- "vue-router": "^4.5.1"
62
+ "vue-router": "^4.5.1",
63
+ "@unhead/vue": "^2.0.11"
43
64
  },
44
65
  "optionalDependencies": {
45
- "gsap": "^3.13.0",
46
66
  "fuse.js": "^6.6.2",
67
+ "gsap": "^3.13.0",
47
68
  "vue3-dropzone": "^2.2.1"
48
69
  },
49
70
  "dependencies": {
50
71
  "@floating-ui/vue": "^1.1.8",
51
72
  "@ulu/utils": "^0.0.30",
52
- "lodash" : "^4.17.21"
73
+ "lodash-es": "^4.17.21"
53
74
  },
54
75
  "devDependencies": {
55
76
  "@fortawesome/fontawesome-svg-core": "^6.7.2",
56
77
  "@fortawesome/free-solid-svg-icons": "^6.7.2",
57
78
  "@fortawesome/vue-fontawesome": "^3.0.8",
58
79
  "@storybook/addon-docs": "^9.1.1",
59
- "@storybook/addon-links": "^9.1.1",
60
80
  "@storybook/addon-essentials": "^9.0.0-alpha.12",
81
+ "@storybook/addon-links": "^9.1.1",
61
82
  "@storybook/vue3-vite": "^9.1.1",
62
- "@ulu/frontend": "^0.1.0-beta.94",
83
+ "@ulu/frontend": "^0.1.0-beta.102",
63
84
  "@vitejs/plugin-vue": "^6.0.0",
64
85
  "ollama": "^0.5.16",
65
86
  "react": "^19.1.1",
@@ -67,8 +88,10 @@
67
88
  "sass-embedded": "^1.89.2",
68
89
  "storybook": "^9.1.1",
69
90
  "storybook-addon-vue-mdx": "^2.0.2",
91
+ "typescript": "^5.3.3",
70
92
  "vite": "^7.0.0",
71
- "vue-router": "^4.5.1"
93
+ "vue-router": "^4.5.1",
94
+ "@unhead/vue": "^2.0.11"
72
95
  },
73
96
  "volta": {
74
97
  "node": "22.17.0"
@@ -0,0 +1,2 @@
1
+ export * from "./systems/index.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/components/index.js"],"names":[],"mappings":""}
@@ -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":"AAsCA;;;;;;;;;;;;;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;;;;;;;;;;;;;EAyKA"}
@@ -0,0 +1,2 @@
1
+ export { useFacets } from "./facets/useFacets.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../lib/components/systems/index.js"],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Symbol for register provide/inject
3
+ */
4
+ export const REGISTER: any;
5
+ export const UNREGISTER: any;
6
+ export const SECTIONS: any;
7
+ //# sourceMappingURL=symbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../../../lib/components/systems/scroll-anchors/symbols.js"],"names":[],"mappings":"AAAA;;GAEG;AACH,2BAAiC;AACjC,6BAAmC;AACnC,2BAAiC"}
@@ -0,0 +1,7 @@
1
+ export { useIcon } from "./useIcon.js";
2
+ export { useModifiers } from "./useModifiers.js";
3
+ export { useWindowResize } from "./useWindowResize.js";
4
+ export { useRequiredInject } from "./useRequiredInject.js";
5
+ export { useBreakpointManager } from "./useBreakpointManager.js";
6
+ export { usePagination } from "./usePagination.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/composables/index.js"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Use the CssBreakpoints module in Vue
3
+ * - Normally use only once, unless you have different sets of breakpoints
4
+ * @param {Object} options Configuration options overrides
5
+ * @return {Object} { manager, active, direction } (all are null in SSR environment until init)
6
+ */
7
+ export function useBreakpointManager(options: any): any;
8
+ //# sourceMappingURL=useBreakpointManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBreakpointManager.d.ts","sourceRoot":"","sources":["../../lib/composables/useBreakpointManager.js"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,wDAyCC"}