@instructure/ui-source-code-editor 8.52.1-snapshot-3 → 8.52.1-snapshot-5

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
@@ -3,9 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [8.52.1-snapshot-3](https://github.com/instructure/instructure-ui/compare/v8.52.0...v8.52.1-snapshot-3) (2024-02-06)
6
+ ## [8.52.1-snapshot-5](https://github.com/instructure/instructure-ui/compare/v8.52.0...v8.52.1-snapshot-5) (2024-02-07)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-source-code-editor
8
+
9
+ ### Features
10
+
11
+ * **ui-source-code-editor:** add search panel ([991e8fa](https://github.com/instructure/instructure-ui/commit/991e8fa8be7461979ee522426f51e877fbb029e5))
9
12
 
10
13
 
11
14
 
@@ -0,0 +1,110 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
+ var _IconSearchLine, _IconArrowOpenDownLin, _IconArrowOpenUpLine;
3
+ /*
4
+ * The MIT License (MIT)
5
+ *
6
+ * Copyright (c) 2015 - present Instructure, Inc.
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ * of this software and associated documentation files (the "Software"), to deal
10
+ * in the Software without restriction, including without limitation the rights
11
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ * copies of the Software, and to permit persons to whom the Software is
13
+ * furnished to do so, subject to the following conditions:
14
+ *
15
+ * The above copyright notice and this permission notice shall be included in all
16
+ * copies or substantial portions of the Software.
17
+ *
18
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ * SOFTWARE.
25
+ */
26
+
27
+ import React, { useState } from 'react';
28
+ import { createRoot } from 'react-dom/client';
29
+ import { setSearchQuery, search, findNext, findPrevious, SearchQuery, closeSearchPanel } from '@codemirror/search';
30
+ import { TextInput } from '@instructure/ui-text-input';
31
+ import { IconButton } from '@instructure/ui-buttons';
32
+ import { IconArrowOpenDownLine, IconArrowOpenUpLine, IconSearchLine } from '@instructure/ui-icons';
33
+ function SearchPanel({
34
+ view,
35
+ searchConfig
36
+ }) {
37
+ const _useState = useState(''),
38
+ _useState2 = _slicedToArray(_useState, 2),
39
+ searchQueryStr = _useState2[0],
40
+ setSearchQueryStr = _useState2[1];
41
+ const handleChange = (_e, value) => {
42
+ setSearchQueryStr(value);
43
+ handleHighlightSearch(value);
44
+ };
45
+ const handleHighlightSearch = searchStr => {
46
+ view.dispatch({
47
+ effects: setSearchQuery.of(new SearchQuery({
48
+ search: searchStr
49
+ }))
50
+ });
51
+ };
52
+ const handleKeyDown = e => {
53
+ if (e.key !== 'Enter') return;
54
+ if (!e.shiftKey) handleFindNext();else handleFindPrev();
55
+ };
56
+ const handleKeyUp = e => {
57
+ if (e.key !== 'Escape') return;
58
+ closeSearchPanel(view);
59
+ };
60
+ const handleFindNext = () => {
61
+ handleHighlightSearch(searchQueryStr);
62
+ findNext(view);
63
+ };
64
+ const handleFindPrev = () => {
65
+ handleHighlightSearch(searchQueryStr);
66
+ findPrevious(view);
67
+ };
68
+ return /*#__PURE__*/React.createElement(TextInput, {
69
+ inputRef: r => r === null || r === void 0 ? void 0 : r.focus(),
70
+ size: "small",
71
+ display: "inline-block",
72
+ width: "20rem",
73
+ placeholder: searchConfig.placeholder,
74
+ onChange: handleChange,
75
+ onKeyDown: handleKeyDown,
76
+ onKeyUp: handleKeyUp,
77
+ renderBeforeInput: _IconSearchLine || (_IconSearchLine = /*#__PURE__*/React.createElement(IconSearchLine, {
78
+ size: "x-small"
79
+ })),
80
+ renderAfterInput: /*#__PURE__*/React.createElement("span", null, /*#__PURE__*/React.createElement(IconButton, {
81
+ size: "small",
82
+ withBorder: false,
83
+ withBackground: false,
84
+ onClick: handleFindNext,
85
+ screenReaderLabel: searchConfig.nextResultLabel
86
+ }, _IconArrowOpenDownLin || (_IconArrowOpenDownLin = /*#__PURE__*/React.createElement(IconArrowOpenDownLine, null))), /*#__PURE__*/React.createElement(IconButton, {
87
+ size: "small",
88
+ withBorder: false,
89
+ withBackground: false,
90
+ onClick: handleFindPrev,
91
+ screenReaderLabel: searchConfig.prevResultLabel
92
+ }, _IconArrowOpenUpLine || (_IconArrowOpenUpLine = /*#__PURE__*/React.createElement(IconArrowOpenUpLine, null))))
93
+ });
94
+ }
95
+ export default function customSearch(searchConfig) {
96
+ return searchConfig ? search({
97
+ createPanel: view => {
98
+ const dom = document.createElement('div');
99
+ dom.style.padding = '8px';
100
+ const root = createRoot(dom);
101
+ root.render( /*#__PURE__*/React.createElement(SearchPanel, {
102
+ view: view,
103
+ searchConfig: searchConfig
104
+ }));
105
+ return {
106
+ dom
107
+ };
108
+ }
109
+ }) : [];
110
+ }
@@ -31,10 +31,7 @@ import { deepEqual as isEqual } from '@instructure/ui-utils';
31
31
  import { EditorState, StateEffect } from '@codemirror/state';
32
32
  import { EditorView, highlightSpecialChars, highlightActiveLine, drawSelection, dropCursor, rectangularSelection, crosshairCursor, lineNumbers, highlightActiveLineGutter, keymap } from '@codemirror/view';
33
33
  import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
34
- import { highlightSelectionMatches
35
- // Search feature is turned off for now, see note at keymaps
36
- // searchKeymap
37
- } from '@codemirror/search';
34
+ import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
38
35
  import { indentSelection, defaultKeymap, indentWithTab, history, historyKeymap } from '@codemirror/commands';
39
36
  import { lintKeymap } from '@codemirror/lint';
40
37
  import { indentOnInput, indentRange, indentUnit, StreamLanguage, bracketMatching, foldGutter, foldKeymap, defaultHighlightStyle, syntaxHighlighting, HighlightStyle } from '@codemirror/language';
@@ -53,6 +50,7 @@ import { requestAnimationFrame } from '@instructure/ui-dom-utils';
53
50
  import { ScreenReaderContent } from '@instructure/ui-a11y-content';
54
51
  import { textDirectionContextConsumer } from '@instructure/ui-i18n';
55
52
  import { withStyle, jsx } from '@instructure/emotion';
53
+ import customSearch from './SearchPanel';
56
54
  import generateStyle from './styles';
57
55
  import generateComponentTheme from './theme';
58
56
  import { rtlHorizontalArrowKeymap } from './customKeybinding';
@@ -306,19 +304,11 @@ let SourceCodeEditor = (_dec = withDeterministicId(), _dec2 = withStyle(generate
306
304
  // and adjust it as desired.
307
305
  highlightSpecialChars(), history(), drawSelection(), dropCursor(), EditorState.allowMultipleSelections.of(true), syntaxHighlighting(defaultHighlightStyle, {
308
306
  fallback: true
309
- }), bracketMatching(), closeBrackets(), autocompletion(), rectangularSelection(), crosshairCursor(), highlightSelectionMatches(), indentOnInput(), keymap.of(this.keymaps)];
307
+ }), bracketMatching(), closeBrackets(), autocompletion(), rectangularSelection(), crosshairCursor(), highlightSelectionMatches(), indentOnInput(), customSearch(this.props.searchConfig), keymap.of(this.keymaps)];
310
308
  }
