(core) Readonly editors

Summary:
Grist should not prevent read-only viewers from opening cell editors since they usually provide much more information than is visible in a cell.

Every editor was enhanced with a read-only mode that provides the same information available for an editor but doesn't allow to change the underlying data.

Test Plan: Browser tests

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2842
This commit is contained in:
Jarosław Sadziński
2021-06-17 18:41:07 +02:00
parent d3dc910784
commit 698c9d4e40
21 changed files with 356 additions and 141 deletions

View File

@@ -35,6 +35,7 @@ export interface ITokenFieldOptions {
acOptions?: IAutocompleteOptions<IToken & ACItem>;
openAutocompleteOnFocus?: boolean;
styles?: ITokenFieldStyles;
readonly?: boolean;
// Allows overriding how tokens are copied to the clipboard, or retrieved from it.
// By default, tokens are placed into clipboard as text/plain comma-separated token labels, with
@@ -89,7 +90,13 @@ export class TokenField extends Disposable {
this.autoDispose(this._tokens.addListener(this._recordUndo.bind(this)));
// Use overridden styles if any were provided.
const {cssTokenField, cssToken, cssInputWrapper, cssTokenInput, cssDeleteButton, cssDeleteIcon} =
const {
cssTokenField,
cssToken,
cssInputWrapper,
cssTokenInput,
cssDeleteButton,
cssDeleteIcon} =
{...tokenFieldStyles, ..._options.styles};
function stop(ev: Event) {
@@ -101,15 +108,18 @@ export class TokenField extends Disposable {
{tabIndex: '-1'},
dom.forEach(this._tokens, (t) =>
cssToken(this._options.renderToken(t.token),
cssDeleteButton(cssDeleteIcon('CrossSmall'), testId('tokenfield-delete')),
dom.cls('selected', (use) => use(this._selection).has(t)),
dom.on('click', (ev) => this._onTokenClick(ev, t)),
dom.on('mousedown', (ev) => this._onMouseDown(ev, t)),
_options.readonly ? null : [
cssDeleteButton(cssDeleteIcon('CrossSmall'), testId('tokenfield-delete')),
dom.on('click', (ev) => this._onTokenClick(ev, t)),
dom.on('mousedown', (ev) => this._onMouseDown(ev, t))
],
testId('tokenfield-token')
),
),
cssInputWrapper(
this._textInput = cssTokenInput(
dom.boolAttr("readonly", this._options.readonly ?? false),
dom.on('focus', this._onInputFocus.bind(this)),
dom.on('blur', () => { this._acHolder.clear(); }),
(this._acOptions ?
@@ -174,6 +184,8 @@ export class TokenField extends Disposable {
// Open the autocomplete dropdown, if autocomplete was configured in the options.
private _openAutocomplete() {
// don't open dropdown in a readonly mode
if (this._options.readonly) { return; }
if (this._acOptions && this._acHolder.isEmpty()) {
Autocomplete.create(this._acHolder, this._textInput, this._acOptions);
}

View File

@@ -130,6 +130,22 @@ function attr(attrName, valueOrFunc) {
}
exports.attr = attr;
/**
* Sets or removes a boolean attribute of a DOM element. According to the spec, empty string is a
* valid true value for the attribute, and the false value is indicated by the attribute's absence.
* @param {String} attrName The name of the attribute to bind, e.g. 'href'.
* @param {Object} valueOrFunc An observable, a constant, or a function for a computed observable.
*/
function boolAttr(attrName, valueOrFunc) {
return makeBinding(valueOrFunc, function(elem, value) {
if (!value) {
elem.removeAttribute(attrName);
} else {
elem.setAttribute(attrName, '');
}
});
}
exports.boolAttr = boolAttr;
/**
* Keeps the style property `property` of a DOM element in sync with an observable value.