@ulu/frontend-vue 0.1.0-beta.11 → 0.1.0-beta.12

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.
@@ -1,46 +1,89 @@
1
- import { ref, computed } from 'vue';
1
+ import { ref, computed, watch } from 'vue';
2
2
  import Fuse from 'fuse.js';
3
3
 
4
- const sortAlpha = items => {
5
- const getTitle = i => (i.title || i.label || "");
6
- return items.sort((a, b) => getTitle(a).localeCompare(getTitle(b)));
4
+ /**
5
+ * Generates facet groups and their possible values from a list of items.
6
+ * @param {Array<Object>} allItems - The full list of items.
7
+ * @param {Array<Object>} facetFields - Configuration for which fields to create facets from.
8
+ * @returns {Array<Object>} The generated facet structure.
9
+ */
10
+ function generateInitialFacets(allItems, facetFields) {
11
+ if (!facetFields || !Array.isArray(facetFields)) return [];
12
+ return facetFields.map(group => {
13
+ const allValues = new Set();
14
+ const getValue = group.getValue || (item => item[group.uid]);
15
+
16
+ allItems.forEach(item => {
17
+ const value = getValue(item);
18
+ if (Array.isArray(value)) {
19
+ value.forEach(v => v && allValues.add(v));
20
+ } else if (value) {
21
+ allValues.add(value);
22
+ }
23
+ });
24
+
25
+ const getLabel = group.getLabel || (value => value);
26
+
27
+ return {
28
+ ...group,
29
+ children: [...allValues].sort().map(value => ({
30
+ uid: value,
31
+ label: getLabel(value),
32
+ selected: false
33
+ }))
34
+ };
35
+ });
7
36
  }
8
- const defaultSorts = {
9
- az: { text: "A-Z", sort: sortAlpha },
10
- za: { text: "Z-A", sort: items => sortAlpha(items).reverse() },
11
- };
37
+
12
38
 
13
39
  /**
14
40
  * A composable for handling client-side faceted search, filtering, and sorting.
15
41
  * @param {import('vue').Ref<Array<Object>>} allItems - A Vue ref containing the full list of items to be processed.
16
42
  * @param {Object} options - Configuration options for the composable.
17
- * @param {Array} [options.initialFacets=[]] - The initial configuration for the facets.
43
+ * @param {Array} [options.initialFacets] - The initial configuration for the facets. Can be generated automatically if `facetFields` is provided.
44
+ * @param {Array} [options.facetFields] - A simpler configuration to automatically generate facets from items. Each item can have `uid`, `name`, `open`, `getValue` and `getLabel`.
18
45
  * @param {String} [options.initialSearchValue=''] - The initial value for the search input.
19
46
  * @param {String} [options.initialSortType='az'] - The initial sort type.
20
47
  * @param {Boolean} [options.noDefaultSorts=false] - If true, the default 'A-Z' and 'Z-A' sorts will not be included.
21
48
  * @param {Object} [options.extraSortTypes={}] - Additional sort types to be merged with the default ones.
22
49
  * @param {Object} [options.searchOptions={}] - Configuration options for Fuse.js.
23
- * @param {Function} [options.getItemFacet] - A function to retrieve facet information from an item.
50
+ * @param {Function} [options.getItemFacet] - A function to retrieve facet information from an item. Should always return an array of values.
51
+ * @param {Function} [options.getSortValue] - A function to get the value to sort by from an item.
24
52
  */
25
53
  export function useFacets(allItems, options = {}) {
54
+ const defaultGetItemFacet = (item, uid) => {
55
+ const value = item[uid];
56
+ if (value === null || typeof value === 'undefined') return [];
57
+ return Array.isArray(value) ? value : [value];
58
+ };
59
+
26
60
  const {
27
- initialFacets = [],
61
+ initialFacets,
62
+ facetFields,
28
63
  initialSearchValue = '',
29
64
  initialSortType = 'az',
30
65
  noDefaultSorts = false,
31
66
  extraSortTypes = {},
32
67
  searchOptions: initialSearchOptions = {},
33
- getItemFacet = (item, uid) => item[uid]
68
+ getItemFacet = defaultGetItemFacet,
69
+ getSortValue = item => (item.title || item.label || "")
34
70
  } = options;
35
71
 
36
- // --- State ---
37
- const facets = ref(createFacets(initialFacets));
38
- const searchValue = ref(initialSearchValue);
39
- const selectedSort = ref(initialSortType);
72
+ const sortAlpha = items => {
73
+ return items.sort((a, b) => {
74
+ const valueA = getSortValue(a);
75
+ const valueB = getSortValue(b);
76
+ return String(valueA).localeCompare(String(valueB));
77
+ });
78
+ }
79
+ const defaultSorts = {
80
+ az: { text: "A-Z", sort: sortAlpha },
81
+ za: { text: "Z-A", sort: items => sortAlpha(items).reverse() },
82
+ };
40
83
 
41
84
  // --- Helpers ---
42
85
  function createFacets(initial) {
43
- return initial.map(group => ({
86
+ return (initial || []).map(group => ({
44
87
  ...group,
45
88
  open: group.open || false,
46
89
  children: group.children.map(facet => ({
@@ -51,6 +94,23 @@ export function useFacets(allItems, options = {}) {
51
94
  }));
52
95
  }
53
96
 
97
+ const generatedFacets = computed(() => {
98
+ if (!facetFields || !allItems.value?.length) return null;
99
+ return generateInitialFacets(allItems.value, facetFields);
100
+ });
101
+
102
+ // --- State ---
103
+ const facets = ref(createFacets(initialFacets || generatedFacets.value));
104
+ const searchValue = ref(initialSearchValue);
105
+ const selectedSort = ref(initialSortType);
106
+
107
+ // If using facetFields, watch for changes in items and regenerate facets
108
+ if (facetFields && !initialFacets) {
109
+ watch(generatedFacets, (newGeneratedFacets) => {
110
+ facets.value = createFacets(newGeneratedFacets);
111
+ });
112
+ }
113
+
54
114
  // --- Computed ---
55
115
  const sortTypes = computed(() => ({
56
116
  ...(noDefaultSorts ? {} : defaultSorts),
@@ -91,10 +151,12 @@ export function useFacets(allItems, options = {}) {
91
151
  return allItems.value;
92
152
  }
93
153
  return allItems.value.filter(item => {
94
- return selectedFacets.value.some(group => {
95
- const cats = getItemFacet(item, group.uid);
96
- if (cats && cats.length) {
97
- return group.children.some(facet => cats.includes(facet.uid));
154
+ // An item must match every active facet group
155
+ return selectedFacets.value.every(group => {
156
+ const itemFacetValues = getItemFacet(item, group.uid);
157
+ if (itemFacetValues && itemFacetValues.length) {
158
+ // An item can match any of the selected facets within a group
159
+ return group.children.some(facet => itemFacetValues.includes(facet.uid));
98
160
  }
99
161
  return false;
100
162
  });
@@ -120,7 +182,21 @@ export function useFacets(allItems, options = {}) {
120
182
 
121
183
  // --- Methods ---
122
184
  function clearFilters() {
123
- facets.value = createFacets(initialFacets);
185
+ facets.value.forEach(group => {
186
+ if (group.children) {
187
+ group.children.forEach(child => child.selected = false);
188
+ }
189
+ });
190
+ }
191
+
192
+ function handleFacetChange({ groupUid, facetUid, selected }) {
193
+ const group = facets.value.find(g => g.uid === groupUid);
194
+ if (group) {
195
+ const facet = group.children.find(f => f.uid === facetUid);
196
+ if (facet) {
197
+ facet.selected = selected;
198
+ }
199
+ }
124
200
  }
125
201
 
126
202
  return {
@@ -135,6 +211,7 @@ export function useFacets(allItems, options = {}) {
135
211
  selectedFacets,
136
212
 
137
213
  // Methods
138
- clearFilters
214
+ clearFilters,
215
+ handleFacetChange
139
216
  };
140
- }
217
+ }
@@ -1,3 +1,4 @@
1
+ export { useFacets } from './facets/useFacets.js';
1
2
  export { default as UluFacetsFilters } from './facets/UluFacetsFilters.vue';
2
3
  export { default as UluFacetsResults } from './facets/UluFacetsResults.vue';
3
4
  export { default as UluFacetsSearch } from './facets/UluFacetsSearch.vue';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ulu/frontend-vue",
3
- "version": "0.1.0-beta.11",
3
+ "version": "0.1.0-beta.12",
4
4
  "description": "A modular and tree-shakeable Vue 3 component library for the Ulu frontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -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;;;;;;;;;;;;;EAqKA"}
@@ -1,2 +1,2 @@
1
- export {};
1
+ export { useFacets } from "./facets/useFacets.js";
2
2
  //# sourceMappingURL=index.d.ts.map