311
309
  get keymaps() {
312
310
  // TODO: if more keymaps are added, list them in the docs as well (#Command keybinding)
313
- const keymaps = [...closeBracketsKeymap, ...this.commandKeybinding, ...historyKeymap, ...foldKeymap, ...completionKeymap, ...lintKeymap
314
-
315
- // TODO: style and include search & replace toolbar feature
316
- // Note: the search & replace toolbar is not styled.
317
- // If this feature is needed in the future, we need a decision about the styling.
318
- // For now we turned the feature off, since RCE doesn't need it.
319
- // ...searchKeymap
320
- ];
321
-
311
+ const keymaps = [...closeBracketsKeymap, ...this.commandKeybinding, ...historyKeymap, ...foldKeymap, ...completionKeymap, ...lintKeymap, ...(this.props.searchConfig ? searchKeymap : [])];
322
312
  if (this.props.indentWithTab) {
323
313
  keymaps.push(indentWithTab);
324
314
  }
@@ -51,9 +51,10 @@ const propTypes = {
51
51
  width: PropTypes.string,
52
52
  // darkTheme: PropTypes.bool,
53
53
  elementRef: PropTypes.func,
54
- containerRef: PropTypes.func
54
+ containerRef: PropTypes.func,
55
+ searchConfig: PropTypes.object
55
56
  };
56
57
  const allowedProps = ['label', 'language', 'readOnly', 'editable', 'lineNumbers', 'foldGutter', 'highlightActiveLineGutter', 'highlightActiveLine', 'lineWrapping', 'autofocus', 'spellcheck', 'direction', 'rtlMoveVisually', 'indentOnLoad', 'indentWithTab', 'indentUnit', 'defaultValue', 'value', 'onChange', 'onFocus', 'onBlur', 'attachment', 'height', 'width',
57
58
  // 'darkTheme',
58
- 'elementRef', 'containerRef'];
59
+ 'elementRef', 'containerRef', 'searchConfig'];
59
60
  export { propTypes, allowedProps };
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = customSearch;
9
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
10
+ var _react = _interopRequireWildcard(require("react"));
11
+ var _client = require("react-dom/client");
12
+ var _search = require("@codemirror/search");
13
+ var _TextInput = require("@instructure/ui-text-input/lib/TextInput");
14
+ var _IconButton = require("@instructure/ui-buttons/lib/IconButton");
15
+ var _IconArrowOpenDownLine = require("@instructure/ui-icons/lib/IconArrowOpenDownLine.js");
16
+ var _IconArrowOpenUpLine2 = require("@instructure/ui-icons/lib/IconArrowOpenUpLine.js");
17
+ var _IconSearchLine2 = require("@instructure/ui-icons/lib/IconSearchLine.js");
18
+ var _IconSearchLine, _IconArrowOpenDownLin, _IconArrowOpenUpLine;
19
+ /*
20
+ * The MIT License (MIT)
21
+ *
22
+ * Copyright (c) 2015 - present Instructure, Inc.
23
+ *
24
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
25
+ * of this software and associated documentation files (the "Software"), to deal
26
+ * in the Software without restriction, including without limitation the rights
27
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
28
+ * copies of the Software, and to permit persons to whom the Software is
29
+ * furnished to do so, subject to the following conditions:
30
+ *
31
+ * The above copyright notice and this permission notice shall be included in all
32
+ * copies or substantial portions of the Software.
33
+ *
34
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40
+ * SOFTWARE.
41
+ */
42
+ function SearchPanel({
43
+ view,
44
+ searchConfig
45
+ }) {
46
+ const _useState = (0, _react.useState)(''),
47
+ _useState2 = (0, _slicedToArray2.default)(_useState, 2),
48
+ searchQueryStr = _useState2[0],
49
+ setSearchQueryStr = _useState2[1];
50
+ const handleChange = (_e, value) => {
51
+ setSearchQueryStr(value);
52
+ handleHighlightSearch(value);
53
+ };
54
+ const handleHighlightSearch = searchStr => {
55
+ view.dispatch({
56
+ effects: _search.setSearchQuery.of(new _search.SearchQuery({
57
+ search: searchStr
58
+ }))
59
+ });
60
+ };
61
+ const handleKeyDown = e => {
62
+ if (e.key !== 'Enter') return;
63
+ if (!e.shiftKey) handleFindNext();else handleFindPrev();
64
+ };
65
+ const handleKeyUp = e => {
66
+ if (e.key !== 'Escape') return;
67
+ (0, _search.closeSearchPanel)(view);
68
+ };
69
+ const handleFindNext = () => {
70
+ handleHighlightSearch(searchQueryStr);
71
+ (0, _search.findNext)(view);
72
+ };
73
+ const handleFindPrev = () => {
74
+ handleHighlightSearch(searchQueryStr);
75
+ (0, _search.findPrevious)(view);
76
+ };
77
+ return /*#__PURE__*/_react.default.createElement(_TextInput.TextInput, {
78
+ inputRef: r => r === null || r === void 0 ? void 0 : r.focus(),
79
+ size: "small",
80
+ display: "inline-block",
81
+ width: "20rem",
82
+ placeholder: searchConfig.placeholder,
83
+ onChange: handleChange,
84
+ onKeyDown: handleKeyDown,
85
+ onKeyUp: handleKeyUp,
86
+ renderBeforeInput: _IconSearchLine || (_IconSearchLine = /*#__PURE__*/_react.default.createElement(_IconSearchLine2.IconSearchLine, {
87
+ size: "x-small"
88
+ })),
89
+ renderAfterInput: /*#__PURE__*/_react.default.createElement("span", null, /*#__PURE__*/_react.default.createElement(_IconButton.IconButton, {
90
+ size: "small",
91
+ withBorder: false,
92
+ withBackground: false,
93
+ onClick: handleFindNext,
94
+ screenReaderLabel: searchConfig.nextResultLabel
95
+ }, _IconArrowOpenDownLin || (_IconArrowOpenDownLin = /*#__PURE__*/_react.default.createElement(_IconArrowOpenDownLine.IconArrowOpenDownLine, null))), /*#__PURE__*/_react.default.createElement(_IconButton.IconButton, {
96
+ size: "small",
97
+ withBorder: false,
98
+ withBackground: false,
99
+ onClick: handleFindPrev,
100
+ screenReaderLabel: searchConfig.prevResultLabel
101
+ }, _IconArrowOpenUpLine || (_IconArrowOpenUpLine = /*#__PURE__*/_react.default.createElement(_IconArrowOpenUpLine2.IconArrowOpenUpLine, null))))
102
+ });
103
+ }
104
+ function customSearch(searchConfig) {
105
+ return searchConfig ? (0, _search.search)({
106
+ createPanel: view => {
107
+ const dom = document.createElement('div');
108
+ dom.style.padding = '8px';
109
+ const root = (0, _client.createRoot)(dom);
110
+ root.render( /*#__PURE__*/_react.default.createElement(SearchPanel, {
111
+ view: view,
112
+ searchConfig: searchConfig
113
+ }));
114
+ return {
115
+ dom
116
+ };
117
+ }
118
+ }) : [];
119
+ }
@@ -30,6 +30,7 @@ var _requestAnimationFrame = require("@instructure/ui-dom-utils/lib/requestAnima
30
30
  var _ScreenReaderContent = require("@instructure/ui-a11y-content/lib/ScreenReaderContent");
31
31
  var _textDirectionContextConsumer = require("@instructure/ui-i18n/lib/textDirectionContextConsumer.js");
32
32
  var _emotion = require("@instructure/emotion");
33
+ var _SearchPanel = _interopRequireDefault(require("./SearchPanel"));
33
34
  var _styles = _interopRequireDefault(require("./styles"));
34
35
  var _theme = _interopRequireDefault(require("./theme"));
35
36
  var _customKeybinding = require("./customKeybinding");
@@ -310,19 +311,11 @@ let SourceCodeEditor = exports.SourceCodeEditor = (_dec = (0, _withDeterministic
310
311
  // and adjust it as desired.
311
312
  (0, _view.highlightSpecialChars)(), (0, _commands.history)(), (0, _view.drawSelection)(), (0, _view.dropCursor)(), _state.EditorState.allowMultipleSelections.of(true), (0, _language.syntaxHighlighting)(_language.defaultHighlightStyle, {
312
313
  fallback: true
313
- }), (0, _language.bracketMatching)(), (0, _autocomplete.closeBrackets)(), (0, _autocomplete.autocompletion)(), (0, _view.rectangularSelection)(), (0, _view.crosshairCursor)(), (0, _search.highlightSelectionMatches)(), (0, _language.indentOnInput)(), _view.keymap.of(this.keymaps)];
314
+ }), (0, _language.bracketMatching)(), (0, _autocomplete.closeBrackets)(), (0, _autocomplete.autocompletion)(), (0, _view.rectangularSelection)(), (0, _view.crosshairCursor)(), (0, _search.highlightSelectionMatches)(), (0, _language.indentOnInput)(), (0, _SearchPanel.default)(this.props.searchConfig), _view.keymap.of(this.keymaps)];
314
315
  }
