@k-int/stripes-kint-components 5.3.1 → 5.5.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,23 @@
1
+ # [5.5.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.4.0...v5.5.0) (2024-03-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * FormModal handleSUbmit clears form ([2abf0dd](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/2abf0dddd042a0ea79970aa09b01ecca073d5705))
7
+
8
+
9
+ ### Features
10
+
11
+ * SASQLookupComponent rowNavigation ([57aa5d2](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/57aa5d231f2700e2930ff26726d8445d03bd9001))
12
+
13
+ # [5.4.0](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.3.1...v5.4.0) (2024-02-29)
14
+
15
+
16
+ ### Features
17
+
18
+ * EditSettingValue: refdata default sort ([9e7f530](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/9e7f5301bb43050ba263b303e0e05462b1268a8a))
19
+ * matchString and highlightString improvements ([83f98e9](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/commit/83f98e9ebc678c3a893504a9959d35f9e1318b7b))
20
+
1
21
  ## [5.3.1](https://gitlab.com/knowledge-integration/folio/stripes-kint-components/compare/v5.3.0...v5.3.1) (2024-01-26)
2
22
 
3
23
 
@@ -37,10 +37,14 @@ const EditSettingValue = props => {
37
37
  if (refdata.length > 0 && refdata.length <= 4) {
38
38
  RefdataComponent = _RefdataButtons.default;
39
39
  }
40
+
41
+ // Adding default sort to refdata object in ascending order by label
42
+ const sortByLabel = (a, b) => a.label.localeCompare(b.label);
43
+ const sortedRefdata = refdata.sort(sortByLabel);
40
44
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactFinalForm.Field, {
41
45
  "aria-label": fieldLabel,
42
46
  component: RefdataComponent,
43
- dataOptions: refdata,
47
+ dataOptions: sortedRefdata,
44
48
  name: "".concat(input.name, ".value")
45
49
  });
46
50
  case 'Password':
@@ -41,12 +41,17 @@ const FormModal = _ref => {
41
41
  onClose(e);
42
42
  restart();
43
43
  };
44
+ const handleSaveAndClear = function () {
45
+ handleSubmit(...arguments);
46
+ restart();
47
+ };
44
48
  const renderFooter = () => {
45
49
  if (footer) {
46
50
  return footer({
47
51
  formState,
48
- handleSubmit,
49
- handleClose
52
+ handleSubmit: handleSaveAndClear,
53
+ handleClose,
54
+ handleSubmitNoRestart: handleSubmit
50
55
  });
51
56
  }
52
57
  const {
@@ -59,7 +64,7 @@ const FormModal = _ref => {
59
64
  buttonStyle: "primary",
60
65
  disabled: submitting || invalid || pristine,
61
66
  marginBottom0: true,
62
- onClick: handleSubmit,
67
+ onClick: handleSaveAndClear,
63
68
  type: "submit",
64
69
  children: kintIntl.formatKintMessage({
65
70
  id: 'saveAndClose',
@@ -75,15 +80,12 @@ const FormModal = _ref => {
75
80
  })]
76
81
  });
77
82
  };
78
- return /*#__PURE__*/(0, _jsxRuntime.jsx)("form", {
79
- onSubmit: handleSubmit,
80
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Modal, {
81
- enforceFocus: false,
82
- footer: renderFooter(),
83
- onClose: handleClose,
84
- ...modalProps,
85
- children: children
86
- })
83
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Modal, {
84
+ enforceFocus: false,
85
+ footer: renderFooter(),
86
+ onClose: handleClose,
87
+ ...modalProps,
88
+ children: children
87
89
  });
88
90
  }
89
91
  });
@@ -32,6 +32,8 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
32
32
  noSearchField,
33
33
  persistedPanesetProps = {},
34
34
  RenderBody,
35
+ rowNavigation = true,
36
+ // Default navigation onRowClick
35
37
  sasqProps,
36
38
  searchFieldAriaLabel,
37
39
  searchFieldProps
@@ -221,6 +223,7 @@ const SASQLookupComponent = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =>
221
223
  intlNS: passedIntlNS,
222
224
  labelOverrides: labelOverrides,
223
225
  query: query,
226
+ rowNavigation: rowNavigation,
224
227
  toggleFilterPane: toggleFilterPane,
225
228
  ...restOfInfiniteQueryProps,
226
229
  ...sasqRenderProps,
@@ -253,6 +256,7 @@ SASQLookupComponent.propTypes = {
253
256
  RenderBody: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]),
