@teselagen/ove 0.8.28 → 0.8.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teselagen/ove",
3
- "version": "0.8.28",
3
+ "version": "0.8.30",
4
4
  "main": "./src/index.js",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/TeselaGen/tg-oss",
@@ -19,8 +19,8 @@
19
19
  "@teselagen/file-utils": "0.3.23",
20
20
  "@teselagen/range-utils": "0.3.20",
21
21
  "@teselagen/react-list": "0.8.18",
22
- "@teselagen/sequence-utils": "0.3.41",
23
- "@teselagen/ui": "0.10.17",
22
+ "@teselagen/sequence-utils": "0.3.42",
23
+ "@teselagen/ui": "0.10.18",
24
24
  "@use-gesture/react": "10.3.0",
25
25
  "biomsa": "^0.2.4",
26
26
  "classnames": "^2.3.2",
@@ -1,12 +1,4 @@
1
- declare const _default: ((state: any) => {
2
- cutsitesByName: {};
3
- cutsitesById: {};
4
- cutsitesArray: never[];
5
- }) & import('reselect').OutputSelectorFields<(...args: any) => {
6
- cutsitesByName: {};
7
- cutsitesById: {};
8
- cutsitesArray: never[];
9
- }, {
1
+ declare const _default: ((state: any, ...params: any[]) => any) & import('reselect').OutputSelectorFields<(...args: readonly unknown[]) => any, {
10
2
  clearCache: () => void;
11
3
  }> & {
12
4
  clearCache: () => void;
@@ -1,4 +1,8 @@
1
- declare const _default: ((state: any, ...params: any[]) => any) & import('reselect').OutputSelectorFields<(...args: readonly unknown[]) => any, {
1
+ declare const _default: ((state: any) => {
2
+ cutsitesByName: {};
3
+ }) & import('reselect').OutputSelectorFields<(args_0: any, args_1: any, args_2: any, args_3: any) => {
4
+ cutsitesByName: {};
5
+ }, {
2
6
  clearCache: () => void;
3
7
  }> & {
4
8
  clearCache: () => void;
@@ -361,6 +361,7 @@ export class Editor extends React.Component {
361
361
  hoveredId,
362
362
  isFullscreen,
363
363
  maxInsertSize,
364
+ getAcceptedInsertChars,
364
365
  showAminoAcidUnitAsCodon,
365
366
  maxAnnotationsToDisplay,
366
367
  minHeight = 400,
@@ -595,6 +596,7 @@ export class Editor extends React.Component {
595
596
  {...panelPropsToSpread}
596
597
  editorName={editorName}
597
598
  maxInsertSize={maxInsertSize}
599
+ getAcceptedInsertChars={getAcceptedInsertChars}
598
600
  showAminoAcidUnitAsCodon={showAminoAcidUnitAsCodon}
599
601
  isProtein={sequenceData.isProtein}
600
602
  onlyShowLabelsThatDoNotFit={onlyShowLabelsThatDoNotFit}
@@ -51,6 +51,8 @@ export const userDefinedHandlersAndOpts = [
51
51
  "enzymeManageOverride",
52
52
  "enzymeGroupsOverride",
53
53
  "additionalEnzymes",
54
+ "getAcceptedInsertChars",
55
+ "maxInsertSize",
54
56
  "onDelete",
55
57
  "onCopy",
56
58
  "autoAnnotateFeatures",
@@ -41,12 +41,23 @@ const Dialogs = {
41
41
  export function GlobalDialog(props) {
42
42
  const { dialogOverrides = {}, editorName } = props;
43
43
  const [uniqKey, setUniqKeyToForceRerender] = useState();
44
+
44
45
  useEffect(() => {
45
46
  //on unmount, clear the global dialog state..
46
47
  return () => {
47
48
  hideDialog();
48
49
  };
49
50
  }, []);
51
+
52
+ useEffect(() => {
53
+ dialogHolder.setUniqKeyToForceRerender = setUniqKeyToForceRerender;
54
+
55
+ if (editorName) {
56
+ const slot = (dialogHolder[editorName] = dialogHolder[editorName] || {});
57
+ slot.setUniqKeyToForceRerender = setUniqKeyToForceRerender;
58
+ }
59
+ }, [editorName]);
60
+
50
61
  if (
51
62
  dialogHolder.editorName &&
52
63
  editorName &&
@@ -54,7 +65,6 @@ export function GlobalDialog(props) {
54
65
  ) {
55
66
  return null;
56
67
  }
57
- dialogHolder.setUniqKeyToForceRerender = setUniqKeyToForceRerender;
58
68
  const Comp =
59
69
  dialogHolder.CustomModalComponent ||
60
70
  dialogOverrides[dialogHolder.overrideName] ||
@@ -15,6 +15,8 @@ export function showDialog({
15
15
  if (!dialogHolder.dialogType && ModalComponent) {
16
16
  dialogHolder.dialogType = "TGCustomModal";
17
17
  }
18
+
19
+ dialogHolder.editorName = props?.editorName;
18
20
  // check if focused element in the dom is within a given editor and add an editor prop to the dialog
19
21
  if (document.activeElement && document.activeElement.closest(".veEditor")) {
20
22
  let editorName;
@@ -36,15 +38,25 @@ export function showDialog({
36
38
  dialogHolder.CustomModalComponent = ModalComponent;
37
39
  dialogHolder.props = props;
38
40
  dialogHolder.overrideName = overrideName;
39
- dialogHolder.setUniqKeyToForceRerender(shortid());
41
+ if (dialogHolder.editorName && dialogHolder?.[dialogHolder.editorName]) {
42
+ dialogHolder?.[dialogHolder.editorName]?.setUniqKeyToForceRerender?.(
43
+ shortid()
44
+ );
45
+ } else {
46
+ dialogHolder?.setUniqKeyToForceRerender?.(shortid());
47
+ }
40
48
  }
41
49
  export function hideDialog() {
42
50
  delete dialogHolder.dialogType;
43
51
  delete dialogHolder.CustomModalComponent;
44
52
  delete dialogHolder.props;
45
53
  delete dialogHolder.overrideName;
54
+ if (dialogHolder.editorName && dialogHolder?.[dialogHolder.editorName]) {
55
+ dialogHolder?.[dialogHolder.editorName]?.setUniqKeyToForceRerender?.();
56
+ } else {
57
+ dialogHolder?.setUniqKeyToForceRerender?.();
58
+ }
46
59
  delete dialogHolder.editorName;
47
- dialogHolder.setUniqKeyToForceRerender();
48
60
  }
49
61
 
50
62
  const typeToDialogType = {
@@ -37,6 +37,7 @@ class _LinearView extends React.Component {
37
37
  bindOutsideChangeHelper = {};
38
38
  getNearestCursorPositionToMouseEvent(rowData, event, callback) {
39
39
  //loop through all the rendered rows to see if the click event lands in one of them
40
+ const isProtein = this.props.sequenceData?.isProtein;
40
41
  let nearestCaretPos = 0;
41
42
  let rowDomNode = this.linearView;
42
43
  rowDomNode = rowDomNode.querySelector(".veRowItem");
@@ -52,11 +53,11 @@ class _LinearView extends React.Component {
52
53
  this.charWidth
53
54
  );
54
55
  nearestCaretPos = numberOfBPsInFromRowStart + 0;
55
- if (nearestCaretPos > maxEnd + 1) {
56
- nearestCaretPos = maxEnd + 1;
56
+ if (nearestCaretPos > maxEnd) {
57
+ nearestCaretPos = isProtein ? maxEnd + 1 : maxEnd;
57
58
  }
58
59
  }
59
- if (this.props.sequenceData && this.props.sequenceData.isProtein) {
60
+ if (isProtein) {
60
61
  nearestCaretPos = Math.round(nearestCaretPos / 3) * 3;
61
62
  }
62
63
  if (maxEnd === 0) nearestCaretPos = 0;
@@ -4,14 +4,101 @@ import withHover from "../../helperComponents/withHover";
4
4
  import getAnnotationNameAndStartStopString from "../../utils/getAnnotationNameAndStartStopString";
5
5
 
6
6
  import React, { useState } from "react";
7
- import { doesLabelFitInAnnotation } from "../utils";
7
+ import { doesLabelFitInAnnotation, getAnnotationTextWidth } from "../utils";
8
8
  import { noop } from "lodash-es";
9
9
  import getAnnotationClassnames from "../../utils/getAnnotationClassnames";
10
- import { getStripedPattern } from "../../utils/editorUtils";
11
10
  import { ANNOTATION_LABEL_FONT_WIDTH } from "../constants";
11
+ import { getStripedPattern } from "../../utils/editorUtils";
12
12
  import { partOverhangs } from "../partOverhangs";
13
13
  import { Tooltip } from "@blueprintjs/core";
14
14
 
15
+ function getAnnotationTextOffset({
16
+ width,
17
+ nameToDisplay,
18
+ hasAPoint,
19
+ pointiness,
20
+ forward
21
+ }) {
22
+ return (
23
+ width / 2 -
24
+ getAnnotationTextWidth(nameToDisplay) / 2 -
25
+ (hasAPoint
26
+ ? (pointiness / 2 + ANNOTATION_LABEL_FONT_WIDTH / 2) * (forward ? 1 : -1)
27
+ : 0)
28
+ );
29
+ }
30
+
31
+ function getAnnotationNameInfo({
32
+ name,
33
+ width,
34
+ hasAPoint,
35
+ pointiness,
36
+ forward,
37
+ charWidth,
38
+ truncateLabelsThatDoNotFit,
39
+ onlyShowLabelsThatDoNotFit,
40
+ annotation
41
+ }) {
42
+ let nameToDisplay = name;
43
+ let textOffset = getAnnotationTextOffset({
44
+ width,
45
+ nameToDisplay,
46
+ hasAPoint,
47
+ pointiness,
48
+ forward
49
+ });
50
+ const widthAvailableForText = width - ANNOTATION_LABEL_FONT_WIDTH * 2;
51
+ if (
52
+ !doesLabelFitInAnnotation(name, { width }, charWidth) ||
53
+ (!onlyShowLabelsThatDoNotFit &&
54
+ ["parts", "features"].includes(annotation.annotationTypePlural))
55
+ ) {
56
+ if (truncateLabelsThatDoNotFit) {
57
+ // Binary search for max fitting substring
58
+ let left = 0;
59
+ let right = name.length;
60
+ let bestFit = "";
61
+
62
+ while (left <= right) {
63
+ const mid = Math.floor((left + right) / 2);
64
+ const candidate = name.slice(0, mid);
65
+ const candidateWidth = getAnnotationTextWidth(candidate);
66
+
67
+ if (candidateWidth <= widthAvailableForText) {
68
+ if (candidate.length > bestFit.length) {
69
+ bestFit = candidate;
70
+ }
71
+ left = mid + 1;
72
+ } else {
73
+ right = mid - 1;
74
+ }
75
+ }
76
+ if (bestFit.length < name.length) {
77
+ bestFit = bestFit.slice(0, -2) + "..";
78
+ }
79
+
80
+ nameToDisplay = bestFit;
81
+
82
+ if (nameToDisplay.length <= 3) {
83
+ textOffset = 0;
84
+ nameToDisplay = "";
85
+ } else {
86
+ textOffset = getAnnotationTextOffset({
87
+ width,
88
+ nameToDisplay,
89
+ hasAPoint,
90
+ pointiness,
91
+ forward
92
+ });
93
+ }
94
+ } else {
95
+ textOffset = 0;
96
+ nameToDisplay = "";
97
+ }
98
+ }
99
+ return { textOffset, nameToDisplay };
100
+ }
101
+
15
102
  function PointedAnnotation(props) {
16
103
  const {
17
104
  className,
@@ -165,39 +252,19 @@ function PointedAnnotation(props) {
165
252
  Q ${pointiness},${height / 2} ${0},${0}
166
253
  z`;
167
254
  }
168
- let nameToDisplay = name;
169
- let textOffset =
170
- width / 2 -
171
- (name.length * 5) / 2 -
172
- (hasAPoint ? (pointiness / 2) * (forward ? 1 : -1) : 0);
173
- if (
174
- !doesLabelFitInAnnotation(name, { width }, charWidth) ||
175
- (!onlyShowLabelsThatDoNotFit &&
176
- ["parts", "features"].includes(annotation.annotationTypePlural))
177
- ) {
178
- if (truncateLabelsThatDoNotFit) {
179
- const fractionToDisplay =
180
- width / (name.length * ANNOTATION_LABEL_FONT_WIDTH);
181
- const numLetters = Math.floor(fractionToDisplay * name.length);
182
- nameToDisplay = name.slice(0, numLetters);
183
- if (nameToDisplay.length > 3) {
184
- if (nameToDisplay.length !== name.length) {
185
- nameToDisplay += "..";
186
- }
187
255
 
188
- textOffset =
189
- width / 2 -
190
- (nameToDisplay.length * 5) / 2 -
191
- (hasAPoint ? (pointiness / 2) * (forward ? 1 : -1) : 0);
192
- } else {
193
- textOffset = 0;
194
- nameToDisplay = "";
195
- }
196
- } else {
197
- textOffset = 0;
198
- nameToDisplay = "";
199
- }
200
- }
256
+ const { textOffset, nameToDisplay } = getAnnotationNameInfo({
257
+ name,
258
+ width,
259
+ hasAPoint,
260
+ pointiness,
261
+ forward,
262
+ charWidth,
263
+ truncateLabelsThatDoNotFit,
264
+ onlyShowLabelsThatDoNotFit,
265
+ annotation
266
+ });
267
+
201
268
  let _textColor = textColor;
202
269
  if (!textColor) {
203
270
  try {
@@ -1,14 +1,30 @@
1
1
  import { ANNOTATION_LABEL_FONT_WIDTH } from "./constants";
2
2
  import { getWidth } from "./getXStartAndWidthOfRowAnnotation";
3
3
 
4
+ // Cache canvas context for text measurement
5
+ let measureCanvas;
6
+ export function getAnnotationTextWidth(
7
+ text,
8
+ fontSize = ANNOTATION_LABEL_FONT_WIDTH,
9
+ fontFamily = "monospace"
10
+ ) {
11
+ if (!measureCanvas) {
12
+ measureCanvas = document.createElement("canvas");
13
+ }
14
+ const ctx = measureCanvas.getContext("2d");
15
+ ctx.font = `${fontSize}px ${fontFamily}`;
16
+ return ctx.measureText(text).width;
17
+ }
18
+
4
19
  export const doesLabelFitInAnnotation = (
5
20
  text = "",
6
21
  { range, width },
7
22
  charWidth
8
23
  ) => {
9
- const textLength = text.length * ANNOTATION_LABEL_FONT_WIDTH;
10
- const widthMinusOne =
11
- (range ? getWidth(range, charWidth, 0) : width) - charWidth;
24
+ const textLength = getAnnotationTextWidth(text);
25
+ const widthMinusOne = range
26
+ ? getWidth(range, charWidth, 0) - ANNOTATION_LABEL_FONT_WIDTH * 2
27
+ : width - ANNOTATION_LABEL_FONT_WIDTH * 2;
12
28
  return widthMinusOne > textLength;
13
29
  };
14
30
 
@@ -284,7 +284,7 @@ const fileCommandDefs = {
284
284
  },
285
285
  exportSequenceAsTeselagenJson: {
286
286
  name: "Download Teselagen JSON File",
287
- handler: props => props.exportSequenceToFile("teselagenJson")
287
+ handler: props => props.exportSequenceToFile("teselagenJson", { getAcceptedInsertChars: props.getAcceptedInsertChars})
288
288
  },
289
289
 
290
290
  viewProperties: {
@@ -10,10 +10,9 @@ import { getRangeLength } from "@teselagen/range-utils";
10
10
  import { getOrfColor } from "../../constants/orfFrameToColorMap";
11
11
  import { connectToEditor } from "../../withEditorProps";
12
12
  import { compose } from "recompose";
13
- import selectors from "../../selectors";
14
13
 
15
14
  import getCommands from "../../commands";
16
- import { sizeSchema } from "./utils";
15
+ import { sizeSchema, getMemoOrfs } from "./utils";
17
16
  import { orfsSubmenu } from "../../MenuBar/viewSubmenu";
18
17
  import { getVisFilter } from "./GenericAnnotationProperties";
19
18
 
@@ -107,7 +106,7 @@ export default compose(
107
106
  readOnly,
108
107
  annotationVisibility,
109
108
  useAdditionalOrfStartCodons,
110
- orfs: selectors.orfsSelector(editorState),
109
+ orfs: getMemoOrfs(editorState),
111
110
  sequenceLength: sequence.length,
112
111
  sequenceData,
113
112
  minimumOrfSize
@@ -14,6 +14,7 @@ import selectors from "../../selectors";
14
14
  import { getMassOfAaString } from "@teselagen/sequence-utils";
15
15
  import { translationsSubmenu } from "../../MenuBar/viewSubmenu";
16
16
  import { getVisFilter } from "./GenericAnnotationProperties";
17
+ import { getMemoOrfs } from "./utils";
17
18
 
18
19
  class TranslationProperties extends React.Component {
19
20
  constructor(props) {
@@ -140,7 +141,7 @@ export default compose(
140
141
  return {
141
142
  readOnly,
142
143
  translations: selectors.translationsSelector(editorState),
143
- orfs: selectors.orfsSelector(editorState),
144
+ orfs: getMemoOrfs(editorState),
144
145
  annotationVisibility,
145
146
  sequenceLength: (sequenceData.sequence || "").length,
146
147
  sequenceData
@@ -1,6 +1,8 @@
1
1
  import React from "react";
2
+ import { isEqual } from "lodash-es";
2
3
  import { convertDnaCaretPositionOrRangeToAA } from "@teselagen/sequence-utils";
3
4
  import { convertRangeTo1Based } from "@teselagen/range-utils";
5
+ import selectors from "../../selectors";
4
6
 
5
7
  export const sizeSchema = isProtein => ({
6
8
  path: "size",
@@ -35,3 +37,30 @@ export const sizeSchema = isProtein => ({
35
37
  );
36
38
  }
37
39
  });
40
+
41
+ export const getMemoOrfs = (() => {
42
+ let lastDeps;
43
+ let lastResult;
44
+ return (editorState) => {
45
+ const {
46
+ sequenceData,
47
+ minimumOrfSize,
48
+ useAdditionalOrfStartCodons
49
+ } = editorState;
50
+
51
+ const { sequence, circular } = sequenceData;
52
+
53
+ const deps = {
54
+ sequence,
55
+ circular,
56
+ minimumOrfSize,
57
+ useAdditionalOrfStartCodons
58
+ };
59
+ if (lastResult && isEqual(deps, lastDeps)) {
60
+ return lastResult;
61
+ }
62
+ lastResult = selectors.orfsSelector(editorState);
63
+ lastDeps = deps;
64
+ return lastResult;
65
+ };
66
+ })();
@@ -4,12 +4,55 @@ import sequenceSelector from "./sequenceSelector";
4
4
  import restrictionEnzymesSelector from "./restrictionEnzymesSelector";
5
5
  import cutsiteLabelColorSelector from "./cutsiteLabelColorSelector";
6
6
  import { createSelector } from "reselect";
7
+ import { isEqual } from "lodash-es";
7
8
 
8
9
  import { flatMap as flatmap, map } from "lodash-es";
9
10
  import { getCutsitesFromSequence } from "@teselagen/sequence-utils";
10
11
  import { getLowerCaseObj } from "../utils/arrayUtils";
11
12
 
12
- function cutsitesSelector(sequence, circular, enzymeList, cutsiteLabelColors) {
13
+ // [{ args: {sequence,circular,enzymeList,cutsiteLabelColors}, result }]
14
+ const cutsitesCache = [];
15
+
16
+ function getCachedResult(argsObj) {
17
+ const idx = cutsitesCache.findIndex(
18
+ entry =>
19
+ entry &&
20
+ isEqual(entry.args, argsObj)
21
+ );
22
+ if (idx === -1) return;
23
+ const hit = cutsitesCache[idx];
24
+ return hit.result;
25
+ }
26
+
27
+ function setCachedResult(
28
+ argsObj,
29
+ result,
30
+ cacheSize = 1
31
+ ) {
32
+ cutsitesCache.push({
33
+ args: argsObj,
34
+ result
35
+ });
36
+ //keep cache size manageable
37
+ if (cutsitesCache.length > cacheSize) cutsitesCache.shift();
38
+ }
39
+
40
+ function cutsitesSelector(
41
+ sequence,
42
+ circular,
43
+ enzymeList,
44
+ cutsiteLabelColors,
45
+ editorSize = 1
46
+ ) {
47
+ const cachedResult = getCachedResult({
48
+ sequence,
49
+ circular,
50
+ enzymeList,
51
+ cutsiteLabelColors
52
+ });
53
+ if (cachedResult) {
54
+ return cachedResult;
55
+ }
13
56
  //get the cutsites grouped by enzyme
14
57
  const cutsitesByName = getLowerCaseObj(
15
58
  getCutsitesFromSequence(sequence, circular, map(enzymeList))
@@ -45,11 +88,22 @@ function cutsitesSelector(sequence, circular, enzymeList, cutsiteLabelColors) {
45
88
  const cutsitesArray = flatmap(cutsitesByName, function (cutsitesForEnzyme) {
46
89
  return cutsitesForEnzyme;
47
90
  });
48
- return {
91
+ const result = {
49
92
  cutsitesByName,
50
93
  cutsitesById,
51
94
  cutsitesArray
52
95
  };
96
+ setCachedResult(
97
+ {
98
+ sequence,
99
+ circular,
100
+ enzymeList,
101
+ cutsiteLabelColors
102
+ },
103
+ result,
104
+ editorSize
105
+ );
106
+ return result;
53
107
  }
54
108
 
55
109
  export default createSelector(
@@ -57,5 +111,6 @@ export default createSelector(
57
111
  circularSelector,
58
112
  restrictionEnzymesSelector,
59
113
  cutsiteLabelColorSelector,
114
+ editorState => editorState.editorSize,
60
115
  cutsitesSelector
61
116
  );
@@ -82,6 +82,7 @@ class SequenceInputNoHotkeys extends React.Component {
82
82
  caretPosition,
83
83
  sequenceData,
84
84
  maxInsertSize,
85
+ getAcceptedInsertChars,
85
86
  showAminoAcidUnitAsCodon
86
87
  } = this.props;
87
88
  const { charsToInsert, hasTempError } = this.state;
@@ -151,8 +152,9 @@ class SequenceInputNoHotkeys extends React.Component {
151
152
  e.target.value,
152
153
  {
153
154
  ...sequenceData,
154
- name: undefined
155
- }
155
+ name: undefined,
156
+ getAcceptedInsertChars
157
+ },
156
158
  );
157
159
  if (warnings.length) {
158
160
  this.setState({
@@ -212,7 +212,8 @@ function VectorInteractionHOC(Component /* options */) {
212
212
  onPaste,
213
213
  disableBpEditing,
214
214
  sequenceData,
215
- maxInsertSize
215
+ maxInsertSize,
216
+ getAcceptedInsertChars
216
217
  } = this.props;
217
218
 
218
219
  if (disableBpEditing) {
@@ -260,7 +261,8 @@ function VectorInteractionHOC(Component /* options */) {
260
261
  topLevelSeqData: sequenceData,
261
262
  provideNewIdsForAnnotations: true,
262
263
  annotationsAsObjects: true,
263
- noCdsTranslations: true
264
+ noCdsTranslations: true,
265
+ getAcceptedInsertChars
264
266
  });
265
267
  if (!seqDataToInsert.sequence.length)
266
268
  return window.toastr.warning("Sorry no valid base pairs to paste");
@@ -283,7 +285,8 @@ function VectorInteractionHOC(Component /* options */) {
283
285
  selectionLayer,
284
286
  copyOptions,
285
287
  disableBpEditing,
286
- readOnly
288
+ readOnly,
289
+ getAcceptedInsertChars,
287
290
  } = this.props;
288
291
  const onCut = this.props.onCut || this.props.onCopy || noop;
289
292
  const seqData = tidyUpSequenceData(
@@ -308,7 +311,8 @@ function VectorInteractionHOC(Component /* options */) {
308
311
  {
309
312
  doNotRemoveInvalidChars: true,
310
313
  annotationsAsObjects: true,
311
- includeProteinSequence: true
314
+ includeProteinSequence: true,
315
+ getAcceptedInsertChars
312
316
  }
313
317
  );
314
318
 
@@ -340,7 +344,8 @@ function VectorInteractionHOC(Component /* options */) {
340
344
  e,
341
345
  tidyUpSequenceData(seqData, {
342
346
  doNotRemoveInvalidChars: true,
343
- annotationsAsObjects: true
347
+ annotationsAsObjects: true,
348
+ getAcceptedInsertChars
344
349
  }),
345
350
  this.props
346
351
  );
@@ -404,6 +409,7 @@ function VectorInteractionHOC(Component /* options */) {
404
409
  readOnly,
405
410
  disableBpEditing,
406
411
  maxInsertSize,
412
+ getAcceptedInsertChars,
407
413
  showAminoAcidUnitAsCodon
408
414
  } = this.props;
409
415
  const sequenceLength = sequenceData.sequence.length;
@@ -423,6 +429,7 @@ function VectorInteractionHOC(Component /* options */) {
423
429
  sequenceLength,
424
430
  caretPosition,
425
431
  maxInsertSize,
432
+ getAcceptedInsertChars,
426
433
  showAminoAcidUnitAsCodon,
427
434
  handleInsert: async seqDataToInsert => {
428
435
  await insertAndSelectHelper({