315
316
  get keymaps() {
316
317
  // TODO: if more keymaps are added, list them in the docs as well (#Command keybinding)
317
- const keymaps = [..._autocomplete.closeBracketsKeymap, ...this.commandKeybinding, ..._commands.historyKeymap, ..._language.foldKeymap, ..._autocomplete.completionKeymap, ..._lint.lintKeymap
318
-
319
- // TODO: style and include search & replace toolbar feature
320
- // Note: the search & replace toolbar is not styled.
321
- // If this feature is needed in the future, we need a decision about the styling.
322
- // For now we turned the feature off, since RCE doesn't need it.
323
- // ...searchKeymap
324
- ];
325
-
318
+ const keymaps = [..._autocomplete.closeBracketsKeymap, ...this.commandKeybinding, ..._commands.historyKeymap, ..._language.foldKeymap, ..._autocomplete.completionKeymap, ..._lint.lintKeymap, ...(this.props.searchConfig ? _search.searchKeymap : [])];
326
319
  if (this.props.indentWithTab) {
327
320
  keymaps.push(_commands.indentWithTab);
328
321
  }
@@ -58,8 +58,9 @@ const propTypes = exports.propTypes = {
58
58
  width: _propTypes.default.string,
59
59
  // darkTheme: PropTypes.bool,
60
60
  elementRef: _propTypes.default.func,
61
- containerRef: _propTypes.default.func
61
+ containerRef: _propTypes.default.func,
62
+ searchConfig: _propTypes.default.object
62
63
  };
63
64
  const allowedProps = exports.allowedProps = ['label', 'language', 'readOnly', 'editable', 'lineNumbers', 'foldGutter', 'highlightActiveLineGutter', 'highlightActiveLine', 'lineWrapping', 'autofocus', 'spellcheck', 'direction', 'rtlMoveVisually', 'indentOnLoad', 'indentWithTab', 'indentUnit', 'defaultValue', 'value', 'onChange', 'onFocus', 'onBlur', 'attachment', 'height', 'width',
64
65
  // 'darkTheme',
65
- 'elementRef', 'containerRef'];
66
+ 'elementRef', 'containerRef', 'searchConfig'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-source-code-editor",
3
- "version": "8.52.1-snapshot-3",
3
+ "version": "8.52.1-snapshot-5",
4
4
  "description": "A UI component library made by Instructure Inc.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -23,11 +23,15 @@
23
23
  },