254
257
  resource: _propTypes.default.object,
255
258
  resultColumns: _propTypes.default.arrayOf(_propTypes.default.object),
259
+ rowNavigation: _propTypes.default.bool,
256
260
  sasqProps: _propTypes.default.object,
257
261
  searchFieldAriaLabel: _propTypes.default.string,
258
262
  searchFieldProps: _propTypes.default.object
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ var _react = require("react");
7
8
  var _propTypes = _interopRequireDefault(require("prop-types"));
8
9
  var _reactRouterDom = require("react-router-dom");
9
10
  var _components = require("@folio/stripes/components");
@@ -11,7 +12,7 @@ var _NoResultsMessage = _interopRequireDefault(require("../../NoResultsMessage")
11
12
  var _jsxRuntime = require("react/jsx-runtime");
12
13
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
14
  const TableBody = _ref => {
14
- var _query$sort;
15
+ var _query$sort, _match$params2;
15
16
  let {
16
17
  data,
17
18
  error,
@@ -23,10 +24,15 @@ const TableBody = _ref => {
23
24
  isLoading,
24
25
  labelOverrides = {},
25
26
  match,
26
- mclProps,
27
+ mclProps: {
28
+ formatter = {},
29
+ ...mclProps
30
+ },
27
31
  onSort,
28
32
  path,
29
33
  resultColumns,
34
+ rowNavigation = true,
35
+ // Default navigation onRowClick
30
36
  toggleFilterPane,
31
37
  query
32
38
  } = _ref;
@@ -44,11 +50,48 @@ const TableBody = _ref => {
44
50
 
45
51
  // Build the list of visible columns
46
52
  const visibleColumns = resultColumns.map(e => e.propertyPath);
53
+ const getRowUrl = (0, _react.useCallback)(rowData => {
54
+ const baseUrl = "".concat(path, "/").concat(rowData === null || rowData === void 0 ? void 0 : rowData.id);
55
+ return {
56
+ url: "".concat(baseUrl).concat(location === null || location === void 0 ? void 0 : location.search),
57
+ path,
58
+ baseUrl,
59
+ location
60
+ };
61
+ }, [location, path]);
62
+ const getEnhancedFormatter = (0, _react.useCallback)(() => {
63
+ const enhancedFormatter = {};
64
+ for (const [key, value] of Object.entries(formatter)) {
65
+ enhancedFormatter[key] = item => value({
66
+ ...item,
67
+ defaultRowUrl: getRowUrl(item)
68
+ });
69
+ }
70
+ return enhancedFormatter;
71
+ }, [formatter, getRowUrl]);
72
+ const getOnRowClick = (0, _react.useCallback)(() => {
73
+ if (rowNavigation) {
74
+ return (_e, rowData) => {
75
+ history.push(getRowUrl(rowData).url);
76
+ };
77
+ }
78
+ return null;
79
+ }, [getRowUrl, history, rowNavigation]);
80
+ const isSelected = (0, _react.useCallback)(_ref2 => {
81
+ var _match$params;
82
+ let {
83
+ item
84
+ } = _ref2;
85
+ return item.id === (match === null || match === void 0 || (_match$params = match.params) === null || _match$params === void 0 ? void 0 : _match$params.id);
86
+ }, [match === null || match === void 0 || (_match$params2 = match.params) === null || _match$params2 === void 0 ? void 0 : _match$params2.id]);
47
87
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.MultiColumnList, {
48
88
  autosize: true,
49
89
  columnMapping: columnMapping,
50
90
  contentData: data === null || data === void 0 ? void 0 : data.results,
91
+ formatter: getEnhancedFormatter() // Pass enhanced formatter
92
+ ,
51
93
  hasMargin: true,
94
+ interactive: rowNavigation,
52
95
  isEmptyMessage: /*#__PURE__*/(0, _jsxRuntime.jsx)(_NoResultsMessage.default, {
53
96
  error,
54
97
  filterPaneIsVisible: filterPaneVisible,
@@ -60,18 +103,10 @@ const TableBody = _ref => {
60
103
  searchTerm: query.query,
61
104
  toggleFilterPane
62
105
  }),
63
- isSelected: _ref2 => {
64
- var _match$params;
65
- let {
66
- item
67
- } = _ref2;
68
- return item.id === (match === null || match === void 0 || (_match$params = match.params) === null || _match$params === void 0 ? void 0 : _match$params.id);
69
- },
106
+ isSelected: isSelected,
70
107
  onHeaderClick: onSort,
71
108
  onNeedMoreData: onNeedMoreData,
72
- onRowClick: (_e, rowData) => {
73
- history.push("".concat(path, "/").concat(rowData === null || rowData === void 0 ? void 0 : rowData.id).concat(location === null || location === void 0 ? void 0 : location.search));
74
- },
109
+ onRowClick: getOnRowClick(),
75
110
  pagingType: "click",
76
111
  sortDirection: sortOrder.startsWith('-') ? 'descending' : 'ascending',
77
112
  sortOrder: sortOrder.replace(/^-/, '').replace(/,.*/, ''),
@@ -101,6 +136,7 @@ TableBody.propTypes = {
101
136
  path: _propTypes.default.string.isRequired,
102
137
  query: _propTypes.default.object,
103
138
  resultColumns: _propTypes.default.arrayOf(_propTypes.default.object),
139
+ rowNavigation: _propTypes.default.bool,
104
140
  toggleFilterPane: _propTypes.default.func
105
141
  };
106
142
  var _default = exports.default = TableBody;
@@ -9,21 +9,37 @@ var _jsxRuntime = require("react/jsx-runtime");
9
9
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
10
  const highlightString = function (match, str) {
11
11
  let ignoreNull = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
12
- const [parts, regex] = (0, _matchString.default)(match, str, ignoreNull);
13
- return parts.filter(part => part).map((part, i) => regex.test(part) ? /*#__PURE__*/(0, _jsxRuntime.jsx)("mark", {
14
- children: part
15
- }, i) : /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
16
- children: part
17
- }, i));
12
+ let simpleSplit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
13
+ const [parts, regex] = (0, _matchString.default)(match, str, ignoreNull, simpleSplit);
14
+ return parts.map((part, i) => {
15
+ // RegExp is stateful, set up a new one to work with
16
+ const immutableRegex = new RegExp(regex);
17
+ if (immutableRegex.exec(part) !== null) {
18
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("mark", {
19
+ children: part
20
+ }, i);
21
+ }
22
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
23
+ children: part
24
+ }, i);
25
+ });
18
26
  };
19
27
  exports.highlightString = highlightString;
20
28
  const boldString = function (match, str) {
21
29
  let ignoreNull = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
22
- const [parts, regex] = (0, _matchString.default)(match, str, ignoreNull);
23
- return parts.filter(part => part).map((part, i) => regex.test(part) ? /*#__PURE__*/(0, _jsxRuntime.jsx)("strong", {
24
- children: part
25
- }, i) : /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
26
- children: part
27
- }, i));
30
+ let simpleSplit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
31
+ const [parts, regex] = (0, _matchString.default)(match, str, ignoreNull, simpleSplit);
32
+ return parts.map((part, i) => {
33
+ // RegExp is stateful, set up a new one to work with
34
+ const immutableRegex = new RegExp(regex);
35
+ if (immutableRegex.exec(part) !== null) {
36
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("strong", {
37
+ children: part
38
+ }, i);
39
+ }
40
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
41
+ children: part
42
+ }, i);
43
+ });
28
44
  };
