@instructure/ui-source-code-editor 11.7.4-snapshot-80 → 11.7.4-snapshot-98

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
- ## [11.7.4-snapshot-80](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-80) (2026-07-07)
6
+ ## [11.7.4-snapshot-98](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-98) (2026-07-14)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-source-code-editor
8
+
9
+ ### Bug Fixes
10
+
11
+ * **ui-source-code-editor:** apply current theme to v2 search panel ([4534fa0](https://github.com/instructure/instructure-ui/commit/4534fa0fc244bfb7138f3d09fbeeb1e73dfa6afd))
9
12
 
10
13
 
11
14
 
@@ -27,9 +27,13 @@ import { setSearchQuery, search, findNext, findPrevious, SearchQuery, closeSearc
27
27
  import { TextInput } from '@instructure/ui-text-input/latest';
28
28
  import { IconButton } from '@instructure/ui-buttons/latest';
29
29
  import { IconArrowOpenDownLine, IconArrowOpenUpLine, IconSearchLine } from '@instructure/ui-icons';
30
- import { createRoot } from 'react-dom/client';
31
30
  import { jsx as _jsx, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
32
- function SearchPanel({
31
+ /**
32
+ * ---
33
+ * private: true
34
+ * ---
35
+ */
36
+ export function SearchPanel({
33
37
  view,
34
38
  searchConfig
35
39
  }) {
@@ -95,18 +99,19 @@ function SearchPanel({
95
99
  })
96
100
  });
97
101
  }
98
- export default function customSearch(searchConfig) {
102
+ export default function customSearch(searchConfig, handlers) {
99
103
  return searchConfig ? search({
100
104
  createPanel: view => {
101
105
  const dom = document.createElement('div');
102
106
  dom.style.padding = '8px';
103
- const root = createRoot(dom);
104
- root.render(_jsx(SearchPanel, {
105
- view: view,
106
- searchConfig: searchConfig
107
- }));
108
107
  return {
109
- dom
108
+ dom,
109
+ mount() {
110
+ handlers.onMount(dom, view);
111
+ },
112
+ destroy() {
113
+ handlers.onDestroy(dom);
114
+ }
110
115
  };
111
116
  }
112
117
  }) : [];
@@ -24,6 +24,7 @@ var _dec, _dec2, _dec3, _class;
24
24
  */
25
25
 
26
26
  import { Component } from 'react';
27
+ import { createPortal } from 'react-dom';
27
28
  import { deepEqual as isEqual } from '@instructure/ui-utils';
28
29
  import { EditorState, StateEffect } from '@codemirror/state';
29
30
  import { EditorView, highlightSpecialChars, highlightActiveLine, drawSelection, dropCursor, rectangularSelection, crosshairCursor, lineNumbers, highlightActiveLineGutter, keymap } from '@codemirror/view';
@@ -46,7 +47,7 @@ import { requestAnimationFrame } from '@instructure/ui-dom-utils';
46
47
  import { ScreenReaderContent } from '@instructure/ui-a11y-content';
47
48
  import { textDirectionContextConsumer } from '@instructure/ui-i18n';
48
49
  import { withStyleNew } from '@instructure/emotion';
49
- import customSearch from './SearchPanel.js';
50
+ import customSearch, { SearchPanel } from './SearchPanel.js';
50
51
  import generateStyle from './styles.js';
51
52
  import { rtlHorizontalArrowKeymap } from './customKeybinding.js';
52
53
  import { allowedProps } from './props.js';
@@ -79,10 +80,30 @@ let SourceCodeEditor = (_dec = withDeterministicId(), _dec2 = withStyleNew(gener
79
80
  };
80
81
  _id;
81
82
  ref = null;
83
+ state = {
84
+ searchPanels: []
85
+ };
82
86
  _containerRef;
83
87
  _editorView;
84
88
  _raf = [];
89
+ _searchPanelId = 0;
90
+ _isUnmounting = false;
85
91
  _newSelectionAfterValueChange;
92
+ handleSearchPanelMount = (dom, view) => {
93
+ this.setState(state => ({
94
+ searchPanels: [...state.searchPanels, {
95
+ id: ++this._searchPanelId,
96
+ dom,
97
+ view
98
+ }]
99
+ }));
100
+ };
101
+ handleSearchPanelDestroy = dom => {
102
+ if (this._isUnmounting) return;
103
+ this.setState(state => ({
104
+ searchPanels: state.searchPanels.filter(panel => panel.dom !== dom)
105
+ }));
106
+ };
86
107
  handleRef = el => {
87
108
  const {
88
109
  elementRef
@@ -245,6 +266,7 @@ let SourceCodeEditor = (_dec = withDeterministicId(), _dec2 = withStyleNew(gener
245
266
  this.assignAriaLabel();
246
267
  }
247
268
  componentWillUnmount() {
269
+ this._isUnmounting = true;
248
270
  this._editorView?.destroy();
249
271
  this.cancelAnimationFrames();
250
272
  }
@@ -262,7 +284,7 @@ let SourceCodeEditor = (_dec = withDeterministicId(), _dec2 = withStyleNew(gener
262
284
  shouldUpdateExtensions(prevProps) {
263
285
  const propsToObserve = ['styles',
264
286
  // needed for theme update
265
- 'themeOverride', 'language', 'readOnly', 'editable', 'lineNumbers', 'highlightActiveLineGutter', 'foldGutter', 'lineWrapping', 'autofocus', 'spellcheck', 'direction', 'dir', 'rtlMoveVisually', 'indentOnLoad', 'indentWithTab', 'indentUnit', 'highlightActiveLine', 'attachment'];
287
+ 'themeOverride', 'language', 'readOnly', 'editable', 'lineNumbers', 'highlightActiveLineGutter', 'foldGutter', 'lineWrapping', 'autofocus', 'spellcheck', 'direction', 'dir', 'rtlMoveVisually', 'indentOnLoad', 'indentWithTab', 'indentUnit', 'highlightActiveLine', 'attachment', 'searchConfig'];
266
288
  for (const prop of propsToObserve) {
267
289
  if (!isEqual(this.props[prop], prevProps[prop])) {
268
290
  return true;
@@ -326,7 +348,10 @@ let SourceCodeEditor = (_dec = withDeterministicId(), _dec2 = withStyleNew(gener
326
348
  // and adjust it as desired.
327
349
  highlightSpecialChars(), history(), drawSelection(), dropCursor(), EditorState.allowMultipleSelections.of(true), syntaxHighlighting(defaultHighlightStyle, {
328
350
  fallback: true
329
- }), bracketMatching(), closeBrackets(), autocompletion(), rectangularSelection(), crosshairCursor(), highlightSelectionMatches(), indentOnInput(), customSearch(this.props.searchConfig), keymap.of(this.keymaps)];
351
+ }), bracketMatching(), closeBrackets(), autocompletion(), rectangularSelection(), crosshairCursor(), highlightSelectionMatches(), indentOnInput(), customSearch(this.props.searchConfig, {
352
+ onMount: this.handleSearchPanelMount,
353
+ onDestroy: this.handleSearchPanelDestroy
354
+ }), keymap.of(this.keymaps)];
330
355
  }
331
356
  get keymaps() {
332
357
  // TODO: if more keymaps are added, list them in the docs as well (#Command keybinding)
@@ -529,14 +554,15 @@ let SourceCodeEditor = (_dec = withDeterministicId(), _dec2 = withStyleNew(gener
529
554
  const {
530
555
  label,
531
556
  styles,
557
+ searchConfig,
532
558
  ...restProps
533
559
  } = this.props;
534
- return _jsx("div", {
560
+ return _jsxs("div", {
535
561
  "data-cid": "SourceCodeEditor",
536
562
  ref: this.handleRef,
537
563
  css: styles?.codeEditor,
538
564
  ...passthroughProps(omitProps(restProps, SourceCodeEditor.allowedProps)),
539
- children: _jsxs("label", {
565
+ children: [_jsxs("label", {
540
566
  css: styles?.label,
541
567
  id: this._id,
542
568
  children: [_jsx(ScreenReaderContent, {
@@ -545,7 +571,14 @@ let SourceCodeEditor = (_dec = withDeterministicId(), _dec2 = withStyleNew(gener
545
571
  ref: this.handleContainerRef,
546
572
  css: styles?.codeEditorContainer
547
573
  })]
548
- })
574
+ }), searchConfig && this.state.searchPanels.map(({
575
+ id,
576
+ dom,
577
+ view
578
+ }) => /*#__PURE__*/createPortal(_jsx(SearchPanel, {
579
+ view: view,
580
+ searchConfig: searchConfig
581
+ }), dom, String(id)))]
549
582
  });
550
583
  }
551
584
  }) || _class) || _class) || _class);
@@ -144,6 +144,10 @@ const generateStyle = (componentTheme, props, sharedTokens) => {
144
144
  '.cm-placeholder': {
145
145
  // for better contrast
146
146
  color: componentTheme.placeholderBackgroundColor
147
+ },
148
+ '.cm-panels': {
149
+ backgroundColor: componentTheme.background,
150
+ color: componentTheme.color
147
151
  }
148
152
  },
149
153
  highlightStyle: [
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.SearchPanel = SearchPanel;
6
7
  exports.default = customSearch;
7
8
  var _react = require("react");
8
9
  var _search = require("@codemirror/search");
@@ -11,7 +12,6 @@ var _latest2 = require("@instructure/ui-buttons/latest");
11
12
  var _IconArrowOpenDownLine = require("@instructure/ui-icons/lib/generated/IconArrowOpenDownLine.js");
12
13
  var _IconArrowOpenUpLine = require("@instructure/ui-icons/lib/generated/IconArrowOpenUpLine.js");
13
14
  var _IconSearchLine = require("@instructure/ui-icons/lib/generated/IconSearchLine.js");
14
- var _client = require("react-dom/client");
15
15
  var _jsxRuntime = require("@emotion/react/jsx-runtime");
16
16
  /*
17
17
  * The MIT License (MIT)
@@ -37,6 +37,11 @@ var _jsxRuntime = require("@emotion/react/jsx-runtime");
37
37
  * SOFTWARE.
38
38
  */
39
39
 
40
+ /**
41
+ * ---
42
+ * private: true
43
+ * ---
44
+ */
40
45
  function SearchPanel({
41
46
  view,
42
47
  searchConfig
@@ -103,18 +108,19 @@ function SearchPanel({
103
108
  })
104
109
  });
105
110
  }
106
- function customSearch(searchConfig) {
111
+ function customSearch(searchConfig, handlers) {
107
112
  return searchConfig ? (0, _search.search)({
108
113
  createPanel: view => {
109
114
  const dom = document.createElement('div');
110
115
  dom.style.padding = '8px';
111
- const root = (0, _client.createRoot)(dom);
112
- root.render((0, _jsxRuntime.jsx)(SearchPanel, {
113
- view: view,
114
- searchConfig: searchConfig
115
- }));
116
116
  return {
117
- dom
117
+ dom,
118
+ mount() {
119
+ handlers.onMount(dom, view);
120
+ },
121
+ destroy() {
122
+ handlers.onDestroy(dom);
123
+ }
118
124
  };
119
125
  }
120
126
  }) : [];
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
3
4
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
5
  Object.defineProperty(exports, "__esModule", {
5
6
  value: true
6
7
  });
7
8
  exports.default = exports.SourceCodeEditor = void 0;
8
9
  var _react = require("react");
10
+ var _reactDom = require("react-dom");
9
11
  var _deepEqual = require("@instructure/ui-utils/lib/deepEqual.js");
10
12
  var _state = require("@codemirror/state");
11
13
  var _view = require("@codemirror/view");
@@ -28,7 +30,7 @@ var _requestAnimationFrame = require("@instructure/ui-dom-utils/lib/requestAnima
28
30
  var _ScreenReaderContent = require("@instructure/ui-a11y-content/lib/ScreenReaderContent");
29
31
  var _textDirectionContextConsumer = require("@instructure/ui-i18n/lib/textDirectionContextConsumer.js");
30
32
  var _emotion = require("@instructure/emotion");
31
- var _SearchPanel = _interopRequireDefault(require("./SearchPanel.js"));
33
+ var _SearchPanel = _interopRequireWildcard(require("./SearchPanel.js"));
32
34
  var _styles = _interopRequireDefault(require("./styles.js"));
33
35
  var _customKeybinding = require("./customKeybinding.js");
34
36
  var _props = require("./props.js");
@@ -86,10 +88,30 @@ let SourceCodeEditor = exports.SourceCodeEditor = (_dec = (0, _withDeterministic
86
88
  };
87
89
  _id;
88
90
  ref = null;
91
+ state = {
92
+ searchPanels: []
93
+ };
89
94
  _containerRef;
90
95
  _editorView;
91
96
  _raf = [];
97
+ _searchPanelId = 0;
98
+ _isUnmounting = false;
92
99
  _newSelectionAfterValueChange;
100
+ handleSearchPanelMount = (dom, view) => {
101
+ this.setState(state => ({
102
+ searchPanels: [...state.searchPanels, {
103
+ id: ++this._searchPanelId,
104
+ dom,
105
+ view
106
+ }]
107
+ }));
108
+ };
109
+ handleSearchPanelDestroy = dom => {
110
+ if (this._isUnmounting) return;
111
+ this.setState(state => ({
112
+ searchPanels: state.searchPanels.filter(panel => panel.dom !== dom)
113
+ }));
114
+ };
93
115
  handleRef = el => {
94
116
  const {
95
117
  elementRef
@@ -252,6 +274,7 @@ let SourceCodeEditor = exports.SourceCodeEditor = (_dec = (0, _withDeterministic
252
274
  this.assignAriaLabel();
253
275
  }
254
276
  componentWillUnmount() {
277
+ this._isUnmounting = true;
255
278
  this._editorView?.destroy();
256
279
  this.cancelAnimationFrames();
257
280
  }
@@ -269,7 +292,7 @@ let SourceCodeEditor = exports.SourceCodeEditor = (_dec = (0, _withDeterministic
269
292
  shouldUpdateExtensions(prevProps) {
270
293
  const propsToObserve = ['styles',
271
294
  // needed for theme update
272
- 'themeOverride', 'language', 'readOnly', 'editable', 'lineNumbers', 'highlightActiveLineGutter', 'foldGutter', 'lineWrapping', 'autofocus', 'spellcheck', 'direction', 'dir', 'rtlMoveVisually', 'indentOnLoad', 'indentWithTab', 'indentUnit', 'highlightActiveLine', 'attachment'];
295
+ 'themeOverride', 'language', 'readOnly', 'editable', 'lineNumbers', 'highlightActiveLineGutter', 'foldGutter', 'lineWrapping', 'autofocus', 'spellcheck', 'direction', 'dir', 'rtlMoveVisually', 'indentOnLoad', 'indentWithTab', 'indentUnit', 'highlightActiveLine', 'attachment', 'searchConfig'];
273
296
  for (const prop of propsToObserve) {
274
297
  if (!(0, _deepEqual.deepEqual)(this.props[prop], prevProps[prop])) {
275
298
  return true;
@@ -333,7 +356,10 @@ let SourceCodeEditor = exports.SourceCodeEditor = (_dec = (0, _withDeterministic
333
356
  // and adjust it as desired.
334
357
  (0, _view.highlightSpecialChars)(), (0, _commands.history)(), (0, _view.drawSelection)(), (0, _view.dropCursor)(), _state.EditorState.allowMultipleSelections.of(true), (0, _language.syntaxHighlighting)(_language.defaultHighlightStyle, {
335
358
  fallback: true
336
- }), (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)];
359
+ }), (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, {
360
+ onMount: this.handleSearchPanelMount,
361
+ onDestroy: this.handleSearchPanelDestroy
362
+ }), _view.keymap.of(this.keymaps)];
337
363
  }
338
364
  get keymaps() {
339
365
  // TODO: if more keymaps are added, list them in the docs as well (#Command keybinding)
@@ -536,14 +562,15 @@ let SourceCodeEditor = exports.SourceCodeEditor = (_dec = (0, _withDeterministic
536
562
  const {
537
563
  label,
538
564
  styles,
565
+ searchConfig,
539
566
  ...restProps
540
567
  } = this.props;
541
- return (0, _jsxRuntime.jsx)("div", {
568
+ return (0, _jsxRuntime.jsxs)("div", {
542
569
  "data-cid": "SourceCodeEditor",
543
570
  ref: this.handleRef,
544
571
  css: styles?.codeEditor,
545
572
  ...(0, _passthroughProps.passthroughProps)((0, _omitProps.omitProps)(restProps, SourceCodeEditor.allowedProps)),
546
- children: (0, _jsxRuntime.jsxs)("label", {
573
+ children: [(0, _jsxRuntime.jsxs)("label", {
547
574
  css: styles?.label,
548
575
  id: this._id,
549
576
  children: [(0, _jsxRuntime.jsx)(_ScreenReaderContent.ScreenReaderContent, {
@@ -552,7 +579,14 @@ let SourceCodeEditor = exports.SourceCodeEditor = (_dec = (0, _withDeterministic
552
579
  ref: this.handleContainerRef,
553
580
  css: styles?.codeEditorContainer
554
581
  })]
555
- })
582
+ }), searchConfig && this.state.searchPanels.map(({
583
+ id,
584
+ dom,
585
+ view
586
+ }) => /*#__PURE__*/(0, _reactDom.createPortal)((0, _jsxRuntime.jsx)(_SearchPanel.SearchPanel, {
587
+ view: view,
588
+ searchConfig: searchConfig
589
+ }), dom, String(id)))]
556
590
  });
557
591
  }
558
592
  }) || _class) || _class) || _class);
@@ -150,6 +150,10 @@ const generateStyle = (componentTheme, props, sharedTokens) => {
150
150
  '.cm-placeholder': {
151
151
  // for better contrast
152
152
  color: componentTheme.placeholderBackgroundColor
153
+ },
154
+ '.cm-panels': {
155
+ backgroundColor: componentTheme.background,
156
+ color: componentTheme.color
153
157
  }
154
158
  },
155
159
  highlightStyle: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-source-code-editor",
3
- "version": "11.7.4-snapshot-80",
3
+ "version": "11.7.4-snapshot-98",
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",
@@ -29,24 +29,24 @@
29
29
  "@codemirror/state": "^6.4.1",
30
30
  "@codemirror/view": "^6.34.1",
31
31
  "@lezer/highlight": "1.2.3",
32
- "@instructure/emotion": "11.7.4-snapshot-80",
33
- "@instructure/shared-types": "11.7.4-snapshot-80",
34
- "@instructure/ui-buttons": "11.7.4-snapshot-80",
35
- "@instructure/ui-dom-utils": "11.7.4-snapshot-80",
36
- "@instructure/ui-a11y-content": "11.7.4-snapshot-80",
37
- "@instructure/ui-i18n": "11.7.4-snapshot-80",
38
- "@instructure/ui-react-utils": "11.7.4-snapshot-80",
39
- "@instructure/ui-icons": "11.7.4-snapshot-80",
40
- "@instructure/ui-text-input": "11.7.4-snapshot-80",
41
- "@instructure/ui-themes": "11.7.4-snapshot-80",
42
- "@instructure/ui-utils": "11.7.4-snapshot-80"
32
+ "@instructure/emotion": "11.7.4-snapshot-98",
33
+ "@instructure/ui-a11y-content": "11.7.4-snapshot-98",
34
+ "@instructure/ui-buttons": "11.7.4-snapshot-98",
35
+ "@instructure/shared-types": "11.7.4-snapshot-98",
36
+ "@instructure/ui-dom-utils": "11.7.4-snapshot-98",
37
+ "@instructure/ui-i18n": "11.7.4-snapshot-98",
38
+ "@instructure/ui-icons": "11.7.4-snapshot-98",
39
+ "@instructure/ui-react-utils": "11.7.4-snapshot-98",
40
+ "@instructure/ui-text-input": "11.7.4-snapshot-98",
41
+ "@instructure/ui-themes": "11.7.4-snapshot-98",
42
+ "@instructure/ui-utils": "11.7.4-snapshot-98"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@testing-library/jest-dom": "^6.9.1",
46
46
  "@testing-library/react": "16.3.2",
47
47
  "@testing-library/user-event": "^14.6.1",
48
48
  "vitest": "^4.1.9",
49
- "@instructure/ui-babel-preset": "11.7.4-snapshot-80"
49
+ "@instructure/ui-babel-preset": "11.7.4-snapshot-98"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "react": ">=18 <=19",
@@ -39,7 +39,6 @@ import {
39
39
  IconArrowOpenUpLine,
40
40
  IconSearchLine
41
41
  } from '@instructure/ui-icons'
42
- import { createRoot } from 'react-dom/client'
43
42
 
44
43
  export type SearchConfig = {
45
44
  placeholder: string
@@ -47,7 +46,18 @@ export type SearchConfig = {
47
46
  prevResultLabel: string
48
47
  }
49
48
 
50
- function SearchPanel({
49
+
50
+ export type SearchPanelHandlers = {
51
+ onMount: (dom: HTMLElement, view: EditorView) => void
52
+ onDestroy: (dom: HTMLElement) => void
53
+ }
54
+
55
+ /**
56
+ * ---
57
+ * private: true
58
+ * ---
59
+ */
60
+ export function SearchPanel({
51
61
  view,
52
62
  searchConfig
53
63
  }: {
@@ -135,15 +145,24 @@ function SearchPanel({
135
145
  )
136
146
  }
137
147
 
138
- export default function customSearch(searchConfig: SearchConfig | undefined) {
148
+ export default function customSearch(
149
+ searchConfig: SearchConfig | undefined,
150
+ handlers: SearchPanelHandlers
151
+ ) {
139
152
  return searchConfig
140
153
  ? search({
141
154
  createPanel: (view) => {
142
155
  const dom = document.createElement('div')
143
156
  dom.style.padding = '8px'
144
- const root = createRoot(dom)
145
- root.render(<SearchPanel view={view} searchConfig={searchConfig} />)
146
- return { dom }
157
+ return {
158
+ dom,
159
+ mount() {
160
+ handlers.onMount(dom, view)
161
+ },
162
+ destroy() {
163
+ handlers.onDestroy(dom)
164
+ }
165
+ }
147
166
  }
148
167
  })
149
168
  : []
@@ -23,6 +23,7 @@
23
23
  */
24
24
 
25
25
  import { Component } from 'react'
26
+ import { createPortal } from 'react-dom'
26
27
  import { deepEqual as isEqual } from '@instructure/ui-utils'
27
28
 
28
29
  import { EditorSelection, EditorState, StateEffect } from '@codemirror/state'
@@ -89,7 +90,7 @@ import { textDirectionContextConsumer } from '@instructure/ui-i18n'
89
90
 
90
91
  import { withStyleNew } from '@instructure/emotion'
91
92
 
92
- import customSearch from './SearchPanel.js'
93
+ import customSearch, { SearchPanel } from './SearchPanel.js'
93
94
 
94
95
  import generateStyle from './styles.js'
95
96
 
@@ -98,6 +99,16 @@ import { rtlHorizontalArrowKeymap } from './customKeybinding.js'
98
99
  import { allowedProps } from './props.js'
99
100
  import type { SourceCodeEditorProps } from './props'
100
101
 
102
+ type SearchPanelInstance = {
103
+ id: number
104
+ dom: HTMLElement
105
+ view: EditorView
106
+ }
107
+
108
+ type SourceCodeEditorState = {
109
+ searchPanels: SearchPanelInstance[]
110
+ }
111
+
101
112
  /**
102
113
  ---
103
114
  category: components
@@ -106,7 +117,10 @@ category: components
106
117
  @withDeterministicId()
107
118
  @withStyleNew(generateStyle)
108
119
  @textDirectionContextConsumer()
109
- class SourceCodeEditor extends Component<SourceCodeEditorProps> {
120
+ class SourceCodeEditor extends Component<
121
+ SourceCodeEditorProps,
122
+ SourceCodeEditorState
123
+ > {
110
124
  static displayName = 'SourceCodeEditor'
111
125
  static readonly componentId = 'SourceCodeEditor'
112
126
 
@@ -133,13 +147,35 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
133
147
 
134
148
  ref: HTMLDivElement | null = null
135
149
 
150
+ state: SourceCodeEditorState = { searchPanels: [] }
151
+
136
152
  private _containerRef?: HTMLDivElement
137
153
  private _editorView?: EditorView
138
154
 
139
155
  private _raf: RequestAnimationFrameType[] = []
140
156
 
157
+ private _searchPanelId = 0
158
+ private _isUnmounting = false
159
+
141
160
  private _newSelectionAfterValueChange?: EditorSelection
142
161
 
162
+ handleSearchPanelMount = (dom: HTMLElement, view: EditorView) => {
163
+ this.setState((state) => ({
164
+ searchPanels: [
165
+ ...state.searchPanels,
166
+ { id: ++this._searchPanelId, dom, view }
167
+ ]
168
+ }))
169
+ }
170
+
171
+ handleSearchPanelDestroy = (dom: HTMLElement) => {
172
+ if (this._isUnmounting) return
173
+
174
+ this.setState((state) => ({
175
+ searchPanels: state.searchPanels.filter((panel) => panel.dom !== dom)
176
+ }))
177
+ }
178
+
143
179
  handleRef = (el: HTMLDivElement | null) => {
144
180
  const { elementRef } = this.props
145
181
 
@@ -310,6 +346,7 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
310
346
  }
311
347
 
312
348
  componentWillUnmount() {
349
+ this._isUnmounting = true
313
350
  this._editorView?.destroy()
314
351
 
315
352
  this.cancelAnimationFrames()
@@ -349,7 +386,8 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
349
386
  'indentWithTab',
350
387
  'indentUnit',
351
388
  'highlightActiveLine',
352
- 'attachment'
389
+ 'attachment',
390
+ 'searchConfig'
353
391
  ]
354
392
 
355
393
  for (const prop of propsToObserve) {
@@ -438,7 +476,10 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
438
476
  crosshairCursor(),
439
477
  highlightSelectionMatches(),
440
478
  indentOnInput(),
441
- customSearch(this.props.searchConfig),
479
+ customSearch(this.props.searchConfig, {
480
+ onMount: this.handleSearchPanelMount,
481
+ onDestroy: this.handleSearchPanelDestroy
482
+ }),
442
483
  keymap.of(this.keymaps)
443
484
  ]
444
485
  }
@@ -669,7 +710,7 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
669
710
  }
670
711
 
671
712
  render() {
672
- const { label, styles, ...restProps } = this.props
713
+ const { label, styles, searchConfig, ...restProps } = this.props
673
714
 
674
715
  return (
675
716
  <div
@@ -687,6 +728,14 @@ class SourceCodeEditor extends Component<SourceCodeEditorProps> {
687
728
  css={styles?.codeEditorContainer}
688
729
  />
689
730
  </label>
731
+ {searchConfig &&
732
+ this.state.searchPanels.map(({ id, dom, view }) =>
733
+ createPortal(
734
+ <SearchPanel view={view} searchConfig={searchConfig} />,
735
+ dom,
736
+ String(id)
737
+ )
738
+ )}
690
739
  </div>
691
740
  )
692
741
  }
@@ -160,6 +160,10 @@ const generateStyle = (
160
160
  '.cm-placeholder': {
161
161
  // for better contrast
162
162
  color: componentTheme.placeholderBackgroundColor
163
+ },
164
+ '.cm-panels': {
165
+ backgroundColor: componentTheme.background,
166
+ color: componentTheme.color
163
167
  }
164
168
  },
165
169