(core) Forms Improvements

Summary:
 - Forms now have a reset button.
 - Choice and Reference fields in forms now have an improved select menu.
 - Formula and attachments column types are no longer mappable or visible in forms.
 - Fields in a form widget are now removed if their column is deleted.
 - The preview button in a published form widget has been replaced with a view button. It now opens the published form in a new tab.
 - A new share menu for published form widgets, with options to copy a link or embed code.
 - Forms can now have multiple sections.
 - Form widgets now indicate when publishing is unavailable (e.g. in forks or unsaved documents).
 - General improvements to form styling.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D4203
This commit is contained in:
George Gevoian
2024-03-20 10:51:59 -04:00
parent aff9c7075c
commit 418681915e
40 changed files with 1643 additions and 617 deletions

View File

@@ -15,7 +15,6 @@ import split = require("lodash/split");
export interface ACItem {
// This should be a trimmed lowercase version of the item's text. It may be an accessor.
// Note that items with empty cleanText are never suggested.
cleanText: string;
}
@@ -65,6 +64,19 @@ interface Word {
pos: number; // Position of the word within the item where it occurred.
}
export interface ACIndexOptions {
/** The max number of items to suggest. Defaults to 50. */
maxResults?: number;
/**
* Suggested matches in the same relative order as items, rather than by score.
*
* Defaults to false.
*/
keepOrder?: boolean;
/** Show items with an empty `cleanText`. Defaults to false. */
showEmptyItems?: boolean;
}
/**
* Implements a search index. It doesn't currently support updates; when any values change, the
* index needs to be rebuilt from scratch.
@@ -75,11 +87,12 @@ export class ACIndexImpl<Item extends ACItem> implements ACIndex<Item> {
// All words from _allItems, sorted.
private _words: Word[];
private _maxResults = this._options.maxResults ?? 50;
private _keepOrder = this._options.keepOrder ?? false;
private _showEmptyItems = this._options.showEmptyItems ?? false;
// Creates an index for the given list of items.
// The max number of items to suggest may be set using _maxResults (default is 50).
// If _keepOrder is true, best matches will be suggested in the order they occur in items,
// rather than order by best score.
constructor(items: Item[], private _maxResults: number = 50, private _keepOrder = false) {
constructor(items: Item[], private _options: ACIndexOptions = {}) {
this._allItems = items.slice(0);
// Collects [word, occurrence, position] tuples for all words in _allItems.
@@ -132,7 +145,9 @@ export class ACIndexImpl<Item extends ACItem> implements ACIndex<Item> {
// Append enough non-matching indices to reach maxResults.
for (let i = 0; i < this._allItems.length && itemIndices.length < this._maxResults; i++) {
if (this._allItems[i].cleanText && !myMatches.has(i)) {
if (myMatches.has(i)) { continue; }
if (this._allItems[i].cleanText || this._showEmptyItems) {
itemIndices.push(i);
}
}

View File

@@ -22,6 +22,7 @@ export type { IOption, IOptionFull } from 'popweasel';
export { getOptionFull } from 'popweasel';
export interface ISimpleListOpt<T, U extends IOption<T> = IOption<T>> {
matchTriggerElemWidth?: boolean;
headerDom?(): DomArg<HTMLElement>;
renderItem?(item: U): DomArg<HTMLElement>;
}
@@ -42,6 +43,14 @@ export class SimpleList<T, U extends IOption<T> = IOption<T>> extends Disposable
const renderItem = opt.renderItem || ((item: U) => getOptionFull(item).label);
this.content = cssMenuWrap(
dom('div',
elem => {
if (opt.matchTriggerElemWidth) {
const style = elem.style;
style.minWidth = _ctl.getTriggerElem().getBoundingClientRect().width + 'px';
style.marginLeft = '0px';
style.marginRight = '0px';
}
},
{class: menuCssClass + ' grist-floating-menu'},
cssMenu.cls(''),
cssMenuExt.cls(''),
@@ -113,7 +122,7 @@ export class SimpleList<T, U extends IOption<T> = IOption<T>> extends Disposable
private _doAction(value: T | null) {
// If value is null, simply close the menu. This happens when pressing enter with no element
// selected.
if (value) { this._action(value); }
if (value !== null) { this._action(value); }
this._ctl.close();
}