@ulu/frontend-vue 0.1.1-beta.1 → 0.1.1-beta.11

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 (217) hide show
  1. package/dist/{breakpoints-DM-CBTtb.js → breakpoints-CE1jJcpB.js} +1 -1
  2. package/dist/frontend-vue.css +1 -1
  3. package/dist/frontend-vue.js +65 -64
  4. package/dist/{index-BNRZ3Apw.js → index-QwwTSMNO.js} +1955 -1737
  5. package/lib/components/collapsible/UluAccordion.vue +9 -9
  6. package/lib/components/collapsible/UluCollapsible.vue +7 -7
  7. package/lib/components/forms/UluSelectableMenu.vue +8 -1
  8. package/lib/components/navigation/UluMenu.vue +5 -3
  9. package/lib/components/navigation/UluMenuStack.vue +47 -30
  10. package/lib/components/systems/facets/UluFacetsActiveFilters.vue +84 -0
  11. package/lib/components/systems/facets/UluFacetsFilterLists.vue +9 -3
  12. package/lib/components/systems/facets/UluFacetsList.vue +2 -0
  13. package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +32 -12
  14. package/lib/components/systems/facets/useFacets.js +166 -40
  15. package/lib/components/systems/index.js +1 -0
  16. package/lib/components/systems/slider/UluSlideShow.vue +2 -2
  17. package/lib/components/visualizations/UluProgressBar.vue +57 -14
  18. package/lib/components/visualizations/UluProgressCircle.vue +124 -111
  19. package/lib/plugins/core/index.js +2 -2
  20. package/lib/plugins/popovers/UluPopover.vue +1 -1
  21. package/package.json +12 -8
  22. package/types/components/collapsible/UluAccordion.vue.d.ts +41 -0
  23. package/types/components/collapsible/UluAccordion.vue.d.ts.map +1 -0
  24. package/types/components/collapsible/UluAccordionGroup.vue.d.ts +19 -0
  25. package/types/components/collapsible/UluAccordionGroup.vue.d.ts.map +1 -0
  26. package/types/components/collapsible/UluCollapsible.vue.d.ts +39 -0
  27. package/types/components/collapsible/UluCollapsible.vue.d.ts.map +1 -0
  28. package/types/components/collapsible/UluDropdown.vue.d.ts +22 -0
  29. package/types/components/collapsible/UluDropdown.vue.d.ts.map +1 -0
  30. package/types/components/collapsible/UluModal.vue.d.ts +253 -0
  31. package/types/components/collapsible/UluModal.vue.d.ts.map +1 -0
  32. package/types/components/collapsible/UluOverflowPopover.vue.d.ts +18 -0
  33. package/types/components/collapsible/UluOverflowPopover.vue.d.ts.map +1 -0
  34. package/types/components/collapsible/UluTab.vue.d.ts +10 -0
  35. package/types/components/collapsible/UluTab.vue.d.ts.map +1 -0
  36. package/types/components/collapsible/UluTabGroup.vue.d.ts +18 -0
  37. package/types/components/collapsible/UluTabGroup.vue.d.ts.map +1 -0
  38. package/types/components/collapsible/UluTabList.vue.d.ts +10 -0
  39. package/types/components/collapsible/UluTabList.vue.d.ts.map +1 -0
  40. package/types/components/collapsible/UluTabPanel.vue.d.ts +10 -0
  41. package/types/components/collapsible/UluTabPanel.vue.d.ts.map +1 -0
  42. package/types/components/collapsible/UluTabPanels.vue.d.ts +10 -0
  43. package/types/components/collapsible/UluTabPanels.vue.d.ts.map +1 -0
  44. package/types/components/elements/UluAlert.vue.d.ts +147 -0
  45. package/types/components/elements/UluAlert.vue.d.ts.map +1 -0
  46. package/types/components/elements/UluBadge.vue.d.ts +30 -0
  47. package/types/components/elements/UluBadge.vue.d.ts.map +1 -0
  48. package/types/components/elements/UluBadgeStack.vue.d.ts +9 -0
  49. package/types/components/elements/UluBadgeStack.vue.d.ts.map +1 -0
  50. package/types/components/elements/UluButton.vue.d.ts +186 -0
  51. package/types/components/elements/UluButton.vue.d.ts.map +1 -0
  52. package/types/components/elements/UluButtonVerbose.vue.d.ts +126 -0
  53. package/types/components/elements/UluButtonVerbose.vue.d.ts.map +1 -0
  54. package/types/components/elements/UluCallout.vue.d.ts +27 -0
  55. package/types/components/elements/UluCallout.vue.d.ts.map +1 -0
  56. package/types/components/elements/UluCard.vue.d.ts +238 -0
  57. package/types/components/elements/UluCard.vue.d.ts.map +1 -0
  58. package/types/components/elements/UluDefinitionList.vue.d.ts +26 -0
  59. package/types/components/elements/UluDefinitionList.vue.d.ts.map +1 -0
  60. package/types/components/elements/UluExternalLink.vue.d.ts +25 -0
  61. package/types/components/elements/UluExternalLink.vue.d.ts.map +1 -0
  62. package/types/components/elements/UluIcon.vue.d.ts +11 -0
  63. package/types/components/elements/UluIcon.vue.d.ts.map +1 -0
  64. package/types/components/elements/UluList.vue.d.ts +37 -0
  65. package/types/components/elements/UluList.vue.d.ts.map +1 -0
  66. package/types/components/elements/UluMain.vue.d.ts +13 -0
  67. package/types/components/elements/UluMain.vue.d.ts.map +1 -0
  68. package/types/components/elements/UluSpokeSpinner.vue.d.ts +9 -0
  69. package/types/components/elements/UluSpokeSpinner.vue.d.ts.map +1 -0
  70. package/types/components/elements/UluTag.vue.d.ts +26 -0
  71. package/types/components/elements/UluTag.vue.d.ts.map +1 -0
  72. package/types/components/forms/UluFileDisplay.vue.d.ts +23 -0
  73. package/types/components/forms/UluFileDisplay.vue.d.ts.map +1 -0
  74. package/types/components/forms/UluFormFile.vue.d.ts +26 -0
  75. package/types/components/forms/UluFormFile.vue.d.ts.map +1 -0
  76. package/types/components/forms/UluFormMessage.vue.d.ts +18 -0
  77. package/types/components/forms/UluFormMessage.vue.d.ts.map +1 -0
  78. package/types/components/forms/UluFormSelect.vue.d.ts +24 -0
  79. package/types/components/forms/UluFormSelect.vue.d.ts.map +1 -0
  80. package/types/components/forms/UluFormText.vue.d.ts +22 -0
  81. package/types/components/forms/UluFormText.vue.d.ts.map +1 -0
  82. package/types/components/forms/UluSearchForm.vue.d.ts +9 -0
  83. package/types/components/forms/UluSearchForm.vue.d.ts.map +1 -0
  84. package/types/components/forms/UluSelectableMenu.vue.d.ts +30 -0
  85. package/types/components/forms/UluSelectableMenu.vue.d.ts.map +1 -0
  86. package/types/components/index.d.ts +48 -0
  87. package/types/components/layout/UluAdaptiveLayout.vue.d.ts +12 -0
  88. package/types/components/layout/UluAdaptiveLayout.vue.d.ts.map +1 -0
  89. package/types/components/layout/UluDataGrid.vue.d.ts +3 -0
  90. package/types/components/layout/UluDataGrid.vue.d.ts.map +1 -0
  91. package/types/components/layout/UluTitleRail.vue.d.ts +91 -0
  92. package/types/components/layout/UluTitleRail.vue.d.ts.map +1 -0
  93. package/types/components/layout/UluWhenBreakpoint.vue.d.ts +20 -0
  94. package/types/components/layout/UluWhenBreakpoint.vue.d.ts.map +1 -0
  95. package/types/components/navigation/UluBreadcrumb.vue.d.ts +71 -0
  96. package/types/components/navigation/UluBreadcrumb.vue.d.ts.map +1 -0
  97. package/types/components/navigation/UluMenu.vue.d.ts +142 -0
  98. package/types/components/navigation/UluMenu.vue.d.ts.map +1 -0
  99. package/types/components/navigation/UluMenuStack.vue.d.ts +19 -0
  100. package/types/components/navigation/UluMenuStack.vue.d.ts.map +1 -0
  101. package/types/components/navigation/UluNavStrip.vue.d.ts +138 -0
  102. package/types/components/navigation/UluNavStrip.vue.d.ts.map +1 -0
  103. package/types/components/navigation/UluPager.vue.d.ts +15 -0
  104. package/types/components/navigation/UluPager.vue.d.ts.map +1 -0
  105. package/types/components/navigation/UluSkipLink.vue.d.ts +3 -0
  106. package/types/components/navigation/UluSkipLink.vue.d.ts.map +1 -0
  107. package/types/components/systems/facets/ExampleFacetsWithPagination.vue.d.ts +3 -0
  108. package/types/components/systems/facets/ExampleFacetsWithPagination.vue.d.ts.map +1 -0
  109. package/types/components/systems/facets/UluFacetsActiveFilters.vue.d.ts +18 -0
  110. package/types/components/systems/facets/UluFacetsActiveFilters.vue.d.ts.map +1 -0
  111. package/types/components/systems/facets/UluFacetsFilterLists.vue.d.ts +27 -0
  112. package/types/components/systems/facets/UluFacetsFilterLists.vue.d.ts.map +1 -0
  113. package/types/components/systems/facets/UluFacetsFilterPopovers.vue.d.ts +25 -0
  114. package/types/components/systems/facets/UluFacetsFilterPopovers.vue.d.ts.map +1 -0
  115. package/types/components/systems/facets/UluFacetsFilterSelects.vue.d.ts +11 -0
  116. package/types/components/systems/facets/UluFacetsFilterSelects.vue.d.ts.map +1 -0
  117. package/types/components/systems/facets/UluFacetsHeaderLayout.vue.d.ts +12 -0
  118. package/types/components/systems/facets/UluFacetsHeaderLayout.vue.d.ts.map +1 -0
  119. package/types/components/systems/facets/UluFacetsList.vue.d.ts +21 -0
  120. package/types/components/systems/facets/UluFacetsList.vue.d.ts.map +1 -0
  121. package/types/components/systems/facets/UluFacetsResults.vue.d.ts +27 -0
  122. package/types/components/systems/facets/UluFacetsResults.vue.d.ts.map +1 -0
  123. package/types/components/systems/facets/UluFacetsSearch.vue.d.ts +15 -0
  124. package/types/components/systems/facets/UluFacetsSearch.vue.d.ts.map +1 -0
  125. package/types/components/systems/facets/UluFacetsSidebarLayout.vue.d.ts +22 -0
  126. package/types/components/systems/facets/UluFacetsSidebarLayout.vue.d.ts.map +1 -0
  127. package/types/components/systems/facets/UluFacetsSort.vue.d.ts +22 -0
  128. package/types/components/systems/facets/UluFacetsSort.vue.d.ts.map +1 -0
  129. package/types/components/systems/facets/useFacets.d.ts +30 -12
  130. package/types/components/systems/facets/useFacets.d.ts.map +1 -1
  131. package/types/components/systems/index.d.ts +24 -0
  132. package/types/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts +60 -0
  133. package/types/components/systems/scroll-anchors/UluScrollAnchors.vue.d.ts.map +1 -0
  134. package/types/components/systems/scroll-anchors/UluScrollAnchorsNav.vue.d.ts +15 -0
  135. package/types/components/systems/scroll-anchors/UluScrollAnchorsNav.vue.d.ts.map +1 -0
  136. package/types/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts +32 -0
  137. package/types/components/systems/scroll-anchors/UluScrollAnchorsNavAnimated.vue.d.ts.map +1 -0
  138. package/types/components/systems/scroll-anchors/UluScrollAnchorsSection.vue.d.ts +50 -0
  139. package/types/components/systems/scroll-anchors/UluScrollAnchorsSection.vue.d.ts.map +1 -0
  140. package/types/components/systems/scroll-anchors/symbols.d.ts +3 -3
  141. package/types/components/systems/scroll-anchors/symbols.d.ts.map +1 -1
  142. package/types/components/systems/skeleton/UluShowSkeleton.vue.d.ts +16 -0
  143. package/types/components/systems/skeleton/UluShowSkeleton.vue.d.ts.map +1 -0
  144. package/types/components/systems/skeleton/UluSkeletonContent.vue.d.ts +9 -0
  145. package/types/components/systems/skeleton/UluSkeletonContent.vue.d.ts.map +1 -0
  146. package/types/components/systems/skeleton/UluSkeletonMedia.vue.d.ts +3 -0
  147. package/types/components/systems/skeleton/UluSkeletonMedia.vue.d.ts.map +1 -0
  148. package/types/components/systems/skeleton/UluSkeletonText.vue.d.ts +13 -0
  149. package/types/components/systems/skeleton/UluSkeletonText.vue.d.ts.map +1 -0
  150. package/types/components/systems/slider/UluImageSlideShow.vue.d.ts +130 -0
  151. package/types/components/systems/slider/UluImageSlideShow.vue.d.ts.map +1 -0
  152. package/types/components/systems/slider/UluSlideShow.vue.d.ts +205 -0
  153. package/types/components/systems/slider/UluSlideShow.vue.d.ts.map +1 -0
  154. package/types/components/systems/slider/UluSlideShowSlide.vue.d.ts +17 -0
  155. package/types/components/systems/slider/UluSlideShowSlide.vue.d.ts.map +1 -0
  156. package/types/components/systems/table-sticky/UluTableSticky.vue.d.ts +455 -0
  157. package/types/components/systems/table-sticky/UluTableSticky.vue.d.ts.map +1 -0
  158. package/types/components/systems/table-sticky/UluTableStickyRows.vue.d.ts +34 -0
  159. package/types/components/systems/table-sticky/UluTableStickyRows.vue.d.ts.map +1 -0
  160. package/types/components/systems/table-sticky/UluTableStickyTable.vue.d.ts +151 -0
  161. package/types/components/systems/table-sticky/UluTableStickyTable.vue.d.ts.map +1 -0
  162. package/types/components/utils/UluCondText.vue.d.ts +29 -0
  163. package/types/components/utils/UluCondText.vue.d.ts.map +1 -0
  164. package/types/components/utils/UluEmpty.vue.d.ts +3 -0
  165. package/types/components/utils/UluEmpty.vue.d.ts.map +1 -0
  166. package/types/components/utils/UluEmptyView.vue.d.ts +3 -0
  167. package/types/components/utils/UluEmptyView.vue.d.ts.map +1 -0
  168. package/types/components/utils/UluPlaceholderImage.vue.d.ts +61 -0
  169. package/types/components/utils/UluPlaceholderImage.vue.d.ts.map +1 -0
  170. package/types/components/utils/UluPlaceholderText.vue.d.ts +24 -0
  171. package/types/components/utils/UluPlaceholderText.vue.d.ts.map +1 -0
  172. package/types/components/utils/UluRouteAnnouncer.vue.d.ts +63 -0
  173. package/types/components/utils/UluRouteAnnouncer.vue.d.ts.map +1 -0
  174. package/types/components/visualizations/UluAnimateNumber.vue.d.ts +16 -0
  175. package/types/components/visualizations/UluAnimateNumber.vue.d.ts.map +1 -0
  176. package/types/components/visualizations/UluProgressBar.vue.d.ts +66 -0
  177. package/types/components/visualizations/UluProgressBar.vue.d.ts.map +1 -0
  178. package/types/components/visualizations/UluProgressCircle.vue.d.ts +48 -0
  179. package/types/components/visualizations/UluProgressCircle.vue.d.ts.map +1 -0
  180. package/types/composables/useBreakpointManager.d.ts +1 -1
  181. package/types/composables/useBreakpointManager.d.ts.map +1 -1
  182. package/types/composables/useDocumentTitle.d.ts +4 -4
  183. package/types/composables/useDocumentTitle.d.ts.map +1 -1
  184. package/types/composables/useIcon.d.ts +1 -1
  185. package/types/composables/useIcon.d.ts.map +1 -1
  186. package/types/composables/useModifiers.d.ts +1 -1
  187. package/types/composables/useModifiers.d.ts.map +1 -1
  188. package/types/composables/useWindowResize.d.ts +1 -1
  189. package/types/composables/useWindowResize.d.ts.map +1 -1
  190. package/types/plugins/modals/UluModalsDisplay.vue.d.ts +14 -0
  191. package/types/plugins/modals/UluModalsDisplay.vue.d.ts.map +1 -0
  192. package/types/plugins/modals/api.d.ts +7 -4
  193. package/types/plugins/modals/api.d.ts.map +1 -1
  194. package/types/plugins/popovers/UluPopover.vue.d.ts +53 -0
  195. package/types/plugins/popovers/UluPopover.vue.d.ts.map +1 -0
  196. package/types/plugins/popovers/UluTooltipDisplay.vue.d.ts +3 -0
  197. package/types/plugins/popovers/UluTooltipDisplay.vue.d.ts.map +1 -0
  198. package/types/plugins/popovers/UluTooltipPopover.vue.d.ts +3 -0
  199. package/types/plugins/popovers/UluTooltipPopover.vue.d.ts.map +1 -0
  200. package/types/plugins/popovers/defaults.d.ts +2 -2
  201. package/types/plugins/popovers/defaults.d.ts.map +1 -1
  202. package/types/plugins/popovers/manager.d.ts +50 -6
  203. package/types/plugins/popovers/manager.d.ts.map +1 -1
  204. package/types/plugins/popovers/useFollow.d.ts +4 -4
  205. package/types/plugins/popovers/useFollow.d.ts.map +1 -1
  206. package/types/plugins/toast/UluToast.vue.d.ts +52 -0
  207. package/types/plugins/toast/UluToast.vue.d.ts.map +1 -0
  208. package/types/plugins/toast/UluToastDisplay.vue.d.ts +13 -0
  209. package/types/plugins/toast/UluToastDisplay.vue.d.ts.map +1 -0
  210. package/types/plugins/toast/defaults.d.ts +39 -2
  211. package/types/plugins/toast/store.d.ts +65 -2
  212. package/types/plugins/toast/store.d.ts.map +1 -1
  213. package/types/utils/dom.d.ts +1 -1
  214. package/types/utils/dom.d.ts.map +1 -1
  215. package/types/utils/router.d.ts +15 -15
  216. package/types/utils/router.d.ts.map +1 -1
  217. package/lib/components/visualizations/progress-bar-examples.html +0 -175