29
45
  exports.boldString = boldString;
@@ -7,13 +7,28 @@ exports.default = void 0;
7
7
  var _escapeRegExp = _interopRequireDefault(require("lodash/escapeRegExp"));
8
8
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
9
  const matchString = function (match, str) {
10
+ var _str$split2;
10
11
  let ignoreNull = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
11
- const regex = new RegExp("".concat(match.split(/(\s+)/).filter(h => h.trim()).map(hl => '(' + (0, _escapeRegExp.default)(hl) + ')').join('|')), 'gi');
12
+ let simpleSplit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
13
+ // Simple regex split -- this is default behaviour
14
+ const regexSimple = new RegExp("".concat(match.split(/(\s+)/).filter(h => h.trim()).map(hl => '(' + (0, _escapeRegExp.default)(hl) + ')').join('|')), 'gi');
15
+
16
+ // Split Elivis "The King" Presley into [Elvis, The King, Presley]
17
+ const regex = new RegExp("".concat(match.split(/(?!\B"[^"]*)\s+(?![^"]*"\B)/).filter(h => h.trim()).map(quotedSection => {
18
+ if (quotedSection.charAt(0) === '"' && quotedSection.charAt(quotedSection.length - 1) === '"') {
19
+ return quotedSection.slice(1, quotedSection.length - 1);
20
+ }
21
+ return quotedSection;
22
+ }).map(hl => '(' + (0, _escapeRegExp.default)(hl) + ')').join('|')), 'gi');
12
23
  if (ignoreNull && !match) {
13
24
  const nullRegex = /a^/gi; // Should match nothing
14
25
 
15
26
  return [[str], nullRegex];
16
27
  }
17
- return [str.split(regex), regex];
28
+ if (simpleSplit) {
29
+ var _str$split;
30
+ return [(_str$split = str.split(regexSimple)) === null || _str$split === void 0 ? void 0 : _str$split.filter(s => s && s.trim()), regexSimple];
31
+ }
32
+ return [(_str$split2 = str.split(regex)) === null || _str$split2 === void 0 ? void 0 : _str$split2.filter(s => s && s.trim()), regex];
18
33
  };