24
24
  "license": "MIT",
25
25
  "devDependencies": {
26
- "@instructure/ui-babel-preset": "8.52.1-snapshot-3",
27
- "@instructure/ui-test-queries": "8.52.1-snapshot-3",
28
- "@instructure/ui-test-utils": "8.52.1-snapshot-3",
26
+ "@instructure/ui-babel-preset": "8.52.1-snapshot-5",
27
+ "@instructure/ui-buttons": "8.52.1-snapshot-5",
28
+ "@instructure/ui-icons": "8.52.1-snapshot-5",
29
+ "@instructure/ui-test-queries": "8.52.1-snapshot-5",
30
+ "@instructure/ui-test-utils": "8.52.1-snapshot-5",
31
+ "@instructure/ui-text-input": "8.52.1-snapshot-5",
29
32
  "@testing-library/jest-dom": "^6.1.4",
30
- "@testing-library/react": "^14.0.0"
33
+ "@testing-library/react": "^14.0.0",
34
+ "react-dom": "^18.2.0"
31
35
  },
32
36
  "dependencies": {
33
37
  "@babel/runtime": "^7.23.2",
@@ -44,17 +48,17 @@
44
48
  "@codemirror/search": "^6.5.4",
45
49
  "@codemirror/state": "^6.3.0",
46
50
  "@codemirror/view": "^6.21.3",
47
- "@instructure/emotion": "8.52.1-snapshot-3",
48
- "@instructure/shared-types": "8.52.1-snapshot-3",
49
- "@instructure/ui-a11y-content": "8.52.1-snapshot-3",
50
- "@instructure/ui-dom-utils": "8.52.1-snapshot-3",
51
- "@instructure/ui-i18n": "8.52.1-snapshot-3",
52
- "@instructure/ui-prop-types": "8.52.1-snapshot-3",
53
- "@instructure/ui-react-utils": "8.52.1-snapshot-3",
54
- "@instructure/ui-test-locator": "8.52.1-snapshot-3",
55
- "@instructure/ui-testable": "8.52.1-snapshot-3",
56
- "@instructure/ui-themes": "8.52.1-snapshot-3",
57
- "@instructure/ui-utils": "8.52.1-snapshot-3",
51
+ "@instructure/emotion": "8.52.1-snapshot-5",
52
+ "@instructure/shared-types": "8.52.1-snapshot-5",
53
+ "@instructure/ui-a11y-content": "8.52.1-snapshot-5",
54
+ "@instructure/ui-dom-utils": "8.52.1-snapshot-5",
55
+ "@instructure/ui-i18n": "8.52.1-snapshot-5",
56
+ "@instructure/ui-prop-types": "8.52.1-snapshot-5",
57
+ "@instructure/ui-react-utils": "8.52.1-snapshot-5",
58
+ "@instructure/ui-test-locator": "8.52.1-snapshot-5",
59
+ "@instructure/ui-testable": "8.52.1-snapshot-5",
60
+ "@instructure/ui-themes": "8.52.1-snapshot-5",
61
+ "@instructure/ui-utils": "8.52.1-snapshot-5",
58
62
  "@lezer/highlight": "1.1.6",
59
63
  "prop-types": "^15.8.1"
60
64
  },
