import {Command} from 'app/client/components/commands'; import {NeedUpgradeError, reportError} from 'app/client/models/errors'; import {colors, testId, vars} from 'app/client/ui2018/cssVars'; import {cssSelectBtn} from 'app/client/ui2018/select'; import {IconName} from 'app/client/ui2018/IconList'; import {icon} from 'app/client/ui2018/icons'; import {commonUrls} from 'app/common/gristUrls'; import {dom, DomElementArg, DomElementMethod} from 'grainjs'; import {MaybeObsArray, Observable, styled} from 'grainjs'; import * as weasel from 'popweasel'; import {IAutocompleteOptions} from 'popweasel'; export interface IOptionFull { value: T; label: string; disabled?: boolean; icon?: IconName; } // For string options, we can use a string for label and value without wrapping into an object. export type IOption = (T & string) | IOptionFull; export function menu(createFunc: weasel.MenuCreateFunc, options?: weasel.IMenuOptions): DomElementMethod { return weasel.menu(createFunc, {...defaults, ...options}); } // TODO Weasel doesn't allow other options for submenus, but probably should. export type ISubMenuOptions = weasel.ISubMenuOptions & weasel.IPopupOptions; export function menuItemSubmenu( submenu: weasel.MenuCreateFunc, options: ISubMenuOptions, ...args: DomElementArg[] ): Element { return weasel.menuItemSubmenu(submenu, {...defaults, ...options}, ...args); } const cssMenuElem = styled('div', ` font-family: ${vars.fontFamily}; font-size: ${vars.mediumFontSize}; line-height: initial; max-width: 400px; padding: 8px 0px 16px 0px; box-shadow: 0 2px 20px 0 rgba(38,38,51,0.6); min-width: 160px; z-index: 999; --weaseljs-selected-background-color: ${vars.primaryBg}; --weaseljs-menu-item-padding: 8px 24px; @media print { & { display: none; } } `); const menuItemStyle = ` justify-content: flex-start; align-items: center; --icon-color: ${colors.lightGreen}; .${weasel.cssMenuItem.className}-sel { --icon-color: ${colors.light}; } &.disabled { cursor: default; opacity: 0.2; } `; export const menuCssClass = cssMenuElem.className; // Add grist-floating-menu class to support existing browser tests const defaults = { menuCssClass: menuCssClass + ' grist-floating-menu' }; /** * Creates a select dropdown widget. The observable `obs` reflects the value of the selected * option, and `optionArray` is an array (regular or observable) of option values and labels. * These may be either strings, or {label, value, icon, disabled} objects. Icons are optional * and must be IconName strings from 'app/client/ui2018/IconList'. * * The type of value may be any type at all; it is opaque to this widget. * * If obs is set to an invalid or disabled value, then defLabel option is used to determine the * label that the select box will show, blank by default. * * Usage: * const fruit = observable("apple"); * select(fruit, ["apple", "banana", "mango"]); * * const employee = observable(17); * const allEmployees = Observable.create(owner, [ * {value: 12, label: "Bob", disabled: true}, * {value: 17, label: "Alice"}, * {value: 21, label: "Eve"}, * ]); * select(employee, allEmployees, {defLabel: "Select employee:"}); * * Note that this select element is not compatible with browser address autofill for usage in * forms, and that formSelect should be used for this purpose. */ export function select(obs: Observable, optionArray: MaybeObsArray>, options: weasel.ISelectUserOptions = {}) { const _menu = cssSelectMenuElem(testId('select-menu')); const _btn = cssSelectBtn(testId('select-open')); const selectOptions = { buttonArrow: cssInlineCollapseIcon('Collapse'), menuCssClass: _menu.className, buttonCssClass: _btn.className, ...options, }; return weasel.select(obs, optionArray, selectOptions, (op) => cssOptionRow( op.icon ? cssOptionRowIcon(op.icon) : null, cssOptionLabel(op.label), testId('select-row') ) ) as HTMLElement; // TODO: should be changed in weasel } /** * Same as select(), but the main element looks like a link rather than a button. */ export function linkSelect(obs: Observable, optionArray: MaybeObsArray>, options: weasel.ISelectUserOptions = {}) { const _btn = cssSelectBtnLink(testId('select-open')); const elem = select(obs, optionArray, {buttonCssClass: _btn.className, ...options}); // It feels strange to have focus stay on this link; remove tabIndex that makes it focusable. elem.removeAttribute('tabIndex'); return elem; } /** * Creates a select dropdown widget that is more ideal for forms. Implemented using the