@instructure/ui-source-code-editor 8.52.1-snapshot-4 → 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 +5 -2
- package/es/SourceCodeEditor/SearchPanel.js +110 -0
- package/es/SourceCodeEditor/index.js +4 -14
- package/es/SourceCodeEditor/props.js +3 -2
- package/lib/SourceCodeEditor/SearchPanel.js +119 -0
- package/lib/SourceCodeEditor/index.js +3 -10
- package/lib/SourceCodeEditor/props.js +3 -2
- package/package.json +20 -16
- package/src/SourceCodeEditor/README.md +27 -0
- package/src/SourceCodeEditor/SearchPanel.tsx +147 -0
- package/src/SourceCodeEditor/index.tsx +6 -13
- package/src/SourceCodeEditor/props.ts +10 -2
- package/tsconfig.build.json +3 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/SourceCodeEditor/SearchPanel.d.ts +7 -0
- package/types/SourceCodeEditor/SearchPanel.d.ts.map +1 -0
- package/types/SourceCodeEditor/index.d.ts +4 -2
- package/types/SourceCodeEditor/index.d.ts.map +1 -1
- package/types/SourceCodeEditor/props.d.ts +5 -0
- package/types/SourceCodeEditor/props.d.ts.map +1 -1
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-
|
|
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
|
-
|
|
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
|
+
"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-
|
|
27
|
-
"@instructure/ui-
|
|
28
|
-
"@instructure/ui-
|
|
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-
|
|
48
|
-
"@instructure/shared-types": "8.52.1-snapshot-
|
|
49
|
-
"@instructure/ui-a11y-content": "8.52.1-snapshot-
|
|
50
|
-
"@instructure/ui-dom-utils": "8.52.1-snapshot-
|
|
51
|
-
"@instructure/ui-i18n": "8.52.1-snapshot-
|
|
52
|
-
"@instructure/ui-prop-types": "8.52.1-snapshot-
|
|
53
|
-
"@instructure/ui-react-utils": "8.52.1-snapshot-
|
|
54
|
-
"@instructure/ui-test-locator": "8.52.1-snapshot-
|
|
55
|
-
"@instructure/ui-testable": "8.52.1-snapshot-
|
|
56
|
-
"@instructure/ui-themes": "8.52.1-snapshot-
|
|
57
|
-
"@instructure/ui-utils": "8.52.1-snapshot-
|
|
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 }
|