@@ -1,6 +1,74 @@
1
1
  import { ref, computed, watch } from 'vue';
2
2
  import Fuse from 'fuse.js';
3
3
 
4
+ /**
5
+ * Helper function to create a union of multiple Sets.
6
+ * @param {Array<Set>} sets - An array of sets to unify.
7
+ * @returns {Set} A new set containing all unique items from the input sets.
8
+ */
9
+ function unionSets(sets) {
10
+ const union = new Set();
11
+ for (const set of sets) {
12
+ for (const item of set) {
13
+ union.add(item);
14
+ }
15
+ }
16
+ return union;
17
+ }
18
+
19
+ /**
20
+ * Helper function to create an intersection of multiple Sets.
21
+ * @param {Array<Set>} sets - An array of sets to intersect.
22
+ * @returns {Set} A new set containing only the items present in all input sets.
23
+ */
24
+ function intersectSets(sets) {
25
+ if (!sets || sets.length === 0) return new Set();
26
+ // Start with a copy of the first set, which is an optimization if it's the smallest.
27
+ // For better performance, sort sets by size and start with the smallest.
28
+ const sortedSets = sets.sort((a, b) => a.size - b.size);
29
+ const intersection = new Set(sortedSets[0]);
30
+ for (let i = 1; i < sortedSets.length; i++) {
31
+ for (const item of intersection) {
32
+ if (!sortedSets[i].has(item)) {
33
+ intersection.delete(item);
34
+ }
35
+ }
36
+ // If intersection becomes empty, no need to continue.
37
+ if (intersection.size === 0) break;
38
+ }
39
+ return intersection;
40
+ }
41
+
42
+ /**
43
+ * Calculates the final set of item indices based on selected facets using the pre-built index.
44
+ * @param {Array<Object>} selected - The array of selected facet groups.
45
+ * @param {Map<String, Set>} index - The inverted index.
46
+ * @param {Set<number>} allItemsSet - A set of all possible item indices.
47
+ * @returns {Set} A set of indices for the items that match the filters.
48
+ */
49
+ function getFilteredSetFromIndex(selected, index, allItemsSet) {
50
+ if (!selected || selected.length === 0) {
51
+ return allItemsSet;
52
+ }
53
+
54
+ const groupSets = selected.map(group => {
55
+ const childSets = group.children.map(child => {
56
+ const key = `${group.uid}:${child.uid}`;
57
+ return index.get(key) || new Set();
58
+ });
59
+
60
+ // For 'all' (AND), intersect the sets within the group.
61
+ if (group.match === 'all') {
62
+ return intersectSets(childSets);
63
+ }
64
+ // For 'some' (OR), union the sets within the group.
65
+ return unionSets(childSets);
66
+ });
67
+
68
+ // Intersect the results from each group.
69
+ return intersectSets(groupSets);
70
+ }
71
+
4
72
  /**
5
73
  * Generates facet groups and their possible values from a list of items.
6
74
  * @param {Array<Object>} allItems - The full list of items.
@@ -54,6 +122,7 @@ function generateInitialFacets(allItems, facetFields) {
54
122
  * @param {Object} [options.searchOptions={}] - Configuration options for Fuse.js.
55
123
  * @param {Function} [options.getItemFacet] - A function to retrieve facet information from an item. Should always return an array of values.
56
124
  * @param {Function} [options.getSortValue] - A function to get the value to sort by from an item.
125
+ * @param {String} [options.countMode='none'] - The mode for calculating facet counts. Can be 'none', 'simple', or 'intuitive'.
57
126
  */
