gristlabs_grist-core/app/client/widgets/NTextEditor.ts
Dmitry S 48e90c4998 (core) Change how formula columns can be converted to data.
Summary:
- No longer convert data columns to formula by typing a leading "=". Instead,
  show a tooltip with a link to click if the conversion was intended.
- No longer convert a formula column to data by deleting its formula. Leave the
  column empty instead.
- Offer the option "Convert formula to data" in column menu for formulas.
- Offer the option to "Clear column"
- If a subset of rows is shown, offer "Clear values" and "Clear entire column".

- Add logic to detect when a view shows a subset of all rows.
- Factor out showTooltip() from showTransientTooltip().

- Add a bunch of test cases to cover various combinations (there are small
  variations in options depending on whether all rows are shown, on whether
  multiple columns are selected, and whether columns include data columns).

Test Plan: Added a bunch of test cases.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2746
2021-03-05 12:42:57 -05:00

109 lines
4.2 KiB
TypeScript

/**
* This is a copy of TextEditor.js, converted to typescript.
*/
import {createGroup} from 'app/client/components/commands';
import {testId} from 'app/client/ui2018/cssVars';
import {createMobileButtons, getButtonMargins} from 'app/client/widgets/EditorButtons';
import {EditorPlacement} from 'app/client/widgets/EditorPlacement';
import {NewBaseEditor, Options} from 'app/client/widgets/NewBaseEditor';
import {CellValue} from "app/common/DocActions";
import {undefDefault} from 'app/common/gutil';
import {dom} from 'grainjs';
export class NTextEditor extends NewBaseEditor {
protected cellEditorDiv: HTMLElement;
protected textInput: HTMLTextAreaElement;
protected commandGroup: any;
private _dom: HTMLElement;
private _editorPlacement: EditorPlacement;
private _contentSizer: HTMLElement;
private _alignment: string;
// Note: TextEditor supports also options.placeholder for use by derived classes, but this is
// easy to apply to this.textInput without needing a separate option.
constructor(options: Options) {
super(options);
this.commandGroup = this.autoDispose(createGroup(options.commands, null, true));
this._alignment = options.field.widgetOptionsJson.peek().alignment || 'left';
this._dom = dom('div.default_editor',
this.cellEditorDiv = dom('div.celleditor_cursor_editor', testId('widget-text-editor'),
this._contentSizer = dom('div.celleditor_content_measure'),
this.textInput = dom('textarea', dom.cls('celleditor_text_editor'),
dom.style('text-align', this._alignment),
dom.prop('value', undefDefault(options.editValue, String(options.cellValue))),
this.commandGroup.attach(),
// Resize the textbox whenever user types in it.
dom.on('input', () => this.resizeInput())
)
),
createMobileButtons(options.commands),
);
}
public attach(cellElem: Element): void {
// Attach the editor dom to page DOM.
this._editorPlacement = EditorPlacement.create(this, this._dom, cellElem, {margins: getButtonMargins()});
// Reposition the editor if needed for external reasons (in practice, window resize).
this.autoDispose(this._editorPlacement.onReposition.addListener(this.resizeInput, this));
this.setSizerLimits();
// Once the editor is attached to DOM, resize it to content, focus, and set cursor.
this.resizeInput();
this.textInput.focus();
const pos = Math.min(this.options.cursorPos, this.textInput.value.length);
this.textInput.setSelectionRange(pos, pos);
}
public getDom(): HTMLElement {
return this._dom;
}
public getCellValue(): CellValue {
return this.textInput.value;
}
public getTextValue() {
return this.textInput.value;
}
public getCursorPos() {
return this.textInput.selectionStart;
}
public setSizerLimits() {
// Set the max width of the sizer to the max we could possibly grow to, so that it knows to wrap
// once we reach it.
const maxSize = this._editorPlacement.calcSizeWithPadding(this.textInput,
{width: Infinity, height: Infinity}, {calcOnly: true});
this._contentSizer.style.maxWidth = Math.ceil(maxSize.width) + 'px';
}
/**
* Helper which resizes textInput to match its content. It relies on having a contentSizer element
* with the same font/size settings as the textInput, and on having `calcSize` helper,
* which is provided by the EditorPlacement class.
*/
protected resizeInput() {
const textInput = this.textInput;
// \u200B is a zero-width space; it is used so the textbox will expand vertically
// on newlines, but it does not add any width.
this._contentSizer.textContent = textInput.value + '\u200B';
const rect = this._contentSizer.getBoundingClientRect();
// Allow for a bit of extra space after the cursor (only desirable when text is left-aligned).
if (this._alignment === "left") {
// Modifiable in modern browsers: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
rect.width += 16;
}
const size = this._editorPlacement.calcSizeWithPadding(textInput, rect);
textInput.style.width = size.width + 'px';
textInput.style.height = size.height + 'px';
}
}