mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-19 16:52:29 +00:00
136 lines
3.7 KiB
TypeScript
136 lines
3.7 KiB
TypeScript
|
/**
|
||
|
* Implements a widget for showing and editing a list of colIds. It offers a select dropdown to
|
||
|
* add a new column, and allows removing already-added columns.
|
||
|
*/
|
||
|
import {aclSelect, cssSelect} from 'app/client/aclui/ACLSelect';
|
||
|
import {colors} from 'app/client/ui2018/cssVars';
|
||
|
import {icon} from 'app/client/ui2018/icons';
|
||
|
import {Computed, dom, Observable, styled} from 'grainjs';
|
||
|
|
||
|
export function aclColumnList(colIds: Observable<string[]>, validColIds: string[]) {
|
||
|
// Define some helpers functions.
|
||
|
function removeColId(colId: string) {
|
||
|
colIds.set(colIds.get().filter(c => (c !== colId)));
|
||
|
}
|
||
|
function addColId(colId: string) {
|
||
|
colIds.set([...colIds.get(), colId]);
|
||
|
selectBox.focus();
|
||
|
}
|
||
|
function onFocus(ev: FocusEvent) {
|
||
|
editing.set(true);
|
||
|
// Focus the select box, except when focus just moved from it, e.g. after Shift-Tab.
|
||
|
if (ev.relatedTarget !== selectBox) {
|
||
|
selectBox.focus();
|
||
|
}
|
||
|
}
|
||
|
function onBlur() {
|
||
|
if (!selectBox.matches('.weasel-popup-open') && colIds.get().length > 0) {
|
||
|
editing.set(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The observable for the selected element is a Computed, with a callback for being set, which
|
||
|
// adds the selected colId to the list.
|
||
|
const newColId = Computed.create(null, (use) => '')
|
||
|
.onWrite((value) => { setTimeout(() => addColId(value), 0); });
|
||
|
|
||
|
// We don't allow adding the same column twice, so for the select dropdown build a list of
|
||
|
// unused colIds.
|
||
|
const unusedColIds = Computed.create(null, colIds, (use, _colIds) => {
|
||
|
const used = new Set(_colIds);
|
||
|
return validColIds.filter(c => !used.has(c));
|
||
|
});
|
||
|
|
||
|
// The "editing" observable determines which of two states is active: to show or to edit.
|
||
|
const editing = Observable.create(null, !colIds.get().length);
|
||
|
|
||
|
let selectBox: HTMLElement;
|
||
|
return cssColListWidget({tabIndex: '0'},
|
||
|
dom.autoDispose(unusedColIds),
|
||
|
cssColListWidget.cls('-editing', editing),
|
||
|
dom.on('focus', onFocus),
|
||
|
dom.forEach(colIds, colId =>
|
||
|
cssColItem(
|
||
|
cssColId(colId),
|
||
|
cssColItemIcon(icon('CrossSmall'),
|
||
|
dom.on('click', () => removeColId(colId))
|
||
|
)
|
||
|
)
|
||
|
),
|
||
|
cssNewColItem(
|
||
|
dom.update(
|
||
|
selectBox = aclSelect(newColId, unusedColIds, {defaultLabel: '[Add Column]'}),
|
||
|
cssSelect.cls('-active'),
|
||
|
dom.on('blur', onBlur),
|
||
|
dom.onKeyDown({Escape: onBlur}),
|
||
|
// If starting out in edit mode, focus the select box.
|
||
|
(editing.get() ? (elem) => { setTimeout(() => elem.focus(), 0); } : null)
|
||
|
),
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
|
||
|
const cssColListWidget = styled('div', `
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
gap: 4px;
|
||
|
position: relative;
|
||
|
outline: none;
|
||
|
margin: 6px 8px;
|
||
|
cursor: pointer;
|
||
|
border-radius: 4px;
|
||
|
|
||
|
border: 1px solid transparent;
|
||
|
&:not(&-editing):hover {
|
||
|
border: 1px solid ${colors.darkGrey};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssColItem = styled('div', `
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: space-between;
|
||
|
border-radius: 3px;
|
||
|
padding-left: 6px;
|
||
|
padding-right: 2px;
|
||
|
|
||
|
.${cssColListWidget.className}-editing & {
|
||
|
background-color: ${colors.mediumGreyOpaque};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssColId = styled('div', `
|
||
|
flex: auto;
|
||
|
height: 24px;
|
||
|
line-height: 24px;
|
||
|
min-width: 0;
|
||
|
overflow: hidden;
|
||
|
text-overflow: ellipsis;
|
||
|
`);
|
||
|
|
||
|
const cssNewColItem = styled('div', `
|
||
|
margin-top: 2px;
|
||
|
display: none;
|
||
|
.${cssColListWidget.className}-editing & {
|
||
|
display: flex;
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
const cssColItemIcon = styled('div', `
|
||
|
flex: none;
|
||
|
height: 16px;
|
||
|
width: 16px;
|
||
|
border-radius: 16px;
|
||
|
display: none;
|
||
|
cursor: default;
|
||
|
--icon-color: ${colors.slate};
|
||
|
&:hover {
|
||
|
background-color: ${colors.slate};
|
||
|
--icon-color: ${colors.light};
|
||
|
}
|
||
|
.${cssColListWidget.className}-editing & {
|
||
|
display: flex;
|
||
|
}
|
||
|
`);
|