58
127
  export function useFacets(allItems, options = {}) {
59
128
  const defaultGetItemFacet = (item, uid) => {
@@ -120,6 +189,37 @@ export function useFacets(allItems, options = {}) {
120
189
  ...extraSortTypes
121
190
  }));
122
191
 
192
+ const facetIndex = computed(() => {
193
+ const index = new Map();
194
+ // Depend on searchedItems to re-build when search changes.
195
+ const items = searchedItems.value;
196
+ if (!items || !facetFields) return index;
197
+
198
+ // Create a map of getters for efficiency inside the loop.
199
+ const getters = new Map(facetFields.map(group => {
200
+ const getValue = group.getValue || (item => item[group.uid]);
201
+ return [group.uid, getValue];
202
+ }));
203
+
204
+ for (let i = 0; i < items.length; i++) {
205
+ const item = items[i];
206
+ for (const group of facetFields) {
207
+ const getValue = getters.get(group.uid);
208
+ const value = getValue(item);
209
+ const values = Array.isArray(value) ? value : (value ? [value] : []);
210
+
211
+ for (const v of values) {
212
+ const key = `${group.uid}:${v}`;
213
+ if (!index.has(key)) {
214
+ index.set(key, new Set());
215
+ }
216
+ index.get(key).add(i);
217
+ }
218
+ }
219
+ }
220
+ return index;
221
+ });
222
+
123
223
  const searchOptions = computed(() => ({
124
224
  shouldSort: true,
125
225
  keys: ["title", "label", "description", "author"],
@@ -138,7 +238,6 @@ export function useFacets(allItems, options = {}) {
138
238
  const selected = [];
139
239
  facets.value.forEach((group) => {
140
240
  const selectedChildren = group.children.filter(child => child.selected);
141
- group.selectedCount = selectedChildren.length;
142
241
  if (selectedChildren.length > 0) {
143
242
  selected.push({ ...group, children: selectedChildren });
144
243
  }
@@ -147,7 +246,23 @@ export function useFacets(allItems, options = {}) {
147
246
  });
148
247
 
149
248
  const filteredItems = computed(() => {
150
- return getFilteredItems(searchedItems.value, selectedFacets.value);
249
+ if (!selectedFacets.value.length) {
250
+ return searchedItems.value;
251
+ }
252
+ const index = facetIndex.value;
253
+ // This check is important because the index might not be ready on the first render.
254
+ if (index.size === 0 && searchedItems.value.length > 0 && facetFields?.length > 0) {
255
+ return [];
256
+ }
257
+ const allItemsSet = new Set(searchedItems.value.map((_, i) => i));
258
+ const filteredIndexSet = getFilteredSetFromIndex(selectedFacets.value, index, allItemsSet);
259
+
260
+ const result = [];
261
+ // Using a direct loop for performance.
262
+ for (const i of filteredIndexSet) {
263
+ result.push(searchedItems.value[i]);
264
+ }
265
+ return result;
151
266
  });
152
267
 
153
268
  const displayItems = computed(() => {
@@ -159,27 +274,12 @@ export function useFacets(allItems, options = {}) {
159
274
  });
160
275
 
161
276
  // --- Methods ---
162
- function getFilteredItems(items, selected) {
163
- if (!selected.length) return items;
164
- return items.filter(item => {
165
- return selected.every(group => {
166
- const itemFacetValues = getItemFacet(item, group.uid);
167
- if (itemFacetValues && itemFacetValues.length) {
168
- if (group.match === 'all') {
169
- return group.children.every(facet => itemFacetValues.includes(facet.uid));
170
- }
171
- return group.children.some(facet => itemFacetValues.includes(facet.uid));
172
- }
173
- return false;
174
- });
175
- });
176
- }
177
-
178
277
  function clearFilters() {
179
278
  facets.value.forEach(group => {
180
279
  if (group.children) {
181
280
  group.children.forEach(child => child.selected = false);
182
281
  }
282
+ group.selectedCount = 0;
183
283
  });
184
284
  }
185
285
 
@@ -197,12 +297,19 @@ export function useFacets(allItems, options = {}) {
197
297
  if (facet) {
198
298
  facet.selected = selected;
199
299
  }
300
+ // Recalculate selectedCount for the group
301
+ group.selectedCount = group.children.filter(c => c.selected).length;
200
302
  }
201
303
  }
202
304
 
203
305
  // --- Watchers ---
204
306
  watch(generatedFacets, (newGeneratedFacets) => {
205
- facets.value = createFacets(initialFacets || newGeneratedFacets);
307
+ const newFacets = createFacets(initialFacets || newGeneratedFacets);
308
+ // Calculate initial counts
309
+ newFacets.forEach(group => {
310
+ group.selectedCount = group.children.filter(c => c.selected).length;
311
+ });
312
+ facets.value = newFacets;
206
313
  }, { immediate: true });
207
314
 
208
315
  watch([selectedFacets, searchedItems], ([currentSelected, currentSearchedItems], [prevSelected, prevSearchedItems]) => {
@@ -223,33 +330,52 @@ export function useFacets(allItems, options = {}) {
223
330
  });
224
331
  });
225
332
  } else if (countMode === 'intuitive') {
333
+ const index = facetIndex.value;
334
+ if (index.size === 0 && searchedItems.value.length > 0 && facetFields?.length > 0) {
335
+ // Index might not be ready yet.
336
+ return;
337
+ }
338
+ const allItemsSet = new Set(searchedItems.value.map((_, i) => i));
339
+ const currentFilteredSet = getFilteredSetFromIndex(currentSelected, index, allItemsSet);
340
+
226
341
  facets.value.forEach(group => {
227
342
  group.children.forEach(child => {
228
- const tempFacets = JSON.parse(JSON.stringify(facets.value));
229
- const tempGroup = tempFacets.find(g => g.uid === group.uid);
230
- const tempChild = tempGroup.children.find(c => c.uid === child.uid);
231
-
232
- if (tempGroup.multiple) {
233
- if (tempChild.selected) {
234
- child.count = filteredItems.value.filter(item => {
235
- const itemValues = getItemFacet(item, group.uid);
236
- return itemValues.includes(child.uid);
237
- }).length;
238
- return;
343
+ const key = `${group.uid}:${child.uid}`;
344
+ const childSet = index.get(key) || new Set();
345
+
346
+ if (child.selected) {
347
+ // --- Logic for already-selected facets (now performant) ---
348
+ if (group.multiple) {
349
+ // This is the intersection of currently filtered items and items with this facet value.
350
+ const intersection = intersectSets([currentFilteredSet, childSet]);
351
+ child.count = intersection.size;
352
+ } else {
353
+ // For single-select, the count is just the total number of filtered items.
354
+ child.count = currentFilteredSet.size;
239
355
  }
240
- tempChild.selected = true;
241
356
  } else {
242
- if (tempChild.selected) {
243
- child.count = filteredItems.value.length;
244
- return;
357
+ // --- Logic for un-selected facets (now without deep clone) ---
358
+ const tempSelected = [];
359
+ for (const g of currentSelected) {
360
+ tempSelected.push({ ...g, children: [...g.children] });
361
+ }
362
+
363
+ let groupInTemp = tempSelected.find(g => g.uid === group.uid);
364
+
365
+ if (!groupInTemp) {
366
+ groupInTemp = { ...group, children: [] };
367
+ tempSelected.push(groupInTemp);
368
+ }
369
+
370
+ if (group.multiple) {
371
+ groupInTemp.children.push(child);
372
+ } else {
373
+ groupInTemp.children = [child];
245
374
  }
246
- tempGroup.children.forEach(f => { f.selected = false; });
247
- tempChild.selected = true;
375
+
376
+ const finalSet = getFilteredSetFromIndex(tempSelected, index, allItemsSet);
377
+ child.count = finalSet.size;
248
378
  }
249
-
250
- const tempSelected = tempFacets.map(g => ({...g, children: g.children.filter(c => c.selected)})).filter(g => g.children.length > 0);
251
- const resultItems = getFilteredItems(currentSearchedItems, tempSelected);
252
- child.count = resultItems.length;
253
379
  });
254
380
  });
255
381
  }
@@ -1,4 +1,5 @@
1
1
  export { useFacets } from './facets/useFacets.js';
2
+ export { default as UluFacetsActiveFilters } from './facets/UluFacetsActiveFilters.vue';
2
3
  export { default as UluFacetsFilterLists } from './facets/UluFacetsFilterLists.vue';
3
4
  export { default as UluFacetsFilterPopovers } from './facets/UluFacetsFilterPopovers.vue';
4
5
  export { default as UluFacetsFilterSelects } from './facets/UluFacetsFilterSelects.vue';
@@ -50,7 +50,7 @@
50
50
  @click="previous"
51
51
  :disabled="!canScrollLeft"
52
52
  >
53
- <UluIcon class="slideshow__control-icon" icon="type:next"/>
53
+ <UluIcon class="slideshow__control-icon" icon="type:previous"/>
54
54
  </button>
55
55
  </li>
56
56
  <li class="slideshow__controls-item slideshow__controls-item--next">
@@ -60,7 +60,7 @@
60
60
  @click="next"
61
61
  :disabled="!canScrollRight"
62
62
  >
63
- <UluIcon class="slideshow__control-icon" icon="type:previous" />
63
+ <UluIcon class="slideshow__control-icon" icon="type:next" />
64
64
  </button>
65
65
  </li>
66
66
  </ul>
@@ -1,13 +1,26 @@
1
1
  <template>
2
2
  <div :class="componentClasses">
3
- <div v-if="label || $slots.icon" class="progress-bar__header">
4
- <strong
3
+ <div
4
+ v-if="label || $slots.label || $slots.icon || amountInHeader"
5
+ class="progress-bar__header"
6
+ >
7
+ <component
5
8
  v-if="label"
9
+ :is="labelElement"
6
10
  class="progress-bar__label"
7
- :class="{ 'hidden-visually': labelHidden }"
11
+ :class="[classes.label, { 'hidden-visually': labelHidden }]"
8
12
  >
9
- {{ label }}
10
- </strong>
13
+ <slot name="label">
14
+ {{ label }}
15
+ </slot>
16
+ </component>
17
+ <div
18
+ v-if="amountInHeader"
19
+ class="progress-bar__value progress-bar__value--amount"
20
+ >
21
+ <strong class="hidden-visually">Amount:</strong>
22
+ <slot name="valueAmount" :value="amount">{{ formatValue(amount, 'amount') }}</slot>
23
+ </div>
11
24
  <div v-if="$slots.icon" class="progress-bar__icon">
12
25
  <slot name="icon" />
13
26
  </div>
@@ -20,21 +33,21 @@
20
33
  :style="{ width: deficitBarWidth }"
21
34
  ></div>
22
35
  </div>
23
- <div v-if="!loader && !indeterminate" class="progress-bar__values">
36
+ <div
37
+ v-if="!noValues && !amountInHeader && (!loader && !indeterminate)"
38
+ class="progress-bar__values"
39
+ >
24
40
  <div class="progress-bar__value progress-bar__value--amount">
25
41
  <strong class="hidden-visually">Amount:</strong>
26
- {{ amount }}
42
+ <slot name="valueAmount" :value="amount">{{ formatValue(amount, 'amount') }}</slot>
27
43
  </div>
28
- <div
29
- v-if="deficit > 0"
30
- class="progress-bar__value progress-bar__value--deficit"
31
- >
44
+ <div v-if="deficit > 0" class="progress-bar__value progress-bar__value--deficit">
32
45
  <strong class="hidden-visually">Deficit: </strong>
33
- -{{ deficit }}
46
+ <slot name="valueDeficit" :value="deficit">-{{ formatValue(deficit, 'deficit') }}</slot>
34
47
  </div>
35
48
  <div class="progress-bar__value progress-bar__value--total">
36
49
  <strong class="hidden-visually">Total:</strong>
37
- {{ total }}
50
+ <slot name="valueTotal" :value="total">{{ formatValue(total, 'total') }}</slot>
38
51
  </div>
39
52
  </div>
40
53
  </div>
@@ -49,13 +62,27 @@ import { computed } from "vue";
49
62
  */
50
63
  const props = defineProps({
51
64
  /**
52
- * The label to display above the progress bar.
65
+ * The label to display above the progress bar. (or use label slot)
53
66
  */
54
67
  label: String,
55
68
  /**
56
69
  * Hides the label visually, but keeps it for screen readers.
57
70
  */
58
71
  labelHidden: Boolean,
72
+ /**
73
+ * Optional classes object (currently only allowing { label } class)
74
+ */
75
+ classes: {
76
+ type: Object,
77
+ default: () => ({})
78
+ },
79
+ /**
80
+ * Element to use for label
81
+ */
82
+ labelElement: {
83
+ type: String,
84
+ default: "strong"
85
+ },
59
86
  /**
60
87
  * The current amount of progress.
61
88
  */
@@ -97,6 +124,22 @@ const props = defineProps({
97
124
  * Applies an indeterminate animation for unknown progress.
98
125
  */
99
126
  indeterminate: Boolean,
127
+ /**
128
+ * Omit values from output (the numbers below the progress bar)
129
+ */
130
+ noValues: Boolean,
131
+ /**
132
+ * A function to format the numerical values (amount, deficit, total).
133
+ * Takes the value and type ('amount', 'deficit', 'total') as input and should return a string.
134
+ */
135
+ formatValue: {
136
+ type: Function,
137
+ default: (value, type) => value,
138
+ },
139
+ /**
140
+ * Will put the amount only in header (there is a headerValue slot it you want to format)
141
+ */
142
+ amountInHeader: Boolean
100
143
  });
101
144
 
102
145
  const getCssPercentage = (amount, total) => {
@@ -23,124 +23,137 @@
23
23
  cy="16"
24
24
  />
25
25
  </svg>
26
- <strong v-if="!showValueOutside" class="progress-circle__chart-value">
27
- {{ percentage }}%
26
+ <strong v-if="!showValueOutside && !noValue" class="progress-circle__chart-value">
27
+ <slot name="value" :value="percentage">{{ formatValue(percentage) }}</slot>
28
28
  </strong>
29
29
  </div>
30
- <strong v-if="showValueOutside" class="progress-circle__value type-small-x">
31
- {{ percentage }}%
30
+ <strong v-if="showValueOutside && !noValue" class="progress-circle__value type-small-x">
31
+ <slot name="value" :value="percentage">{{ formatValue(percentage) }}</slot>
32
32
  </strong>
33
33
  </div>
34
34
  </template>
35
35
 
36
- <script>
36
+ <script setup>
37
+ import { ref, computed, watch, onMounted } from 'vue';
38
+
39
+ /**
40
+ * A circular progress indicator component.
41
+ * @slot value - The value display. Overrides the `formatValue` prop.
42
+ */
43
+ const props = defineProps({
44
+ /**
45
+ * The label for accessibility (visually hidden).
46
+ */
47
+ label: {
48
+ type: String,
49
+ default: "Progress"
50
+ },
51
+ /**
52
+ * The progress percentage (0-100).
53
+ */
54
+ percentage: {
55
+ type: Number,
56
+ default: 0
57
+ },
58
+ /**
59
+ * A function to format the percentage value.
60
+ * Takes the number as input and should return a string.
61
+ */
62
+ formatValue: {
63
+ type: Function,
64
+ default: (value) => `${ value }%`,
65
+ },
66
+ /**
67
+ * Hides the percentage value display.
68
+ */
69
+ noValue: Boolean,
70
+ /**
71
+ * Renders a smaller version of the component.
72
+ */
73
+ small: Boolean,
74
+ /**
75
+ * Displays the percentage value outside (to the side) of the circle.
76
+ */
77
+ outside: Boolean,
78
+ /**
79
+ * Displays the percentage value below the circle.
80
+ */
81
+ outsideBelow: Boolean,
82
+ /**
83
+ * Sets the status color of the progress circle (e.g., 'low', 'incomplete', 'complete').
84
+ */
85
+ status: {
86
+ type: String,
87
+ default: ''
88
+ },
37
89
  /**
38
- * A circular progress indicator component.
90
+ * Renders the component as a solid pie chart instead of a donut.
39
91
  */
40
- export default {
41
- name: 'UluProgressCircle',
42
- props: {
43
- /**
44
- * The label for accessibility (visually hidden).
45
- */
46
- label: {
47
- type: String,
48
- default: "Progress"
49
- },
50
- /**
51
- * The progress percentage (0-100).
52
- */
53
- percentage: {
54
- type: Number,
55
- default: 0
56
- },
57
- /**
58
- * Renders a smaller version of the component.
59
- */
60
- small: Boolean,
61
- /**
62
- * Displays the percentage value outside (to the side) of the circle.
63
- */
64
- outside: Boolean,
65
- /**
66
- * Displays the percentage value below the circle.
67
- */
68
- outsideBelow: Boolean,
69
- /**
70
- * Sets the status color of the progress circle (e.g., 'low', 'incomplete', 'complete').
71
- */
72
- status: {
73
- type: String,
74
- default: ''
75
- },
76
- /**
77
- * Renders the component as a solid pie chart instead of a donut.
78
- */
79
- pieStyle: Boolean,
80
- /**
81
- * Removes the center mask, filling the entire circle.
82
- */
83
- noMask: Boolean,
84
- /**
85
- * The duration of the animation in milliseconds.
86
- */
87
- duration: {
88
- type: Number,
89
- default: 1000 // Matches SCSS animation-duration
90
- },
91
- /**
92
- * The easing function for the animation.
93
- */
94
- easing: {
95
- type: String,
96
- default: "ease-in" // Matches SCSS animation-timing
97
- }
98
- },
99
- watch: {
100
- // Need to reanimate if value changes
101
- percentage(newVal, oldVal) {
102
- if (newVal !== oldVal) {
103
- this.animate(this.normalize(oldVal));
104
- }
105
- }
106
- },
107
- computed: {
108
- endDasharray() {
109
- return `${ this.normalize(this.percentage) } 100`;
110
- },
111
- showValueOutside() {
112
- return this.outside || this.outsideBelow || this.small;
113
- },
114
- componentClasses() {
115
- const classes = {
116
- 'progress-circle': true,
117
- 'progress-circle--small': this.small,
118
- 'progress-circle--pie': this.pieStyle,
119
- 'progress-circle--outside': this.showValueOutside,
120
- 'progress-circle--outside-below': this.outsideBelow,
121
- 'progress-circle--no-mask': this.noMask,
122
- };
123
- if (this.status) {
124
- classes[`progress-circle--${this.status}`] = true;
125
- }
126
- return classes;
127
- }
128
- },
129
- methods: {
130
- normalize(percentage) {
131
- // Added the 1% extra to 100% because sometimes it renders with a tiny gap
132
- return percentage === 100 ? 101 : percentage;
133
- },
134
- animate(from = 0) {
135
- const { pie } = this.$refs;
136
- if (!pie || !pie.animate) return; // No Animation API or element not ready
137
- const { duration, easing, endDasharray } = this;
138
- const keyframes = { strokeDasharray: [`${ from } 100`, endDasharray] };
139
- pie.animate(keyframes, { duration, easing, fill: "forwards" });
140
- }
141
- },
142
- mounted() {
143
- this.animate();
144
- }
92
+ pieStyle: Boolean,
93
+ /**
94
+ * Removes the center mask, filling the entire circle.
95
+ */
96
+ noMask: Boolean,
97
+ /**
98
+ * The duration of the animation in milliseconds.
99
+ */
100
+ duration: {
101
+ type: Number,
102
+ default: 1000 // Matches SCSS animation-duration
103
+ },
104
+ /**
105
+ * The easing function for the animation.
106
+ */
107
+ easing: {
108
+ type: String,
109
+ default: "ease-in" // Matches SCSS animation-timing
110
+ }
111
+ });
112
+
113
+ const pie = ref(null);
114
+
115
+ const normalize = (percentage) => {
116
+ // Added the 1% extra to 100% because sometimes it renders with a tiny gap
117
+ return percentage === 100 ? 101 : percentage;
118
+ };
119
+
120
+ const animate = (from = 0) => {
121
+ if (!pie.value || !pie.value.animate) return; // No Animation API or element not ready
122
+
123
+ const keyframes = { strokeDasharray: [`${from} 100`, endDasharray.value] };
124
+ pie.value.animate(keyframes, { duration: props.duration, easing: props.easing, fill: "forwards" });
125
+ };
126
+
127
+ watch(() => props.percentage, (newVal, oldVal) => {
128
+ if (newVal !== oldVal) {
129
+ animate(normalize(oldVal));
130
+ }
131
+ });
132
+
133
+ const endDasharray = computed(() => {
134
+ return `${normalize(props.percentage)} 100`;
135
+ });
136
+
137
+ const showValueOutside = computed(() => {
138
+ return props.outside || props.outsideBelow || props.small;
139
+ });
140
+
141
+ const componentClasses = computed(() => {
142
+ const classes = {
143
+ 'progress-circle': true,
144
+ 'progress-circle--small': props.small,
145
+ 'progress-circle--pie': props.pieStyle,
146
+ 'progress-circle--outside': showValueOutside.value,
147
+ 'progress-circle--outside-below': props.outsideBelow,
148
+ 'progress-circle--no-mask': props.noMask,
149
+ };
150
+ if (props.status) {
151
+ classes[`progress-circle--${props.status}`] = true;
145
152
  }
153
+ return classes;
154
+ });
155
+
156
+ onMounted(() => {
157
+ animate();
158
+ });
146
159
  </script>
@@ -24,8 +24,8 @@ const defaults = {
24
24
  pathSeparator: "fas fa-chevron-right",
25
25
  image: "fas fa-image",
26
26
  file: "fas fa-file",
27
- next: "fas fa-chevron-left",
28
- previous: "fas fa-chevron-right",
27
+ previous: "fas fa-chevron-left",
28
+ next: "fas fa-chevron-right",
29
29
  dropdownExpand: "fas fa-caret-down"
30
30
  }
31
31
  };