instantsearch.js 4.85.2 → 4.86.0

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,10 +1,19 @@
1
1
  import type { Connector, TransformItems, WidgetRenderState } from '../../types';
2
2
  /**
3
3
  * The **SortBy** connector provides the logic to build a custom widget that will display a
4
- * list of indices. With Algolia, this is most commonly used for changing ranking strategy. This allows
5
- * a user to change how the hits are being sorted.
4
+ * list of indices or sorting strategies. With Algolia, this is most commonly used for changing
5
+ * ranking strategy. This allows a user to change how the hits are being sorted.
6
+ *
7
+ * This connector supports two sorting modes:
8
+ * 1. **Index-based (traditional)**: Uses the `value` property to switch between different indices.
9
+ * This is the standard behavior for non-composition setups.
10
+ *
11
+ * 2. **Strategy-based (composition mode)**: Uses the `strategy` property to apply sorting strategies
12
+ * via the `sortBy` search parameter. This is only available when using Algolia Compositions.
13
+ *
14
+ * Items can mix both types in the same widget, allowing for flexible sorting options.
6
15
  */
7
- export type SortByItem = {
16
+ export type SortByIndexItem = {
8
17
  /**
9
18
  * The name of the index to target.
10
19
  */
@@ -13,10 +22,30 @@ export type SortByItem = {
13
22
  * The label of the index to display.
14
23
  */
15
24
  label: string;
25
+ /**
26
+ * Ensures mutual exclusivity with strategy.
27
+ */
28
+ strategy?: never;
29
+ };
30
+ export type SortByStrategyItem = {
31
+ /**
32
+ * The name of the sorting strategy to use.
33
+ * Only available in composition mode.
34
+ */
35
+ strategy: string;
36
+ /**
37
+ * The label of the strategy to display.
38
+ */
39
+ label: string;
40
+ /**
41
+ * Ensures mutual exclusivity with value.
42
+ */
43
+ value?: never;
16
44
  };
45
+ export type SortByItem = SortByIndexItem | SortByStrategyItem;
17
46
  export type SortByConnectorParams = {
18
47
  /**
19
- * Array of objects defining the different indices to choose from.
48
+ * Array of objects defining the different indices or strategies to choose from.
20
49
  */
21
50
  items: SortByItem[];
22
51
  /**
@@ -26,19 +55,22 @@ export type SortByConnectorParams = {
26
55
  };
27
56
  export type SortByRenderState = {
28
57
  /**
29
- * The initially selected index.
58
+ * The initially selected index or strategy.
30
59
  */
31
60
  initialIndex?: string;
32
61
  /**
33
- * The currently selected index.
62
+ * The currently selected index or strategy.
34
63
  */
35
64
  currentRefinement: string;
36
65
  /**
37
- * All the available indices
66
+ * All the available indices and strategies
38
67
  */
39
- options: SortByItem[];
68
+ options: Array<{
69
+ value: string;
70
+ label: string;
71
+ }>;
40
72
  /**
41
- * Switches indices and triggers a new search.
73
+ * Switches indices or strategies and triggers a new search.
42
74
  */
43
75
  refine: (value: string) => void;
44
76
  /**
@@ -12,10 +12,33 @@ var withUsage = createDocumentationMessageGenerator({
12
12
 
13
13
  /**
14
14
  * The **SortBy** connector provides the logic to build a custom widget that will display a
15
- * list of indices. With Algolia, this is most commonly used for changing ranking strategy. This allows
16
- * a user to change how the hits are being sorted.
15
+ * list of indices or sorting strategies. With Algolia, this is most commonly used for changing
16
+ * ranking strategy. This allows a user to change how the hits are being sorted.
17
+ *
18
+ * This connector supports two sorting modes:
19
+ * 1. **Index-based (traditional)**: Uses the `value` property to switch between different indices.
20
+ * This is the standard behavior for non-composition setups.
21
+ *
22
+ * 2. **Strategy-based (composition mode)**: Uses the `strategy` property to apply sorting strategies
23
+ * via the `sortBy` search parameter. This is only available when using Algolia Compositions.
24
+ *
25
+ * Items can mix both types in the same widget, allowing for flexible sorting options.
17
26
  */
18
27
 
28
+ function isStrategyItem(item) {
29
+ return 'strategy' in item && item.strategy !== undefined;
30
+ }
31
+ function getItemValue(item) {
32
+ if (isStrategyItem(item)) {
33
+ return item.strategy;
34
+ }
35
+ return item.value;
36
+ }
37
+ function isValidStrategy(itemsLookup, value) {
38
+ if (!value) return false;
39
+ var item = itemsLookup[value];
40
+ return item !== undefined && isStrategyItem(item);
41
+ }
19
42
  var connectSortBy = function connectSortBy(renderFn) {
20
43
  var unmountFn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
21
44
  checkRendering(renderFn, withUsage());
@@ -30,14 +53,38 @@ var connectSortBy = function connectSortBy(renderFn) {
30
53
  if (!Array.isArray(items)) {
31
54
  throw new Error(withUsage('The `items` option expects an array of objects.'));
32
55
  }
56
+ var itemsLookup = {};
57
+ items.forEach(function (item, index) {
58
+ var hasValue = 'value' in item && item.value !== undefined;
59
+ var hasStrategy = 'strategy' in item && item.strategy !== undefined;
60
+
61
+ // Validate mutual exclusivity
62
+ if (hasValue && hasStrategy) {
63
+ throw new Error(withUsage("Item at index ".concat(index, " cannot have both \"value\" and \"strategy\" properties.")));
64
+ }
65
+ if (!hasValue && !hasStrategy) {
66
+ throw new Error(withUsage("Item at index ".concat(index, " must have either a \"value\" or \"strategy\" property.")));
67
+ }
68
+ var itemValue = getItemValue(item);
69
+ itemsLookup[itemValue] = item;
70
+ });
71
+ connectorState.itemsLookup = itemsLookup;
33
72
  return {
34
73
  $$type: 'ais.sortBy',
35
74
  init: function init(initOptions) {
36
75
  var instantSearchInstance = initOptions.instantSearchInstance;
76
+
77
+ // Check if strategies are used outside composition mode
78
+ var hasStrategyItems = items.some(function (item) {
79
+ return 'strategy' in item && item.strategy;
80
+ });
81
+ if (hasStrategyItems && !instantSearchInstance.compositionID) {
82
+ throw new Error(withUsage('Sorting strategies can only be used in composition mode. Please provide a "compositionID" to your InstantSearch instance.'));
83
+ }
37
84
  var widgetRenderState = this.getWidgetRenderState(initOptions);
38
85
  var currentIndex = widgetRenderState.currentRefinement;
39
86
  var isCurrentIndexInItems = find(items, function (item) {
40
- return item.value === currentIndex;
87
+ return getItemValue(item) === currentIndex;
41
88
  });
42
89
  process.env.NODE_ENV === 'development' ? warning(isCurrentIndexInItems !== undefined, "The index named \"".concat(currentIndex, "\" is not listed in the `items` of `sortBy`.")) : void 0;
43
90
  renderFn(_objectSpread(_objectSpread({}, widgetRenderState), {}, {
@@ -53,7 +100,17 @@ var connectSortBy = function connectSortBy(renderFn) {
53
100
  dispose: function dispose(_ref2) {
54
101
  var state = _ref2.state;
55
102
  unmountFn();
56
- return connectorState.initialIndex ? state.setIndex(connectorState.initialIndex) : state;
103
+
104
+ // Clear sortBy parameter if it was set
105
+ if (connectorState.isUsingComposition && state.sortBy) {
106
+ state = state.setQueryParameter('sortBy', undefined);
107
+ }
108
+
109
+ // Restore initial index if changed
110
+ if (connectorState.initialValue && state.index !== connectorState.initialValue) {
111
+ return state.setIndex(connectorState.initialValue);
112
+ }
113
+ return state;
57
114
  },
58
115
  getRenderState: function getRenderState(renderState, renderOptions) {
59
116
  return _objectSpread(_objectSpread({}, renderState), {}, {
@@ -64,22 +121,54 @@ var connectSortBy = function connectSortBy(renderFn) {
64
121
  var results = _ref3.results,
65
122
  helper = _ref3.helper,
66
123
  state = _ref3.state,
67
- parent = _ref3.parent;
68
- if (!connectorState.initialIndex && parent) {
69
- connectorState.initialIndex = parent.getIndexName();
124
+ parent = _ref3.parent,
125
+ instantSearchInstance = _ref3.instantSearchInstance;
126
+ // Capture initial value (composition ID or main index)
127
+ if (!connectorState.initialValue && parent) {
128
+ connectorState.initialValue = parent.getIndexName();
70
129
  }
71
- if (!connectorState.setIndex) {
72
- connectorState.setIndex = function (indexName) {
73
- helper.setIndex(indexName).search();
130
+
131
+ // Create refine function if not exists
132
+ if (!connectorState.refine) {
133
+ // Cache composition mode status for lifecycle methods that don't have access to instantSearchInstance
134
+ connectorState.isUsingComposition = Boolean(instantSearchInstance === null || instantSearchInstance === void 0 ? void 0 : instantSearchInstance.compositionID);
135
+ connectorState.refine = function (value) {
136
+ // O(1) lookup using the items lookup table
137
+ var item = connectorState.itemsLookup[value];
138
+ if (item && isStrategyItem(item)) {
139
+ // Strategy-based: set sortBy parameter for composition API
140
+ // The composition backend will interpret this and apply the sorting strategy
141
+ helper.setQueryParameter('sortBy', item.strategy).search();
142
+ } else {
143
+ // Index-based: clear any existing sortBy parameter and switch to the new index
144
+ // Clearing sortBy is critical when transitioning from strategy to index-based sorting
145
+ helper.setQueryParameter('sortBy', undefined).setIndex(value).search();
146
+ }
74
147
  };
75
148
  }
149
+
150
+ // Transform items first (on original structure)
151
+ var transformedItems = transformItems(items, {
152
+ results: results
153
+ });
154
+
155
+ // Normalize items: all get a 'value' property for the render state
156
+ var normalizedItems = transformedItems.map(function (item) {
157
+ return {
158
+ label: item.label,
159
+ value: getItemValue(item)
160
+ };
161
+ });
162
+
163
+ // Determine current refinement
164
+ // In composition mode, prefer sortBy parameter if it corresponds to a valid strategy item
165
+ // Otherwise use the index (for index-based items or when no valid strategy is active)
166
+ var currentRefinement = connectorState.isUsingComposition && isValidStrategy(connectorState.itemsLookup, state.sortBy) ? state.sortBy : state.index;
76
167
  var hasNoResults = results ? results.nbHits === 0 : true;
77
168
  return {
78
- currentRefinement: state.index,
79
- options: transformItems(items, {
80
- results: results
81
- }),
82
- refine: connectorState.setIndex,
169
+ currentRefinement: currentRefinement,
170
+ options: normalizedItems,
171
+ refine: connectorState.refine,
83
172
  hasNoResults: hasNoResults,
84
173
  canRefine: !hasNoResults && items.length > 0,
85
174
  widgetParams: widgetParams
@@ -87,14 +176,25 @@ var connectSortBy = function connectSortBy(renderFn) {
87
176
  },
88
177
  getWidgetUiState: function getWidgetUiState(uiState, _ref4) {
89
178
  var searchParameters = _ref4.searchParameters;
90
- var currentIndex = searchParameters.index;
179
+ // In composition mode with an active strategy, use sortBy parameter
180
+ // Otherwise use index-based behavior (traditional mode)
181
+ var currentValue = connectorState.isUsingComposition && isValidStrategy(connectorState.itemsLookup, searchParameters.sortBy) ? searchParameters.sortBy : searchParameters.index;
91
182
  return _objectSpread(_objectSpread({}, uiState), {}, {
92
- sortBy: currentIndex !== connectorState.initialIndex ? currentIndex : undefined
183
+ sortBy: currentValue !== connectorState.initialValue ? currentValue : undefined
93
184
  });
94
185
  },
95
186
  getWidgetSearchParameters: function getWidgetSearchParameters(searchParameters, _ref5) {
96
187
  var uiState = _ref5.uiState;
97
- return searchParameters.setQueryParameter('index', uiState.sortBy || connectorState.initialIndex || searchParameters.index);
188
+ var sortByValue = uiState.sortBy || connectorState.initialValue || searchParameters.index;
189
+ if (isValidStrategy(connectorState.itemsLookup, sortByValue)) {
190
+ var item = connectorState.itemsLookup[sortByValue];
191
+ // Strategy-based: set the sortBy parameter for composition API
192
+ // The index remains as the compositionID
193
+ return searchParameters.setQueryParameter('sortBy', item.strategy);
194
+ }
195
+
196
+ // Index-based: set the index parameter (traditional behavior)
197
+ return searchParameters.setQueryParameter('index', sortByValue);
98
198
  }
99
199
  };
100
200
  };
@@ -1,2 +1,2 @@
1
- declare const _default: "4.85.2";
1
+ declare const _default: "4.86.0";
2
2
  export default _default;
package/es/lib/version.js CHANGED
@@ -1 +1 @@
1
- export default '4.85.2';
1
+ export default '4.86.0';
@@ -58,6 +58,14 @@ type AutocompleteWidgetParams<TItem extends BaseHit> = {
58
58
  */
59
59
  storageKey?: string;
60
60
  templates?: Partial<{
61
+ /**
62
+ * Template to use for the header, before the list of items.
63
+ */
64
+ header: Template<{
65
+ items: Array<{
66
+ query: string;
67
+ }>;
68
+ }>;
61
69
  /**
62
70
  * Template to use for each result. This template will receive an object containing a single record.
63
71
  */
@@ -69,6 +77,7 @@ type AutocompleteWidgetParams<TItem extends BaseHit> = {
69
77
  onRemoveRecentSearch: () => void;
70
78
  }>;
71
79
  }>;
80
+ cssClasses?: Partial<AutocompleteIndexClassNames>;
72
81
  };
73
82
  /**
74
83
  * Search parameters to apply to the autocomplete indices.