19
34
  var _default = exports.default = matchString;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-int/stripes-kint-components",
3
- "version": "5.3.1",
3
+ "version": "5.5.0",
4
4
  "description": "Stripes Component library for K-Int specific applications",
5
5
  "sideEffects": [
6
6
  "*.css"
@@ -52,7 +52,7 @@
52
52
  "@semantic-release/npm": "^11.0.0",
53
53
  "babel-eslint": "^10.1.0",
54
54
  "babel-plugin-const-enum": "^1.0.1",
55
- "babel-plugin-module-resolver": "^4.0.0",
55
+ "babel-plugin-module-resolver": "^5.0.0",
56
56
  "babel-plugin-require-context-hook": "^1.0.0",
57
57
  "babel-plugin-transform-async-to-promises": "^0.8.15",
58
58
  "babel-polyfill": "^6.26.0",
@@ -74,7 +74,7 @@
74
74
  "react-router-dom": "^5.2.0",
75
75
  "redux": "^4.0.0",
76
76
  "redux-observable": "^1.2.0",
77
- "regenerator-runtime": "^0.13.3",
77
+ "regenerator-runtime": "^0.14.0",
78
78
  "rxjs": "^6.6.3",
79
79
  "semantic-release": "^22.0.6",
80
80
  "sinon": "^14.0.0",
package/renovate.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": [
4
+ "config:base"
5
+ ]
6
+ }
@@ -36,11 +36,15 @@ const EditSettingValue = (props) => {
36
36
  RefdataComponent = RefdataButtons;
37
37
  }
38
38
 
39
+ // Adding default sort to refdata object in ascending order by label
40
+ const sortByLabel = (a, b) => (a.label.localeCompare(b.label));
41
+ const sortedRefdata = refdata.sort(sortByLabel);
42
+
39
43
  return (
40
44
  <Field
41
45
  aria-label={fieldLabel}
42
46
  component={RefdataComponent}
43
- dataOptions={refdata}
47
+ dataOptions={sortedRefdata}
44
48
  name={`${input.name}.value`}
45
49
  />
46
50
  );
