aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionSchema/SchemaCellField.tsx
diff options
context:
space:
mode:
authorNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-06-20 01:57:07 -0400
committerNathan-SR <144961007+Nathan-SR@users.noreply.github.com>2024-06-20 01:57:07 -0400
commitd207cf565968167c59b16baf6ca5ce2543c680ea (patch)
tree28b4757d490e5807201616c623c2bdc2c2f66ba6 /src/client/views/collections/collectionSchema/SchemaCellField.tsx
parentb04015291de1d785ad54c0fad1f66903fb055bf4 (diff)
cursor position consistency for schema cell field
Diffstat (limited to 'src/client/views/collections/collectionSchema/SchemaCellField.tsx')
-rw-r--r--src/client/views/collections/collectionSchema/SchemaCellField.tsx86
1 files changed, 72 insertions, 14 deletions
diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
index 5f758683d..3af3b1d61 100644
--- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx
@@ -22,8 +22,10 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
private _disposers: { [name: string]: IReactionDisposer } = {};
private _inputref: HTMLDivElement | null = null;
+ private _content: string = '';
_overlayDisposer?: () => void;
@observable _editing: boolean = false;
+ @observable _displayedContent = '';
constructor(props: SchemaCellFieldProps) {
super(props);
@@ -50,12 +52,14 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
},
{ fireImmediately: true }
);
+ this._content = this.props.GetValue() ?? '';
+ this.setContent(this._content);
}
componentDidUpdate(prevProps: Readonly<SchemaCellFieldProps>) {
super.componentDidUpdate(prevProps);
if (this._editing && this._props.editing === false) {
- this._inputref?.innerText && this.finalizeEdit(this._inputref.innerText, false, true, false);
+ this.finalizeEdit(false, true, false);
} else
runInAction(() => {
if (this._props.editing !== undefined) this._editing = this._props.editing;
@@ -65,9 +69,14 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
componentWillUnmount(): void {
this._overlayDisposer?.();
this._disposers.editing?.();
- this._inputref?.innerText && this.finalizeEdit(this._inputref.innerText, false, true, false);
+ this.finalizeEdit(false, true, false);
}
+ @action
+ setContent = (content: string) => {
+ this._displayedContent = content;
+ };
+
@action
setIsFocused = (value: boolean) => {
const wasFocused = this._editing;
@@ -75,7 +84,52 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
return wasFocused !== this._editing;
};
- onChange = (e: FormEvent<HTMLInputElement>) => {
+ get cursorPosition() {
+ const selection = window.getSelection();
+ if (!selection || selection.rangeCount === 0 || !this._inputref) return null;
+
+ const range = selection.getRangeAt(0);
+ const adjRange = range.cloneRange();
+
+ adjRange.selectNodeContents(this._inputref);
+ adjRange.setEnd(range.startContainer, range.startOffset);
+
+ return adjRange.toString().length;
+ }
+
+ restoreCursorPosition = (position: number | null) => {
+ const selection = window.getSelection();
+ if (!selection || position === null || !this._inputref) return;
+
+ const range = document.createRange();
+ range.setStart(this._inputref, 0);
+ range.collapse(true);
+
+ let currentPos = 0;
+ const setRange = (nodes: NodeList) => {
+ for (let i = 0; i < nodes.length; ++i) {
+ const node = nodes[i];
+
+ if (node.nodeType === Node.TEXT_NODE) {
+ if (!node.textContent) return;
+ const nextPos = currentPos + node.textContent.length;
+ if (position <= nextPos) {
+ range.setStart(node, position - currentPos);
+ range.collapse(true);
+ selection.removeAllRanges();
+ selection.addRange(range);
+ return true;
+ }
+ currentPos = nextPos;
+
+ } else if ((node.nodeType === Node.ELEMENT_NODE) && (setRange(node.childNodes))) return true;
+ }
+ return false;
+ }
+ };
+
+ onChange = (e: FormEvent<HTMLDivElement>) => {
+ const cursorPos = this.cursorPosition;
const targVal = e.currentTarget.innerText;
if (!(targVal.startsWith(':=') || targVal.startsWith('='))) {
this._overlayDisposer?.();
@@ -83,6 +137,9 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
} else if (!this._overlayDisposer) {
this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 });
}
+ this._content = targVal;
+ this.setContent(targVal);
+ setTimeout(() => this.restoreCursorPosition(cursorPos), 0);
this._props.highlightCells?.(targVal);
};
@@ -92,7 +149,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
switch (e.key) {
case 'Tab':
e.stopPropagation();
- this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, false);
+ this.finalizeEdit(e.shiftKey, false, false);
break;
case 'Backspace':
e.stopPropagation();
@@ -100,7 +157,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
case 'Enter':
e.stopPropagation();
if (!e.ctrlKey) {
- this.finalizeEdit(e.currentTarget.value, e.shiftKey, false, true);
+ this.finalizeEdit(e.shiftKey, false, true);
}
break;
case 'Escape':
@@ -128,9 +185,9 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
};
@action
- finalizeEdit(value: string, shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
- //if (invalid) raise error
- if (this._props.SetValue(value, shiftDown, enterKey)) {
+ finalizeEdit(shiftDown: boolean, lostFocus: boolean, enterKey: boolean) {
+ this.setContent(this._content);
+ if (this._props.SetValue(this._content, shiftDown, enterKey)) {
this._editing = false;
} else {
this._editing = false;
@@ -151,31 +208,32 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro
this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : this.props.contents ? this._props.contents?.valueOf() : ''
}
</span>
- }
+ };
renderEditor = () => {
return (
- <div
+ <div
contentEditable
- className="editableView-input"
+ className='editableView-static'
ref={r => { this._inputref = r; }}
style={{ overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, }}
- onBlur={e => this.finalizeEdit(this._inputref ? this._inputref.innerText : '', false, true, false)}
+ onBlur={e => this.finalizeEdit(false, true, false)}
autoFocus
onInput={this.onChange}
onKeyDown={this.onKeyDown}
onPointerDown={e => e.stopPropagation}
onClick={e => e.stopPropagation}
onPointerUp={e => e.stopPropagation}
+ dangerouslySetInnerHTML={{ __html: `<span>${this._displayedContent}</span>` }}
>
</div>
);
- }
+ };
render() {
const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n');
if ((this._editing && gval !== undefined)) {
- return <div>{this.renderEditor()}</div>;
+ return <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}>{this.renderEditor()}</div>;
} else return (
this._props.contents instanceof ObjectField ? null : (
<div