@@ -785,3 +785,30 @@ function exampleMethod(props: Props) {
785
785
 
786
786
  render(<AttachmentExample />)
787
787
  ```
788
+
789
+ ### Search
790
+
791
+ To enable the search panel use the `searchConfig` prop.
792
+
793
+ You can open the search panel in the code editor by pressing `cmd/ctrl+f` when it is in focus (otherwise the browser's search will open). The reason you would use this instead of the browser native search is because it will miss results that are far out of view in the text rendered by the editor. This is the limitation of the underlying CodeMirror component.
794
+
795
+ Hitting `Enter` jumps to the next result and `Shift+Enter` to the previous. Alternatively you can use the up and down buttons to the right of the input field.
796
+
797
+ ```js
798
+ ---
799
+ type: example
800
+ ---
801
+ <SourceCodeEditor
802
+ label="lorem"
803
+ language="markdown"
804
+ defaultValue={`Lorem ipsum dolor sit amet, officia excepteur ex fugiat reprehenderit
805
+ enim labore culpa sint ad nisi Lorem pariatur mollit ex esse exercitation amet.
806
+ Nisi anim cupidatat excepteur officia.
807
+ `}
808
+ searchConfig={{
809
+ placeholder: 'Find in code...',
810
+ nextResultLabel: 'Next result',
811
+ prevResultLabel: 'Previouse result',
812
+ }}
813
+ />
814
+ ```
@@ -0,0 +1,147 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import React, { useState } from 'react'
26
+ import { createRoot } from 'react-dom/client'
27
+ import {
28
+ setSearchQuery,
29
+ search,
30
+ findNext,
31
+ findPrevious,
32
+ SearchQuery,
33
+ closeSearchPanel
34
+ } from '@codemirror/search'
35
+ import { EditorView } from '@codemirror/view'
36
+ import { TextInput } from '@instructure/ui-text-input'
37
+ import { IconButton } from '@instructure/ui-buttons'
38
+ import {
39
+ IconArrowOpenDownLine,
40
+ IconArrowOpenUpLine,
41
+ IconSearchLine
42
+ } from '@instructure/ui-icons'
43
+
44
+ export type SearchConfig = {
45
+ placeholder: string
46
+ nextResultLabel: string
47
+ prevResultLabel: string
48
+ }
49
+
50
+ function SearchPanel({
51
+ view,
52
+ searchConfig
53
+ }: {
54
+ view: EditorView
55
+ searchConfig: SearchConfig
56
+ }) {
57
+ const [searchQueryStr, setSearchQueryStr] = useState('')
58
+
59
+ const handleChange = (
60
+ _e: React.ChangeEvent<HTMLInputElement>,
61
+ value: string
62
+ ) => {
63
+ setSearchQueryStr(value)
64
+ handleHighlightSearch(value)
65
+ }
66
+
67
+ const handleHighlightSearch = (searchStr: string) => {
68
+ view.dispatch({
69
+ effects: setSearchQuery.of(
70
+ new SearchQuery({
71
+ search: searchStr
72
+ })
73
+ )
74
+ })
75
+ }
76
+
77
+ const handleKeyDown = (e: React.KeyboardEvent) => {
78
+ if (e.key !== 'Enter') return
79
+ if (!e.shiftKey) handleFindNext()
80
+ else handleFindPrev()
81
+ }
82
+
83
+ const handleKeyUp = (e: React.KeyboardEvent) => {
84
+ if (e.key !== 'Escape') return
85
+ closeSearchPanel(view)
86
+ }
87
+
88
+ const handleFindNext = () => {
89
+ handleHighlightSearch(searchQueryStr)
90
+ findNext(view)
91
+ }
92
+
93
+ const handleFindPrev = () => {
94
+ handleHighlightSearch(searchQueryStr)
95
+ findPrevious(view)
96
+ }
97
+
98
+ return (
99
+ <TextInput
100
+ inputRef={(r) => r?.focus()}
101
+ size="small"
102
+ display="inline-block"
103
+ width="20rem"
104
+ placeholder={searchConfig.placeholder}
105
+ onChange={handleChange}
106
+ onKeyDown={handleKeyDown}
107
+ onKeyUp={handleKeyUp}
108
+ renderBeforeInput={<IconSearchLine size="x-small" />}
109
+ renderAfterInput={
110
+ <span>
111
+ <IconButton
112
+ size="small"
113
+ withBorder={false}
114
+ withBackground={false}
115
+ onClick={handleFindNext}
116
+ screenReaderLabel={searchConfig.nextResultLabel}
117
+ >
118
+ <IconArrowOpenDownLine />
119
+ </IconButton>
120
+ <IconButton
121
+ size="small"
122
+ withBorder={false}
123
+ withBackground={false}
124
+ onClick={handleFindPrev}
125
+ screenReaderLabel={searchConfig.prevResultLabel}
126
+ >
127
+ <IconArrowOpenUpLine />
128
+ </IconButton>
129
+ </span>
130
+ }
131
+ />
132
+ )
133
+ }
134
+
135
+ export default function customSearch(searchConfig: SearchConfig | undefined) {
136
+ return searchConfig
137
+ ? search({
138
+ createPanel: (view) => {
139
+ const dom = document.createElement('div')
140
+ dom.style.padding = '8px'
141
+ const root = createRoot(dom)
142
+ root.render(<SearchPanel view={view} searchConfig={searchConfig} />)
143
+ return { dom }
144
+ }
145
+ })
146
+ : []
147
+ }
@@ -47,11 +47,7 @@ import {
47
47
  closeBrackets,
48
48
  closeBracketsKeymap
49
49
  } from '@codemirror/autocomplete'
50
- import {
51
- highlightSelectionMatches
52
- // Search feature is turned off for now, see note at keymaps
53
- // searchKeymap
54
- } from '@codemirror/search'
50
+ import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
55
51
  import {
56
52
  indentSelection,
57
53
  defaultKeymap,
@@ -95,6 +91,8 @@ import { textDirectionContextConsumer } from '@instructure/ui-i18n'
95
91
 
96
92
  import { withStyle, jsx } from '@instructure/emotion'
97
93
 
94
+ import customSearch from './SearchPanel'
95
+
98
96
  import generateStyle from './styles'
99
97
  import generateComponentTheme from './theme'
100
98
 
@@ -435,7 +433,7 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
435
433
  crosshairCursor(),
436
434
  highlightSelectionMatches(),
437
435
  indentOnInput(),
438
-
436
+ customSearch(this.props.searchConfig),
439
437
  keymap.of(this.keymaps)
440
438
  ]
441
439
  }
@@ -448,13 +446,8 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
448
446
  ...historyKeymap,
449
447
  ...foldKeymap,
450
448
  ...completionKeymap,
451
- ...lintKeymap
452
-
453
- // TODO: style and include search & replace toolbar feature
454
- // Note: the search & replace toolbar is not styled.
455
- // If this feature is needed in the future, we need a decision about the styling.
456
- // For now we turned the feature off, since RCE doesn't need it.
457
- // ...searchKeymap
449
+ ...lintKeymap,
450
+ ...(this.props.searchConfig ? searchKeymap : [])
458
451
  ]
459
452
 
460
453
  if (this.props.indentWithTab) {
@@ -40,6 +40,7 @@ import type {
40
40
  } from '@instructure/emotion'
41
41
  import type { WithDeterministicIdProps } from '@instructure/ui-react-utils'
42
42
  import type { BidirectionalProps } from '@instructure/ui-i18n'
43
+ import type { SearchConfig } from './SearchPanel'
43
44
 
44
45
  type SourceCodeEditorOwnProps = {
45
46
  /**
@@ -204,6 +205,11 @@ type SourceCodeEditorOwnProps = {
204
205
  * provides a reference to the html element of the editor's container
205
206
  */
206
207
  containerRef?: (element: HTMLDivElement | null) => void
208
+
209
+ /**
210
+ * enable search panel in editor when pressing ctrl/cmd+f
211
+ */
212
+ searchConfig?: SearchConfig
207
213
  }
208
214
 
209
215
  type PropKeys = keyof SourceCodeEditorOwnProps
@@ -268,7 +274,8 @@ const propTypes: PropValidators<PropKeys> = {
268
274
  width: PropTypes.string,
269
275
  // darkTheme: PropTypes.bool,
270
276
  elementRef: PropTypes.func,
271
- containerRef: PropTypes.func
277
+ containerRef: PropTypes.func,
278
+ searchConfig: PropTypes.object
272
279
  }
273
280
 
274
281
  const allowedProps: AllowedPropKeys = [
@@ -298,7 +305,8 @@ const allowedProps: AllowedPropKeys = [
298
305
  'width',
299
306
  // 'darkTheme',
300
307
  'elementRef',
301
- 'containerRef'
308
+ 'containerRef',
309
+ 'searchConfig'
302
310
  ]
303
311
 
304
312
  export type { SourceCodeEditorProps, SourceCodeEditorStyle }