@@ -27,9 +27,14 @@ const FormModal = ({
27
27
  restart();
28
28
  };
29
29
 
30
+ const handleSaveAndClear = (...onSaveProps) => {
31
+ handleSubmit(...onSaveProps);
32
+ restart();
33
+ };
34
+
30
35
  const renderFooter = () => {
31
36
  if (footer) {
32
- return footer({ formState, handleSubmit, handleClose });
37
+ return footer({ formState, handleSubmit: handleSaveAndClear, handleClose, handleSubmitNoRestart: handleSubmit });
33
38
  }
34
39
 
35
40
  const { invalid, pristine, submitting } = formState;
@@ -39,7 +44,7 @@ const FormModal = ({
39
44
  buttonStyle="primary"
40
45
  disabled={submitting || invalid || pristine}
41
46
  marginBottom0
42
- onClick={handleSubmit}
47
+ onClick={handleSaveAndClear}
43
48
  type="submit"
44
49
  >
45
50
  {kintIntl.formatKintMessage({
@@ -61,18 +66,14 @@ const FormModal = ({
61
66
  };
62
67
 
63
68
  return (
64
- <form
65
- onSubmit={handleSubmit}
69
+ <Modal
70
+ enforceFocus={false}
71
+ footer={renderFooter()}
72
+ onClose={handleClose}
73
+ {...modalProps}
66
74
  >
67
- <Modal
68
- enforceFocus={false}
69
- footer={renderFooter()}
70
- onClose={handleClose}
71
- {...modalProps}
72
- >
73
- {children}
74
- </Modal>
75
- </form>
75
+ {children}
76
+ </Modal>
76
77
  );
77
78
  }}
78
79
  </Form>
@@ -44,6 +44,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
44
44
  noSearchField,
45
45
  persistedPanesetProps = {},
46
46
  RenderBody,
47
+ rowNavigation = true, // Default navigation onRowClick
47
48
  sasqProps,
48
49
  searchFieldAriaLabel,
49
50
  searchFieldProps
@@ -266,6 +267,7 @@ const SASQLookupComponent = forwardRef((props, ref) => {
266
267
  intlNS={passedIntlNS}
267
268
  labelOverrides={labelOverrides}
268
269
  query={query}
270
+ rowNavigation={rowNavigation}
269
271
  toggleFilterPane={toggleFilterPane}
270
272
  {...restOfInfiniteQueryProps}
271
273
  {...sasqRenderProps}
@@ -320,6 +322,7 @@ SASQLookupComponent.propTypes = {
320
322
  ]),
321
323
  resource: PropTypes.object,
322
324
  resultColumns: PropTypes.arrayOf(PropTypes.object),
325
+ rowNavigation: PropTypes.bool,
323
326
  sasqProps: PropTypes.object,
324
327
  searchFieldAriaLabel: PropTypes.string,
325
328
  searchFieldProps: PropTypes.object
@@ -1,3 +1,4 @@
1
+ import { useCallback } from 'react';
1
2
  import PropTypes from 'prop-types';
2
3
 
3
4
  import { useHistory, useLocation } from 'react-router-dom';
@@ -18,10 +19,14 @@ const TableBody = ({
18
19
  isLoading,
19
20
  labelOverrides = {},
20
21
  match,
21
- mclProps,
22
+ mclProps: {
23
+ formatter = {},
24
+ ...mclProps
25
+ },
22
26
  onSort,
23
27
  path,
24
28
  resultColumns,
29
+ rowNavigation = true, // Default navigation onRowClick
25
30
  toggleFilterPane,
26
31
  query,
27
32
  }) => {
@@ -41,12 +46,45 @@ const TableBody = ({
41
46
  // Build the list of visible columns
42
47
  const visibleColumns = resultColumns.map(e => e.propertyPath);
43
48
 
49
+ const getRowUrl = useCallback((rowData) => {
50
+ const baseUrl = `${path}/${rowData?.id}`;
51
+ return {
52
+ url: `${baseUrl}${location?.search}`,
53
+ path,
54
+ baseUrl,
55
+ location
56
+ };
57
+ }, [location, path]);
58
+
59
+ const getEnhancedFormatter = useCallback(() => {
60
+ const enhancedFormatter = {};
61
+ for (const [key, value] of Object.entries(formatter)) {
62
+ enhancedFormatter[key] = (item) => value({ ...item, defaultRowUrl: getRowUrl(item) });
63
+ }
64
+
65
+ return enhancedFormatter;
66
+ }, [formatter, getRowUrl]);
67
+
68
+ const getOnRowClick = useCallback(() => {
69
+ if (rowNavigation) {
70
+ return (_e, rowData) => {
71
+ history.push(getRowUrl(rowData).url);
72
+ };
73
+ }
74
+
75
+ return null;
76
+ }, [getRowUrl, history, rowNavigation]);
77
+
78
+ const isSelected = useCallback(({ item }) => item.id === match?.params?.id, [match?.params?.id]);
79
+
44
80
  return (
45
81
  <MultiColumnList
46
82
  autosize
47
83
  columnMapping={columnMapping}
48
84
  contentData={data?.results}
85
+ formatter={getEnhancedFormatter()} // Pass enhanced formatter
49
86
  hasMargin
87
+ interactive={rowNavigation}
50
88
  isEmptyMessage={
51
89
  <NoResultsMessage
52
90
  {...{
@@ -62,12 +100,10 @@ const TableBody = ({
62
100
  }}
63
101
  />
64
102
  }
65
- isSelected={({ item }) => item.id === match?.params?.id}
103
+ isSelected={isSelected}
66
104
  onHeaderClick={onSort}
67
105
  onNeedMoreData={onNeedMoreData}
68
- onRowClick={(_e, rowData) => {
69
- history.push(`${path}/${rowData?.id}${location?.search}`);
70
- }}
106
+ onRowClick={getOnRowClick()}
71
107
  pagingType="click"
72
108
  sortDirection={sortOrder.startsWith('-') ? 'descending' : 'ascending'}
73
109
  sortOrder={sortOrder.replace(/^-/, '').replace(/,.*/, '')}
@@ -99,6 +135,7 @@ TableBody.propTypes = {
99
135
  path: PropTypes.string.isRequired,
100
136
  query: PropTypes.object,
101
137
  resultColumns: PropTypes.arrayOf(PropTypes.object),
138
+ rowNavigation: PropTypes.bool,
102
139
  toggleFilterPane: PropTypes.func
103
140
  };
104
141
 
@@ -1,38 +1,54 @@
1
1
  import matchString from './matchString';
2
2
 
3
- const highlightString = (match, str, ignoreNull = true) => {
4
- const [parts, regex] = matchString(match, str, ignoreNull);
3
+ const highlightString = (match, str, ignoreNull = true, simpleSplit = true) => {
4
+ const [parts, regex] = matchString(match, str, ignoreNull, simpleSplit);
5
5
 
6
6
  return (
7
- parts.filter(part => part).map((part, i) => (
8
- regex.test(part) ?
9
- <mark
10
- key={i}
11
- >
12
- {part}
13
- </mark> :
7
+ parts.map((part, i) => {
8
+ // RegExp is stateful, set up a new one to work with
9
+ const immutableRegex = new RegExp(regex);
10
+ if (immutableRegex.exec(part) !== null) {
11
+ return (
12
+ <mark
13
+ key={i}
14
+ >
15
+ {part}
16
+ </mark>
17
+ );
18
+ }
19
+
20
+ return (
14
21
  <span key={i}>
15
22
  {part}
16
23
  </span>
17
- ))
24
+ );
25
+ })
18
26
  );
19
27
  };
20
28
 
21
- const boldString = (match, str, ignoreNull = true) => {
22
- const [parts, regex] = matchString(match, str, ignoreNull);
29
+ const boldString = (match, str, ignoreNull = true, simpleSplit = true) => {
30
+ const [parts, regex] = matchString(match, str, ignoreNull, simpleSplit);
23
31
 
24
32
  return (
25
- parts.filter(part => part).map((part, i) => (
26
- regex.test(part) ?
27
- <strong
28
- key={i}
29
- >
30
- {part}
31
- </strong> :
33
+ parts.map((part, i) => {
34
+ // RegExp is stateful, set up a new one to work with
35
+ const immutableRegex = new RegExp(regex);
36
+ if (immutableRegex.exec(part) !== null) {
37
+ return (
38
+ <strong
39
+ key={i}
40
+ >
41
+ {part}
42
+ </strong>
43
+ );
44
+ }
45
+
46
+ return (
32
47
  <span key={i}>
33
48
  {part}
34
49
  </span>
35
- ))
50
+ );
51
+ })
36
52
  );
37
53
  };
38
54
 
@@ -1,14 +1,35 @@
1
1
  import escapeRegExp from 'lodash/escapeRegExp';
2
2
 
3
- const matchString = (match, str, ignoreNull = true) => {
4
- const regex = new RegExp(`${match.split(/(\s+)/).filter(h => h.trim()).map(hl => '(' + escapeRegExp(hl) + ')').join('|')}`, 'gi');
3
+ const matchString = (match, str, ignoreNull = true, simpleSplit = true) => {
4
+ // Simple regex split -- this is default behaviour
5
+ const regexSimple = new RegExp(`${match.split(/(\s+)/).filter(h => h.trim()).map(hl => '(' + escapeRegExp(hl) + ')').join('|')}`, 'gi');
6
+
7
+ // Split Elivis "The King" Presley into [Elvis, The King, Presley]
8
+ const regex = new RegExp(`${
9
+ match.split(/(?!\B"[^"]*)\s+(?![^"]*"\B)/)
10
+ .filter(h => h.trim())
11
+ .map(quotedSection => {
12
+ if (quotedSection.charAt(0) === '"' && quotedSection.charAt(quotedSection.length - 1) === '"') {
13
+ return quotedSection.slice(1, quotedSection.length - 1);
14
+ }
15
+ return quotedSection;
16
+ })
17
+ .map(hl => '(' + escapeRegExp(hl) + ')')
18
+ .join('|')
19
+ }`,
20
+ 'gi');
21
+
5
22
  if (ignoreNull && !match) {
6
23
  const nullRegex = /a^/gi; // Should match nothing
7
24
 
8
25
  return [[str], nullRegex];
9
26
  }
10
27
 
11
- return [str.split(regex), regex];
28
+ if (simpleSplit) {
29
+ return [str.split(regexSimple)?.filter(s => s && s.trim()), regexSimple];
30
+ }
31
+
32
+ return [str.split(regex)?.filter(s => s && s.trim()), regex];
12
33
  };
13
34
 
14
35
  export default matchString;