#26 - Fix for arrow selection in the dropdown

This commit is contained in:
Elio Struyf
2020-12-10 21:35:31 +01:00
parent 886584b925
commit daac7883b8
4 changed files with 68 additions and 62 deletions
+16 -1
View File
@@ -104,10 +104,25 @@
border: 1px solid rgba(0, 0, 0, .9);
}
.article__tags input {
.article__tags__input input {
border: 1px solid var(--vscode-inputValidation-infoBorder);
}
.article__tags__input.freeform {
position: relative;
}
.article__tags__input.freeform input {
padding-right: 35px;
}
.article__tags__input button {
position: absolute;
top: 1px;
right: 1px;
width: 30px;
}
.article__tags ul {
color: var(--vscode-dropdown-foreground);
background-color: var(--vscode-dropdown-background);
+2 -2
View File
@@ -38,7 +38,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
(settings && settings.tags && settings.tags.length > 0) && (
<TagPicker type={TagType.tags}
crntSelected={metadata.tags || []}
options={settings.tags.map(c => ({ key: c.toLowerCase(), value: c }))}
options={settings.tags}
freeform={settings.freeform}
focussed={focusElm === TagType.tags}
unsetFocus={unsetFocus} />
@@ -48,7 +48,7 @@ export const ViewPanel: React.FunctionComponent<IViewPanelProps> = (props: React
(settings && settings.categories && settings.categories.length > 0) && (
<TagPicker type={TagType.categories}
crntSelected={metadata.categories || []}
options={settings.categories.map(c => ({ key: c.toLowerCase(), value: c }))}
options={settings.categories}
freeform={settings.freeform}
focussed={focusElm === TagType.categories}
unsetFocus={unsetFocus} />
+47 -55
View File
@@ -6,12 +6,10 @@ import { TagType } from '../TagType';
import { MessageHelper } from '../helper/MessageHelper';
import Downshift from 'downshift';
export interface KeyValue { key: string, value: string };
export interface ITagPickerProps {
type: string;
crntSelected: string[];
options: KeyValue[];
options: string[];
freeform: boolean;
focussed: boolean;
unsetFocus: () => void;
@@ -23,7 +21,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
const [ inputValue, setInputValue ] = React.useState<string>("");
const prevSelected = usePrevious(crntSelected);
const inputRef = React.useRef<HTMLInputElement | null>(null);
const dsRef = React.useRef<Downshift<KeyValue> | null>(null);
const dsRef = React.useRef<Downshift<string> | null>(null);
/**
* Removes an option
@@ -60,7 +58,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
if (focussed && inputRef && inputRef.current) {
inputRef.current.focus();
}
}
};
/**
* On item selection
@@ -68,13 +66,12 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
* @param compState
*/
const onSelect = (selectedItem: string | null) => {
console.log(selectedItem)
if (selectedItem) {
let value = selectedItem || "";
const item = options.find(o => o.key === selectedItem.toLowerCase());
if (item && item.value) {
value = item.value;
const item = options.find(o => o.toLowerCase() === selectedItem.toLowerCase());
if (item) {
value = item;
}
const uniqValues = Array.from(new Set([...selected, value]));
@@ -82,46 +79,27 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
sendUpdate(uniqValues);
setInputValue("");
}
}
};
/**
* Allow free value entries
* @param event
* Inserts a tag which is not known
* @param closeMenu
*/
const onEnterSelection = (event: React.KeyboardEvent<HTMLInputElement>, closeCb: () => void) => {
if (freeform && event.key === "Enter" && inputValue) {
setTimeout(() => {
onSelect(inputValue);
}, 100);
} else if (event.key === "Escape") {
if (closeCb) {
closeCb();
}
} else {
return true;
const insertUnkownTag = (closeMenu: (cb?: any) => void) => {
if (inputValue) {
onSelect(inputValue);
closeMenu();
}
}
};
/**
* Filters the options which can be selected
* @param option
* @param inputValue
*/
const filterList = (option: KeyValue, inputValue: string | null) => {
return !selected.includes(option.value) && option.key.includes((inputValue || "").toLowerCase());
}
function stateReducer(state: any, changes: any) {
// this prevents the menu from being closed when the user
// selects an item with a keyboard or mouse
switch (changes.type) {
case Downshift.stateChangeTypes.keyDownEnter:
console.log(`Enter`, JSON.stringify(changes));
return changes;
default:
return changes
}
}
const filterList = (option: string, inputValue: string | null) => {
return !selected.includes(option) && option.toLowerCase().includes((inputValue || "").toLowerCase());
};
React.useEffect(() => {
setTimeout(() => {
@@ -140,30 +118,44 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
<h3>{type}</h3>
<Downshift ref={dsRef}
onChange={(selected) => onSelect(selected?.key || "")}
itemToString={item => (item ? item.value : '')}
onChange={(selected) => onSelect(selected || "")}
itemToString={item => (item ? item : '')}
inputValue={inputValue}
onInputValueChange={(value) => setInputValue(value)}
stateReducer={stateReducer}>
onInputValueChange={(value) => setInputValue(value)}>
{
({ getInputProps, getItemProps, getMenuProps, isOpen, inputValue, getRootProps, openMenu, closeMenu }) => (
({ getInputProps, getItemProps, getMenuProps, isOpen, inputValue, getRootProps, openMenu, closeMenu, clearSelection }) => (
<>
<div {...getRootProps(undefined, {suppressRefError: true})}>
<input {...getInputProps({
ref: inputRef,
onFocus: openMenu as any,
onClick: openMenu as any,
onBlur: () => { closeMenu(); unsetFocus(); },
onKeyDown: (e) => onEnterSelection(e, closeMenu)
})}
<div {...getRootProps(undefined, {suppressRefError: true})} className={`article__tags__input ${freeform ? 'freeform' : ''}`}>
<input {
...getInputProps({
ref: inputRef,
onFocus: openMenu as any,
onClick: openMenu as any,
onBlur: () => {
closeMenu();
unsetFocus();
if (!inputValue) {
clearSelection();
}
}
})
}
placeholder={`Pick your ${type.toLowerCase()}`} />
{
freeform && (
<button title={`Add the unknown tag`}
disabled={!inputValue}
onClick={() => insertUnkownTag(closeMenu)}>+</button>
)
}
</div>
<ul className={`article__tags__dropbox ${isOpen ? "open" : "closed" }`} {...getMenuProps()}>
{
isOpen ? options.filter((option) => filterList(option, inputValue)).map((item, index) => (
<li {...getItemProps({ key: item.value, index, item })} >
{ item.value }
<li {...getItemProps({ key: item, index, item })} >
{ item }
</li>
)) : null
}
@@ -173,7 +165,7 @@ export const TagPicker: React.FunctionComponent<ITagPickerProps> = (props: React
}
</Downshift>
<Tags values={selected} onRemove={onRemove} onCreate={onCreate} options={options} />
<Tags values={selected.sort((a: string, b: string) => a.toLowerCase() < b.toLowerCase() ? -1 : 1 )} onRemove={onRemove} onCreate={onCreate} options={options} />
</div>
);
};
+3 -4
View File
@@ -1,10 +1,9 @@
import * as React from 'react';
import { Tag } from './Tag';
import { KeyValue } from './TagPicker';
export interface ITagsProps {
values: string[];
options: KeyValue[];
options: string[];
onCreate: (tags: string) => void;
onRemove: (tags: string) => void;
@@ -13,8 +12,8 @@ export interface ITagsProps {
export const Tags: React.FunctionComponent<ITagsProps> = (props: React.PropsWithChildren<ITagsProps>) => {
const { values, options, onCreate, onRemove } = props;
const knownTags = values.filter(v => options.map(o => o.value).includes(v));
const unknownTags = values.filter(v => !options.map(o => o.value).includes(v));
const knownTags = values.filter(v => options.includes(v));
const unknownTags = values.filter(v => !options.includes(v));
return (
<div className={`article__tags__items`}>