@k-int/stripes-kint-components 5.22.0 → 5.23.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [5.23.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.22.0...v5.23.0) (2025-06-25)
2
+
3
+
4
+ ### Features
5
+
6
+ * SearchKeyControl subIndexes ([40036a4](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/40036a4353f24e47480e8060d901ef44c5c3b6ed))
7
+
1
8
  # [5.22.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.21.0...v5.22.0) (2025-06-13)
2
9
 
3
10
 
@@ -25,9 +25,12 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
25
25
 
26
26
  // Memoise this process so keyState changes if and only if options/qIndex change
27
27
  const createKeyState = (0, _react.useCallback)(() => options.reduce((acc, curr) => {
28
+ // Is in use if URL contains ALL of the indexes
29
+ const subindexes = (curr?.indexes?.length ?? 0) > 0 ? curr.indexes : [curr.key];
28
30
  acc[curr.key] = {
29
- inUse: qIndexArray?.includes(curr.key),
30
- label: curr.label ?? curr.key
31
+ inUse: subindexes.every(si => qIndexArray.includes(si)),
32
+ label: curr.label ?? curr.key,
33
+ subIndexes: curr.indexes
31
34
  };
32
35
  return acc;
33
36
  }, {}), [options, qIndexArray]);
@@ -48,11 +51,44 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
48
51
  } = (0, _hooks.usePrevNextPagination)({
49
52
  defaultToPageOne: false
50
53
  });
54
+ const handleSearchKeyChange = (0, _react.useCallback)(_ref2 => {
55
+ let {
56
+ e: {
57
+ target: {
58
+ checked: targetIsChecked
59
+ } = {}
60
+ } = {},
61
+ key,
62
+ value
63
+ } = _ref2;
64
+ // Set up which indexes need to change
65
+ const indicesToChange = (value?.subIndexes?.length ?? 0) > 0 ? value.subIndexes : [key];
66
+
67
+ // If false, we must remove from the qIndex
68
+ if (!targetIsChecked) {
69
+ indicesToChange.forEach(ind => {
70
+ const indexOfKey = qIndexArray.indexOf(ind);
71
+ if (indexOfKey > -1) {
72
+ // only splice array when item is found
73
+ qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
74
+ }
75
+ });
76
+ } else {
77
+ // If true, we need to add to qIndex
78
+ indicesToChange.forEach(ind => {
79
+ qIndexArray.push(ind);
80
+ });
81
+ }
82
+ setQIndex(qIndexArray?.join(','));
83
+ if (currentPage) {
84
+ resetPage();
85
+ }
86
+ }, [currentPage, qIndexArray, resetPage, setQIndex]);
51
87
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
52
88
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
53
89
  className: _SearchKeyControl.default.searchKeyControlContainer,
54
- children: Object.entries(keyState).map(_ref2 => {
55
- let [key, value] = _ref2;
90
+ children: Object.entries(keyState).map(_ref3 => {
91
+ let [key, value] = _ref3;
56
92
  /* At this point we have "key" corresponding to a searchKey option,
57
93
  * and "value" an object of the shape
58
94
  {
@@ -64,23 +100,11 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
64
100
  checked: value?.inUse,
65
101
  className: _SearchKeyControl.default.searchKeyControlElement,
66
102
  label: value?.label,
67
- onChange: e => {
68
- // If false, we must remove from the qIndex
69
- if (!e.target.checked) {
70
- const indexOfKey = qIndexArray.indexOf(key);
71
- if (indexOfKey > -1) {
72
- // only splice array when item is found
73
- qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
74
- }
75
- } else {
76
- // If true, we need to add to qIndex
77
- qIndexArray.push(key);
78
- }
79
- setQIndex(qIndexArray?.join(','));
80
- if (currentPage) {
81
- resetPage();
82
- }
83
- }
103
+ onChange: e => handleSearchKeyChange({
104
+ e,
105
+ key,
106
+ value
107
+ })
84
108
  }, `search-key-control-${key}`);
85
109
  })
86
110
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-int/stripes-kint-components",
3
- "version": "5.22.0",
3
+ "version": "5.23.0",
4
4
  "description": "Stripes Component library for K-Int specific applications",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -1,12 +1,14 @@
1
1
  # SearchKeyControl
2
2
 
3
- A component that renders a group of checkboxes allowing users to select one or more search keys (indexes). It directly interacts with the `qIndex` URL parameter (managed via the `useQIndex` hook) to reflect the currently active search indexes.
3
+ A component that renders a group of checkboxes allowing users to select one or more search keys (indexes). It synchronizes its state with the `qIndex` URL parameter (managed via the `useQIndex` hook), updating this parameter by injecting or removing individual keys based on user interactions.
4
4
 
5
- > **Note:** This component is designed to be controlled by the `qIndex` URL parameter rather than receiving explicit `value` or `onChange` props for the entire set of keys. It modifies the `qIndex` parameter by adding or removing individual keys based on checkbox interactions.
5
+ > **Note:** This component is **controlled by the `qIndex` URL parameter** rather than external props like `value` or `onChange`. When users interact with checkboxes, the component modifies the `qIndex` by adding or removing search keys accordingly. It **does not** replace the entire `qIndex` value.
6
+
7
+ ---
6
8
 
7
9
  ## Basic Usage
8
10
 
9
- Below is an example demonstrating how to use **SearchKeyControl**. You provide an array of `options`, each defining a potential search key with a label. The component automatically reads the current `qIndex` from the URL, determines the checked state of each checkbox, and updates the `qIndex` when checkboxes are toggled.
11
+ Provide an `options` array with each search key to be rendered. Each option includes a `key`, a display `label`, and optionally, an `indexes` array to represent multiple underlying sub-indexes.
10
12
 
11
13
  ```jsx
12
14
  import { SearchKeyControl } from '@k-int/stripes-kint-components';
@@ -16,55 +18,78 @@ const MySearchForm = () => {
16
18
  { key: 'keyword', label: 'Keyword' },
17
19
  { key: 'title', label: 'Title' },
18
20
  { key: 'author', label: 'Author' },
19
- { key: 'subject', label: 'Subject' }
21
+ {
22
+ key: 'subjectPlus',
23
+ label: 'Subject (incl. subdivisions)',
24
+ indexes: ['subject', 'subdivision']
25
+ }
20
26
  ];
21
27
 
22
28
  return (
23
29
  <div>
24
- {/* ... other search form elements ... */}
25
30
  <label>Search Indexes:</label>
26
31
  <SearchKeyControl options={searchKeyOptions} />
27
- {/* ... submit button etc ... */}
28
32
  </div>
29
33
  );
30
34
  };
31
35
 
32
36
  export default MySearchForm;
33
- ````
37
+ ```
38
+
39
+ In this example:
34
40
 
35
- In this example, **SearchKeyControl**:
41
+ * **Checkboxes** are rendered for each `key`, including the composite `subjectPlus`.
42
+ * The `qIndex` URL parameter (e.g. `?qIndex=title,author`) determines the checked state.
43
+ * Clicking a checkbox adds or removes the relevant index or indexes:
36
44
 
37
- - Renders checkboxes for "Keyword", "Title", "Author", and "Subject".
38
- - Reads the `qIndex` URL parameter (e.g., `?qIndex=keyword,title`).
39
- - Sets the "Keyword" and "Title" checkboxes as checked based on the example `qIndex`.
40
- - If the user clicks the "Author" checkbox, the component updates the URL parameter to `?qIndex=keyword,title,author`.
41
- - If the user then unclicks the "Keyword" checkbox, the URL parameter becomes `?qIndex=title,author`.
42
- - It also automatically resets pagination (via `usePrevNextPagination`) when the `qIndex` changes.
45
+ * For simple entries like `"title"`, toggling directly modifies `qIndex`.
46
+ * For composite entries like `"subjectPlus"`, checking adds both `"subject"` and `"subdivision"`; unchecking removes both.
47
+ * **Pagination is reset** automatically via `usePrevNextPagination()` when the `qIndex` changes.
48
+
49
+ ---
43
50
 
44
51
  ## Props
45
52
 
46
- | Name | Type | Description | Default | Required |
47
- |:----------|:------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
48
- | `options` | `arrayOf(object)` | An array of objects defining the available search keys. Each object must have: <br>• `key` (string): The value representing the search index. <br/>• `label` (string \| node): The text or element displayed next to the checkbox. | `[]` | ✕ |
53
+ | Name | Type | Description | Default | Required |
54
+ | :-------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------ | :------- |
55
+ | `options` | `arrayOf(object)` | Array of search key definitions. Each object must include:<br>• `key` (string): A unique identifier.<br>• `label` (string \| node): The displayed label.<br>• `indexes` (array of strings, optional): One or more sub-indexes that this checkbox controls (defaults to `[key]` if omitted). | `[]` | ✕ |
49
56
 
57
+ ---
50
58
 
51
59
  ## How It Works
52
60
 
53
- 1. **Reading Search Index State:**
54
- The component uses the `useQIndex` hook to read the current `qIndex` URL parameter, which is expected to be a comma-separated string of active search keys (e.g., `"keyword,title"`). It memoizes this string split into an array (`qIndexArray`).
61
+ ### 1. Reading Search Index State
62
+
63
+ The component uses `useQIndex()` to retrieve and manage the `qIndex` URL parameter. The value is split into an array of active keys (`qIndexArray`), trimmed for whitespace and memoized.
64
+
65
+ ### 2. Creating Internal State
66
+
67
+ A `keyState` object maps each option's `key` to:
68
+
69
+ * `inUse`: A boolean indicating whether all of the entry’s `indexes` are present in `qIndexArray`.
70
+ * `label`: The label to display next to the checkbox.
71
+ * `subIndexes`: The underlying `indexes` array (or `[key]` if not specified).
72
+
73
+ This mapping is memoized via `useCallback()` and kept in sync with URL state using `useEffect()`.
74
+
75
+ ### 3. Rendering Checkboxes
76
+
77
+ Each `keyState` entry is rendered as a checkbox using `@folio/stripes/components`' `Checkbox`. Its `checked` state is tied to `inUse`, and its `label` comes from the `label` field.
78
+
79
+ ### 4. Handling Changes
80
+
81
+ On user interaction:
82
+
83
+ * If a checkbox is checked:
84
+
85
+ * The component **adds** all `subIndexes` for the key to the `qIndexArray`.
86
+ * If a checkbox is unchecked:
55
87
 
56
- 2. **Determining Checkbox State:**
57
- It uses `useState`, `useEffect`, `useMemo`, and `useCallback` along with `lodash/isEqual` to create and maintain an internal `keyState` object. This object maps each `key` from the `options` prop to its display `label` and an `inUse` boolean flag. The `inUse` flag is determined by checking if the option's `key` exists within the current `qIndexArray` derived from the `qIndex` URL parameter. This ensures the checkboxes visually reflect the state in the URL.
88
+ * The component **removes** all `subIndexes` for the key from `qIndexArray`.
58
89
 
59
- 3. **Rendering Checkboxes:**
60
- The component iterates over the `keyState` object and renders a `@folio/stripes/components` `Checkbox` for each entry. The `checked` prop of the checkbox is bound to the `inUse` flag from `keyState`, and the `label` is set accordingly.
90
+ The updated array is joined back into a comma-separated string and passed to `setQIndex()` to update the URL.
61
91
 
62
- 4. **Handling Changes:**
63
- When a checkbox's state is changed by the user:
92
+ ### 5. Pagination Reset
64
93
 
65
- * The `onChange` handler determines if the key should be added or removed from the list of active keys.
66
- * It updates a temporary copy of the `qIndexArray` (either adding the key with `push` or removing it with `splice`).
67
- * It calls `setQIndex` (from `useQIndex`) with the modified array joined back into a comma-separated string, which updates the URL parameter.
94
+ If the current page is non-zero (via `usePrevNextPagination`), the component calls `resetPage()` after modifying `qIndex`. This ensures consistency in result navigation after a filter change.
68
95
 
69
- 5. **Pagination Reset:**
70
- The component also uses the `usePrevNextPagination` hook. If pagination is active (`currentPage` has a value), it calls `resetPage()` after updating the `qIndex`. This ensures that users are returned to the first page of results when the search indexes change, preventing potentially invalid page numbers for the new search scope.
@@ -23,7 +23,14 @@ const SearchKeyControl = ({
23
23
  // Memoise this process so keyState changes if and only if options/qIndex change
24
24
  const createKeyState = useCallback(() => (
25
25
  options.reduce((acc, curr) => {
26
- acc[curr.key] = { inUse: qIndexArray?.includes(curr.key), label: curr.label ?? curr.key };
26
+ // Is in use if URL contains ALL of the indexes
27
+ const subindexes = ((curr?.indexes?.length ?? 0) > 0) ? curr.indexes : [curr.key];
28
+
29
+ acc[curr.key] = {
30
+ inUse: subindexes.every(si => qIndexArray.includes(si)),
31
+ label: curr.label ?? curr.key,
32
+ subIndexes: curr.indexes
33
+ };
27
34
  return acc;
28
35
  }, {})
29
36
  ), [options, qIndexArray]);
@@ -41,6 +48,31 @@ const SearchKeyControl = ({
41
48
  // Check to see if page param exists, and if it does then changing qIndex should reset it
42
49
  const { currentPage, resetPage } = usePrevNextPagination({ defaultToPageOne: false });
43
50
 
51
+ const handleSearchKeyChange = useCallback(({ e: { target: { checked: targetIsChecked } = {} } = {}, key, value }) => {
52
+ // Set up which indexes need to change
53
+ const indicesToChange = ((value?.subIndexes?.length ?? 0) > 0) ? value.subIndexes : [key];
54
+
55
+ // If false, we must remove from the qIndex
56
+ if (!targetIsChecked) {
57
+ indicesToChange.forEach(ind => {
58
+ const indexOfKey = qIndexArray.indexOf(ind);
59
+ if (indexOfKey > -1) { // only splice array when item is found
60
+ qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
61
+ }
62
+ });
63
+ } else {
64
+ // If true, we need to add to qIndex
65
+ indicesToChange.forEach(ind => {
66
+ qIndexArray.push(ind);
67
+ });
68
+ }
69
+
70
+ setQIndex(qIndexArray?.join(','));
71
+ if (currentPage) {
72
+ resetPage();
73
+ }
74
+ }, [currentPage, qIndexArray, resetPage, setQIndex]);
75
+
44
76
  return (
45
77
  <>
46
78
  <div className={css.searchKeyControlContainer}>
@@ -59,23 +91,7 @@ const SearchKeyControl = ({
59
91
  checked={value?.inUse}
60
92
  className={css.searchKeyControlElement}
61
93
  label={value?.label}
62
- onChange={e => {
63
- // If false, we must remove from the qIndex
64
- if (!e.target.checked) {
65
- const indexOfKey = qIndexArray.indexOf(key);
66
- if (indexOfKey > -1) { // only splice array when item is found
67
- qIndexArray.splice(indexOfKey, 1); // 2nd parameter means remove one item only
68
- }
69
- } else {
70
- // If true, we need to add to qIndex
71
- qIndexArray.push(key);
72
- }
73
-
74
- setQIndex(qIndexArray?.join(','));
75
- if (currentPage) {
76
- resetPage();
77
- }
78
- }}
94
+ onChange={e => handleSearchKeyChange({ e, key, value })}
79
95
  />
80
96
  );
81
97
  })